У меня проблемы с запросами POST через соединение HTTP / 2.
Сервер находится на узле 13.6.0 с использованием http2.createSecureServer.
У меня есть Коа. js 2.8.1, с коа-телом 4.1.1, работающим с телом на согласованных маршрутах (через тело и необработанное тело). Клиент использует выборку ES6 для POST json содержимого.
Ошибка возникает, когда raw-body вызывает readStream (). Raw-body захватывает все данные тела, но затем он никогда не получает событие 'END' из потока, поэтому он зависает на неопределенное время.
НО - это происходит только в очень специфических c условиях: a.) Это никогда не происходит при первом POST (и никогда при GET); б.) Это не происходит с дополнительными запросами POST, если они следуют друг за другом менее чем за 10 секунд - это будет работать весь день; c.) Но он потерпит неудачу на 100%, когда POST-запрос будет следовать за предыдущим POST на 10 СЕКУНД ИЛИ БОЛЬШЕ. Кажется, что где-то срабатывает таймер.
Вот и все. У меня есть обходной путь: я изменил raw-body, чтобы метод onData следил за количеством байтов - как только он получает ожидаемое количество байтов, он вызывает onEnd () и выдает ошибку. Это работает отлично, но не в долгосрочной перспективе.
Я предполагаю, что у меня где-то болтается обещание или какая-то другая ошибка, но какое-то оставшееся состояние, кажется, портит мои потоки HTTP / 2. Соединение остается открытым, а сервер обрабатывает другие потоки. Если вы добавите другой POST-запрос, зависший поток выдаст свое событие END, и raw-body продолжит работу, но затем новый будет зависать в той же точке, поэтому клиент фактически получает содержимое из ранее зависшего POST, но новый запрос остается зависшим. и т. д.
Промежуточное программное обеспечение выглядит следующим образом:
app.use( async (ctx, next) => {
try {
//const { socket: { alpnProtocol } } = ctx.req.httpVersion === '2.0' ? ctx.req.stream.session : ctx.req;
console.log('app: got request httpVersion='+ctx.req.httpVersion +' :method='+ ctx.req.headers[':method'] + ' :path='+ JSON.stringify(ctx.req.headers[':path']) ); //+' alpnProtocol='+alpnProtocol );
await next()
} catch (err) {
console.log('caught error err='+err);
ctx.body = { message: err.message }
ctx.status = err.status || 500
}
})
app.use( helmet() ); // implement security headers, here using helmet default values to trap out cross-browser, etc.
app.use( favicon(__dirname + '/public/favicon.ico')); // serve the favicon; __dirname is node.js global for dir of the currently executing script.
app.use( logger() ); // make Bunyan logger available
app.use( koa_static( path.join(__dirname, 'public')) ); // __dirname is a node.js global variable designating the working dir of the currently executing script.
app.use( methodOverride() ); // reset node's 'req.method', e.g. submit via POST, but add header "X-HTTP-Method-Override: PUT" (or DELETE);
app.use( session( { store: koa_redis({ host: redis_host }) }, app) );
app.use( passport.initialize()); // this binds the passport instance to the session introduced in the middleware stack above (initialize is in node_modules/passport/lib/middleware/initialize.js)
app.use( passport.authenticate( 'session') ); // first try to authenticate via session id; this will pass on sessions but fail on api 'bearer token' requests
app.use( forceSSL( {port: app.config_ssl.port_force_ssl } ) ); // redirect all calls on port 80 to port 443.
app.use( public_router.routes() );
/*
AFTER the public router runs, authenticate via 'bearer' in node_modules_mod;
In this modified version, node_modules_mod exits immediately if a valid session ID is found; otherwise tries authenticating a bearer token
This must be applied AFTER the public router checks for a match, to expose the home and login page without any authentication
*/
app.use( passport.authenticate('bearer', { session:false, failureRedirect:'/login/' } ) );
app.use( async ( ctx, next ) => {
if ( ctx.isAuthenticated() ){
let t1 = new Date().getTime(); // ms in epoch
console.log('\napp.js: user authenticated at '+t1+ ' '+moment().format('LLLL') );
await next()
} else {
console.log('\napp.js: router finds user not authenticated for: '+JSON.stringify(this.request));
res.statusCode = status || 302;
res.setHeader('Location', '/login/');
res.setHeader('Content-Length', '0');
res.end();
}
})
app.use( secure_router.routes() );
var ssl_options = {
key: fs.readFileSync( app.config_ssl['ssl_options_key']), // './ssl/gcrt443-key.pem'
cert: fs.readFileSync( app.config_ssl['ssl_options_cert']), // './ssl/gcrt443-cert.pem'
allowHTTP1: true
}
http.createServer( app.callback() ).listen( app.config_ssl.port );
http2.createSecureServer( ssl_options, app.callback() ).listen( app.config_ssl.port_ssl );
Клиент использует JavaScript ES6 fetch: submit_pdata: (p, data) => {
return fetch( p, {
method: 'POST', // or 'PUT'
//mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify( data ) // data can be `string` or {object}!
})
.then( res => {
return res.json()
})
.then( res => {
if( res && res.success ){
...process...
} else { console.log('Error: server reports: '+ ( res? JSON.stringify(res.errfor) : ' null response' ) ); }
})
.catch( e => console.error('Error:', e) );