koa icon indicating copy to clipboard operation
koa copied to clipboard

manually close the connection after ctx.body

Open bogomips opened this issue 7 years ago • 10 comments

I have a route like this: api.put('/my/path', mdw0, body({fields:'body'}),mdw1, mdw2, controller);

The first middleware mdw0 reads some headers before the body is parsed and might decide to send an error if the size is > allowed_size or to call await next()

The problem is that the connection remains active till the end of the upload, so I don't have any advantage in using this strategy, the actual response with 413 arrives to the browser immediately if I kill the node process otherwise it remains in pending till the end.

So, how can i manually end the connection after i set ctx.status and ctx.body?

bogomips avatar Jan 06 '17 16:01 bogomips

ctx.res.end()

i5ting avatar Jan 10 '17 09:01 i5ting

Thanks, It was one of my first attempt. I have something like this

ctx.status=413; ctx.body=jcompose(ctx.status); ctx.res.end();

Unfortunately, the upload keeps going till the end, even if morgan prints the log out.

bogomips avatar Jan 10 '17 14:01 bogomips

I even tried to throw an error: ctx.throw ('payload too big',413)

The upload keeps going till the end.

bogomips avatar Jan 24 '17 19:01 bogomips

This seems to be app specific problem, or perhaps (what looks like) koa-router. I can't reproduce this at all. Throw works as intended (and I'm pretty sure there's tests coverage for this as well).

'use strict'
const Koa = require('koa')
const app = new Koa()

app.use((ctx, next) => {
  ctx.state.mwCalled = []
  return next()
    .catch((err) => { ctx.body = ctx.state.mwCalled })
})

app.use((ctx, next) => {
  ctx.state.mwCalled.push('first')
  return next()
})

app.use((ctx, next) => {
  ctx.state.mwCalled.push('second')
  ctx.throw('payload to big', 413)
  return next()
})

app.use((ctx, next) => {
  ctx.state.mwCalled.push('third')
})

app.listen()

fl0w avatar Jan 25 '17 03:01 fl0w

I tried your code, I made just few changes in the middleware 2 that now looks like:

app.use((ctx, next) => {
  ctx.state.mwCalled.push('second')
  let contentLen = parseInt(ctx.request.header['content-length']);   
  if (contentLen > 10971520)  {
		console.log("Content-lenth too big, I throw an exception");
  		ctx.throw('payload to big', 413)
  }
  else
  	return next()
})

When I upload a file with size less then 10971520, I get ["first", "second", "third"] If I upload a bigger file, I see in the console "Content-lenth too big, I throw an exception" but in the browser the network status is "pending" till the end of the upload. When the whole file has been uploaded, I finally receive the output. As expected, it is just [ "first", "second"] So, it actually blocks the flow, but it does not stop the upload.

bogomips avatar Jan 25 '17 12:01 bogomips

I believe this is an issue with node itself, not with koa. I remember doing the same thing before v1, but http changed some time

jonathanong avatar Feb 18 '17 21:02 jonathanong

Can you send a response code like 204 or 500? or ctx.res.close()

ralyodio avatar Feb 25 '17 06:02 ralyodio

this will require investigating http itself. if anyone wants to make a non-koa test case, that would be great!

jonathanong avatar Mar 15 '17 13:03 jonathanong

So I'm trying my luck on this one by POSTing a large file to a native node server, e.g:

'use strict'
const http = require('http')

// edit noticed now that this was the last iteration, tried multiple "variations" :)
function handleRequest (req, res) {
  if (parseInt(req.headers['content-length']) > 1375347) {
    res.end('to large')
  }
  res.end('done in full')
}

const server = http.createServer(handleRequest)
server.listen(8000)

It seems to me that even though res.end() is called, the socket isn't actually destroyed and there still an 100 continuation handshake. res.end() just sets closed state and writes what's passed (i.e. user cannot write now).

If socket is manually res.destroy()'ed (via ctx.res.destroy() in Koa context), I think OP would get the expected result (i.e. connection dropped, don't care about the full file being sent or not - though I'm not sure about the implications of actually doing this).

I'm still new to the inner workings of http and haven't even begun debugging socket, so I might be off here.

fl0w avatar Mar 29 '17 15:03 fl0w

Hello! Any updates?

I have same problem with throwing errors when large files are uploading: the connection with the client doesn't close. I tried to use @koa/multer, koa-body and just formidable, the result is the same - the error occurs, then I can catch it in my errorHandler middleware to write ctx.status and ctx.body, but the connection would be alive until the file compleatly loaded. With @koa/multer I had one another problem: uploading stopped and never finished, so now I use formidable.

Executing this code at the end of errorHandler solves problem, but it looks like a dirty hack:

if (error instanceof SomeUploadingError()) {
	// nextTick is needed to wait for ctx.body to be written in response
	process.nextTick(() => {
		ctx.req.destroy();
	});
}

Btw, I tried to use multer with express. Same logic, same errors, but result is different - the connection ends immediately when an error occurs and the client receives it. @koa/multer are using multer, so the problem somewhere in koa logic I guess.

Ready to provide additional information if needed.

psixdev avatar Dec 12 '23 13:12 psixdev