fix bug on ecconreset

This commit is contained in:
monoid 2021-01-25 21:48:00 +09:00
parent 98a5f52dab
commit 2b5839826b
3 changed files with 100 additions and 48 deletions

View File

@ -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';

View File

@ -1,15 +1,55 @@
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'; let ZipStreamCache: { [path: string]: [StreamZip, number] } = {};
async function acquireZip(path: string) {
if (ZipStreamCache[path] === undefined) {
const ret = await readZip(path);
if (ZipStreamCache[path] === undefined) {
ZipStreamCache[path] = [ret, 1];
console.log(`acquire ${path} 1`);
return ret;
}
ret.close();
}
const [ret, refCount] = ZipStreamCache[path];
ZipStreamCache[path] = [ret, refCount + 1];
console.log(`acquire ${path} ${refCount + 1}`);
return ret;
}
function releaseZip(path: string) {
const obj = ZipStreamCache[path];
if (obj === undefined) throw new Error("error! key invalid");
const [ref, refCount] = obj;
console.log(`release ${path} : ${refCount}`);
if (refCount === 1) {
ref.close();
delete ZipStreamCache[path];
return;
}
ZipStreamCache[path] = [ref, refCount - 1];
}
async function renderZipImage(ctx: Context, path: string, page: number) { async function renderZipImage(ctx: Context, path: string, page: number) {
const image_ext = ['gif', 'png', 'jpeg', 'bmp', 'webp', 'jpg']; const image_ext = ["gif", "png", "jpeg", "bmp", "webp", "jpg"];
let zip = await readZip(path); console.log(`opened ${page}`);
const entries = entriesByNaturalOrder(zip).filter(x=>{ let zip = await acquireZip(path);
const ext = x.name.split('.').pop(); const entries = entriesByNaturalOrder(zip).filter((x) => {
const ext = x.name.split(".").pop();
return ext !== undefined && image_ext.includes(ext); return ext !== undefined && image_ext.includes(ext);
}); });
if (0 <= page && page < entries.length) { if (0 <= page && page < entries.length) {
@ -18,17 +58,28 @@ async function renderZipImage(ctx: Context,path : string, page:number){
if (since_last_modified(ctx, last_modified)) { if (since_last_modified(ctx, last_modified)) {
return; return;
} }
const read_stream = (await createReadStreamFromZip(zip,entry)); const read_stream = (await createReadableStreamFromZip(zip, entry));
read_stream.on('close',()=>zip.close()); /**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.body = read_stream;
ctx.response.length = entry.size; ctx.response.length = entry.size;
//console.log(`${entry.name}'s ${page}:${entry.size}`); //console.log(`${entry.name}'s ${page}:${entry.size}`);
ctx.response.type = entry.name.split(".").pop() as string; ctx.response.type = entry.name.split(".").pop() as string;
ctx.status = 200; ctx.status = 200;
ctx.set('Date', new Date().toUTCString()); ctx.set("Date", new Date().toUTCString());
ctx.set("Last-Modified", last_modified.toUTCString()); ctx.set("Last-Modified", last_modified.toUTCString());
} } else {
else{
ctx.status = 404; ctx.status = 404;
} }
} }
@ -40,7 +91,7 @@ export class MangaRouter extends Router<ContentContext>{
await renderZipImage(ctx, ctx.state.location.path, 0); await renderZipImage(ctx, ctx.state.location.path, 0);
}); });
this.get("/:page(\\d+)", async (ctx, next) => { this.get("/:page(\\d+)", async (ctx, next) => {
const page = Number.parseInt(ctx.params['page']); const page = Number.parseInt(ctx.params["page"]);
await renderZipImage(ctx, ctx.state.location.path, page); await renderZipImage(ctx, ctx.state.location.path, page);
}); });
this.get("/thumbnail", async (ctx, next) => { this.get("/thumbnail", async (ctx, next) => {

View File

@ -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)});