fix bug on ecconreset
This commit is contained in:
parent
98a5f52dab
commit
2b5839826b
@ -1,5 +1,5 @@
|
|||||||
import {ContentFile, createDefaultClass,registerContentReferrer, ContentConstructOption} from './file';
|
import {ContentFile, createDefaultClass,registerContentReferrer, ContentConstructOption} from './file';
|
||||||
import {readZip,createReadStreamFromZip, readAllFromZip} from '../util/zipwrap';
|
import {readZip, readAllFromZip} from '../util/zipwrap';
|
||||||
import { DocumentBody } from '../model/doc';
|
import { DocumentBody } from '../model/doc';
|
||||||
import {extname} from 'path';
|
import {extname} from 'path';
|
||||||
|
|
||||||
|
@ -1,52 +1,103 @@
|
|||||||
|
import { Context, DefaultContext, DefaultState, Next } from "koa";
|
||||||
|
import {
|
||||||
|
createReadableStreamFromZip,
|
||||||
|
entriesByNaturalOrder,
|
||||||
|
readZip,
|
||||||
|
} from "../util/zipwrap";
|
||||||
|
import { since_last_modified } from "./util";
|
||||||
|
import { ContentContext } from "./context";
|
||||||
|
import Router from "koa-router";
|
||||||
|
import StreamZip from "node-stream-zip";
|
||||||
|
|
||||||
import {Context, DefaultContext, DefaultState, Next} from 'koa';
|
/**
|
||||||
import {readZip,entriesByNaturalOrder,createReadStreamFromZip} from '../util/zipwrap';
|
* zip stream cache.
|
||||||
import {since_last_modified} from './util';
|
*/
|
||||||
import {ContentContext} from './context';
|
|
||||||
import Router from 'koa-router';
|
|
||||||
|
|
||||||
async function renderZipImage(ctx: Context,path : string, page:number){
|
let ZipStreamCache: { [path: string]: [StreamZip, number] } = {};
|
||||||
const image_ext = ['gif', 'png', 'jpeg', 'bmp', 'webp', 'jpg'];
|
|
||||||
let zip = await readZip(path);
|
async function acquireZip(path: string) {
|
||||||
const entries = entriesByNaturalOrder(zip).filter(x=>{
|
if (ZipStreamCache[path] === undefined) {
|
||||||
const ext = x.name.split('.').pop();
|
const ret = await readZip(path);
|
||||||
return ext !== undefined && image_ext.includes(ext);
|
if (ZipStreamCache[path] === undefined) {
|
||||||
});
|
ZipStreamCache[path] = [ret, 1];
|
||||||
if(0 <= page && page < entries.length){
|
console.log(`acquire ${path} 1`);
|
||||||
const entry = entries[page];
|
return ret;
|
||||||
const last_modified = new Date(entry.time);
|
|
||||||
if(since_last_modified(ctx,last_modified)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const read_stream = (await createReadStreamFromZip(zip,entry));
|
|
||||||
read_stream.on('close',()=>zip.close());
|
|
||||||
ctx.body = read_stream;
|
|
||||||
ctx.response.length = entry.size;
|
|
||||||
//console.log(`${entry.name}'s ${page}:${entry.size}`);
|
|
||||||
ctx.response.type = entry.name.split(".").pop() as string;
|
|
||||||
ctx.status = 200;
|
|
||||||
ctx.set('Date', new Date().toUTCString());
|
|
||||||
ctx.set("Last-Modified",last_modified.toUTCString());
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
ctx.status = 404;
|
|
||||||
}
|
}
|
||||||
|
ret.close();
|
||||||
|
}
|
||||||
|
const [ret, refCount] = ZipStreamCache[path];
|
||||||
|
ZipStreamCache[path] = [ret, refCount + 1];
|
||||||
|
console.log(`acquire ${path} ${refCount + 1}`);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MangaRouter extends Router<ContentContext>{
|
function releaseZip(path: string) {
|
||||||
constructor(){
|
const obj = ZipStreamCache[path];
|
||||||
super();
|
if (obj === undefined) throw new Error("error! key invalid");
|
||||||
this.get("/",async (ctx,next)=>{
|
const [ref, refCount] = obj;
|
||||||
await renderZipImage(ctx,ctx.state.location.path,0);
|
console.log(`release ${path} : ${refCount}`);
|
||||||
});
|
if (refCount === 1) {
|
||||||
this.get("/:page(\\d+)",async (ctx,next)=>{
|
ref.close();
|
||||||
const page = Number.parseInt(ctx.params['page']);
|
delete ZipStreamCache[path];
|
||||||
await renderZipImage(ctx,ctx.state.location.path,page);
|
return;
|
||||||
});
|
}
|
||||||
this.get("/thumbnail", async (ctx,next)=>{
|
ZipStreamCache[path] = [ref, refCount - 1];
|
||||||
await renderZipImage(ctx,ctx.state.location.path,0);
|
}
|
||||||
});
|
|
||||||
|
async function renderZipImage(ctx: Context, path: string, page: number) {
|
||||||
|
const image_ext = ["gif", "png", "jpeg", "bmp", "webp", "jpg"];
|
||||||
|
console.log(`opened ${page}`);
|
||||||
|
let zip = await acquireZip(path);
|
||||||
|
const entries = entriesByNaturalOrder(zip).filter((x) => {
|
||||||
|
const ext = x.name.split(".").pop();
|
||||||
|
return ext !== undefined && image_ext.includes(ext);
|
||||||
|
});
|
||||||
|
if (0 <= page && page < entries.length) {
|
||||||
|
const entry = entries[page];
|
||||||
|
const last_modified = new Date(entry.time);
|
||||||
|
if (since_last_modified(ctx, last_modified)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const read_stream = (await createReadableStreamFromZip(zip, entry));
|
||||||
|
/**Exceptions (ECONNRESET, ECONNABORTED) may be thrown when processing this request
|
||||||
|
* for reasons such as when the browser unexpectedly closes the connection.
|
||||||
|
* Once such an exception is raised, the stream is not properly destroyed,
|
||||||
|
* so there is a problem with the zlib stream being accessed even after the stream is closed.
|
||||||
|
* So it waits for 100 ms and releases it.
|
||||||
|
* Additionaly, there is a risk of memory leak becuase zlib stream is not properly destroyed.
|
||||||
|
* @todo modify function 'stream' in 'node-stream-zip' library to prevent memory leak*/
|
||||||
|
read_stream.once("close", () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
releaseZip(path);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.body = read_stream;
|
||||||
|
ctx.response.length = entry.size;
|
||||||
|
//console.log(`${entry.name}'s ${page}:${entry.size}`);
|
||||||
|
ctx.response.type = entry.name.split(".").pop() as string;
|
||||||
|
ctx.status = 200;
|
||||||
|
ctx.set("Date", new Date().toUTCString());
|
||||||
|
ctx.set("Last-Modified", last_modified.toUTCString());
|
||||||
|
} else {
|
||||||
|
ctx.status = 404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MangaRouter extends Router<ContentContext> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.get("/", async (ctx, next) => {
|
||||||
|
await renderZipImage(ctx, ctx.state.location.path, 0);
|
||||||
|
});
|
||||||
|
this.get("/:page(\\d+)", async (ctx, next) => {
|
||||||
|
const page = Number.parseInt(ctx.params["page"]);
|
||||||
|
await renderZipImage(ctx, ctx.state.location.path, page);
|
||||||
|
});
|
||||||
|
this.get("/thumbnail", async (ctx, next) => {
|
||||||
|
await renderZipImage(ctx, ctx.state.location.path, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MangaRouter;
|
export default MangaRouter;
|
@ -1,5 +1,6 @@
|
|||||||
import StreamZip, { ZipEntry } from 'node-stream-zip';
|
import StreamZip, { ZipEntry } from 'node-stream-zip';
|
||||||
import {orderBy} from 'natural-orderby';
|
import {orderBy} from 'natural-orderby';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
export async function readZip(path : string):Promise<StreamZip>{
|
export async function readZip(path : string):Promise<StreamZip>{
|
||||||
return new Promise((resolve,reject)=>{
|
return new Promise((resolve,reject)=>{
|
||||||
@ -22,7 +23,7 @@ export function entriesByNaturalOrder(zip: StreamZip){
|
|||||||
const ret = orderBy(Object.values(entries),v=>v.name);
|
const ret = orderBy(Object.values(entries),v=>v.name);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
export async function createReadStreamFromZip(zip:StreamZip,entry: ZipEntry):Promise<NodeJS.ReadableStream>{
|
export async function createReadableStreamFromZip(zip:StreamZip,entry: ZipEntry):Promise<NodeJS.ReadableStream>{
|
||||||
return new Promise((resolve,reject)=>{
|
return new Promise((resolve,reject)=>{
|
||||||
zip.stream(entry,(err, stream)=>{
|
zip.stream(entry,(err, stream)=>{
|
||||||
if(stream !== undefined){
|
if(stream !== undefined){
|
||||||
@ -35,7 +36,7 @@ export async function createReadStreamFromZip(zip:StreamZip,entry: ZipEntry):Pro
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
export async function readAllFromZip(zip:StreamZip,entry: ZipEntry):Promise<Buffer>{
|
export async function readAllFromZip(zip:StreamZip,entry: ZipEntry):Promise<Buffer>{
|
||||||
const stream = await createReadStreamFromZip(zip,entry);
|
const stream = await createReadableStreamFromZip(zip,entry);
|
||||||
const chunks:Uint8Array[] = [];
|
const chunks:Uint8Array[] = [];
|
||||||
return new Promise((resolve,reject)=>{
|
return new Promise((resolve,reject)=>{
|
||||||
stream.on('data',(data)=>{chunks.push(data)});
|
stream.on('data',(data)=>{chunks.push(data)});
|
||||||
|
Loading…
Reference in New Issue
Block a user