routup 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { subtle } from 'uncrypto';
4
4
  import { compile, all } from 'proxy-addr';
5
5
  import { getType, get } from 'mime-explorer';
6
6
  import Negotiator from 'negotiator';
7
- import { Readable, Writable } from 'readable-stream';
7
+ import { Readable, PassThrough, Writable } from 'readable-stream';
8
8
  import { HTTPError } from '@ebec/http';
9
9
  import { pathToRegexp } from 'path-to-regexp';
10
10
 
@@ -32,6 +32,7 @@ var HeaderName;
32
32
  HeaderName["CONTENT_LENGTH"] = "content-length";
33
33
  HeaderName["CONTENT_RANGE"] = "content-range";
34
34
  HeaderName["CONTENT_TYPE"] = "content-type";
35
+ HeaderName["CONNECTION"] = "connection";
35
36
  HeaderName["COOKIE"] = "cookie";
36
37
  HeaderName["ETag"] = "etag";
37
38
  HeaderName["HOST"] = "host";
@@ -46,6 +47,7 @@ var HeaderName;
46
47
  HeaderName["RETRY_AFTER"] = "retry-after";
47
48
  HeaderName["SET_COOKIE"] = "set-cookie";
48
49
  HeaderName["TRANSFER_ENCODING"] = "transfer-encoding";
50
+ HeaderName["X_ACCEL_BUFFERING"] = "x-accel-buffering";
49
51
  HeaderName["X_FORWARDED_HOST"] = "x-forwarded-host";
50
52
  HeaderName["X_FORWARDED_FOR"] = "x-forwarded-for";
51
53
  HeaderName["X_FORWARDED_PROTO"] = "x-forwarded-proto";
@@ -544,6 +546,10 @@ function getRequestHostName(req, options) {
544
546
  return index !== -1 ? hostname.substring(0, index) : hostname;
545
547
  }
546
548
 
549
+ function isRequestHTTP2(req) {
550
+ return typeof getRequestHeader(req, ':path') !== 'undefined' && typeof getRequestHeader(req, ':method') !== 'undefined';
551
+ }
552
+
547
553
  function getRequestIP(req, options) {
548
554
  options = options || {};
549
555
  let trustProxy;
@@ -759,6 +765,96 @@ function isResponseGone(res) {
759
765
  }
760
766
  return false;
761
767
  }
768
+ function setResponseGone(res, value) {
769
+ res[GoneSymbol] = value;
770
+ }
771
+
772
+ function serializeEventStreamMessage(message) {
773
+ let result = '';
774
+ if (message.id) {
775
+ result += `id: ${message.id}\n`;
776
+ }
777
+ if (message.event) {
778
+ result += `event: ${message.event}\n`;
779
+ }
780
+ if (typeof message.retry === 'number' && Number.isInteger(message.retry)) {
781
+ result += `retry: ${message.retry}\n`;
782
+ }
783
+ result += `data: ${message.data}\n\n`;
784
+ return result;
785
+ }
786
+
787
+ class EventStream {
788
+ open() {
789
+ this.response.req.on('close', ()=>this.end());
790
+ this.response.req.on('error', (err)=>{
791
+ this.emit('error', err);
792
+ this.end();
793
+ });
794
+ this.passThrough.on('data', (chunk)=>this.response.write(chunk));
795
+ this.passThrough.on('error', (err)=>{
796
+ this.emit('error', err);
797
+ this.end();
798
+ });
799
+ this.passThrough.on('close', ()=>this.end());
800
+ this.response.setHeader(HeaderName.CONTENT_TYPE, 'text/event-stream');
801
+ this.response.setHeader(HeaderName.CACHE_CONTROL, 'private, no-cache, no-store, no-transform, must-revalidate, max-age=0');
802
+ this.response.setHeader(HeaderName.X_ACCEL_BUFFERING, 'no');
803
+ if (!isRequestHTTP2(this.response.req)) {
804
+ this.response.setHeader(HeaderName.CONNECTION, 'keep-alive');
805
+ }
806
+ this.response.statusCode = 200;
807
+ }
808
+ write(message) {
809
+ if (typeof message === 'string') {
810
+ this.write({
811
+ data: message
812
+ });
813
+ return;
814
+ }
815
+ if (!this.passThrough.closed && this.passThrough.writable) {
816
+ this.passThrough.write(serializeEventStreamMessage(message));
817
+ }
818
+ }
819
+ end() {
820
+ if (this.flushed) return;
821
+ this.flushed = true;
822
+ if (!this.passThrough.closed) {
823
+ this.passThrough.end();
824
+ }
825
+ this.emit('close');
826
+ setResponseGone(this.response, true);
827
+ this.response.end();
828
+ }
829
+ on(event, listener) {
830
+ if (typeof this.eventHandlers[event] === 'undefined') {
831
+ this.eventHandlers[event] = [];
832
+ }
833
+ this.eventHandlers[event].push(listener);
834
+ }
835
+ emit(event, ...args) {
836
+ if (typeof this.eventHandlers[event] === 'undefined') {
837
+ return;
838
+ }
839
+ const listeners = this.eventHandlers[event].slice();
840
+ for(let i = 0; i < listeners.length; i++){
841
+ listeners[i].apply(this, args);
842
+ }
843
+ }
844
+ constructor(response){
845
+ this.response = response;
846
+ this.passThrough = new PassThrough({
847
+ encoding: 'utf-8'
848
+ });
849
+ this.flushed = false;
850
+ this.eventHandlers = {};
851
+ this.open();
852
+ }
853
+ }
854
+
855
+ function createEventStream(response) {
856
+ return new EventStream(response);
857
+ }
762
858
 
763
859
  function appendResponseHeader(res, name, value) {
764
860
  let header = res.getHeader(name);
@@ -842,6 +938,78 @@ function setResponseHeaderContentType(res, input, ifNotExists) {
842
938
  }
843
939
  }
844
940
 
941
+ async function sendStream(res, stream, next) {
942
+ if (isWebStream(stream)) {
943
+ return stream.pipeTo(new WritableStream({
944
+ write (chunk) {
945
+ res.write(chunk);
946
+ }
947
+ })).then(()=>{
948
+ if (next) {
949
+ return next();
950
+ }
951
+ res.end();
952
+ return Promise.resolve();
953
+ }).catch((err)=>{
954
+ if (next) {
955
+ return next(err);
956
+ }
957
+ return Promise.reject(err);
958
+ });
959
+ }
960
+ return new Promise((resolve, reject)=>{
961
+ stream.on('open', ()=>{
962
+ stream.pipe(res);
963
+ });
964
+ /* istanbul ignore next */ stream.on('error', (err)=>{
965
+ if (next) {
966
+ Promise.resolve().then(()=>next(err)).then(()=>resolve()).catch((e)=>reject(e));
967
+ return;
968
+ }
969
+ res.end();
970
+ reject(err);
971
+ });
972
+ stream.on('close', ()=>{
973
+ if (next) {
974
+ Promise.resolve().then(()=>next()).then(()=>resolve()).catch((e)=>reject(e));
975
+ return;
976
+ }
977
+ res.end();
978
+ resolve();
979
+ });
980
+ });
981
+ }
982
+
983
+ async function sendWebBlob(res, blob) {
984
+ setResponseHeaderContentType(res, blob.type);
985
+ await sendStream(res, blob.stream());
986
+ }
987
+
988
+ async function sendWebResponse(res, webResponse) {
989
+ if (webResponse.redirected) {
990
+ res.setHeader(HeaderName.LOCATION, webResponse.url);
991
+ }
992
+ if (webResponse.status) {
993
+ res.statusCode = webResponse.status;
994
+ }
995
+ if (webResponse.statusText) {
996
+ res.statusMessage = webResponse.statusText;
997
+ }
998
+ webResponse.headers.forEach((value, key)=>{
999
+ if (key === HeaderName.SET_COOKIE) {
1000
+ res.appendHeader(key, splitCookiesString(value));
1001
+ } else {
1002
+ res.setHeader(key, value);
1003
+ }
1004
+ });
1005
+ if (webResponse.body) {
1006
+ await sendStream(res, webResponse.body);
1007
+ return Promise.resolve();
1008
+ }
1009
+ res.end();
1010
+ return Promise.resolve();
1011
+ }
1012
+
845
1013
  async function send(res, chunk) {
846
1014
  switch(typeof chunk){
847
1015
  case 'string':
@@ -853,11 +1021,28 @@ async function send(res, chunk) {
853
1021
  case 'number':
854
1022
  case 'object':
855
1023
  {
856
- if (Buffer.isBuffer(chunk)) {
857
- setResponseHeaderContentType(res, 'bin', true);
858
- } else if (chunk !== null) {
859
- chunk = JSON.stringify(chunk);
860
- setResponseHeaderContentType(res, 'application/json', true);
1024
+ if (chunk !== null) {
1025
+ if (chunk instanceof Error) {
1026
+ throw chunk;
1027
+ }
1028
+ if (isStream(chunk)) {
1029
+ await sendStream(res, chunk);
1030
+ return;
1031
+ }
1032
+ if (isWebBlob(chunk)) {
1033
+ await sendWebBlob(res, chunk);
1034
+ return;
1035
+ }
1036
+ if (isWebResponse(chunk)) {
1037
+ await sendWebResponse(res, chunk);
1038
+ return;
1039
+ }
1040
+ if (Buffer.isBuffer(chunk)) {
1041
+ setResponseHeaderContentType(res, 'bin', true);
1042
+ } else {
1043
+ chunk = JSON.stringify(chunk);
1044
+ setResponseHeaderContentType(res, 'application/json', true);
1045
+ }
861
1046
  }
862
1047
  break;
863
1048
  }
@@ -940,48 +1125,6 @@ function sendCreated(res, chunk) {
940
1125
  return send(res, chunk);
941
1126
  }
942
1127
 
943
- async function sendStream(res, stream, next) {
944
- if (isWebStream(stream)) {
945
- return stream.pipeTo(new WritableStream({
946
- write (chunk) {
947
- res.write(chunk);
948
- }
949
- })).then(()=>{
950
- if (next) {
951
- return next();
952
- }
953
- res.end();
954
- return Promise.resolve();
955
- }).catch((err)=>{
956
- if (next) {
957
- return next(err);
958
- }
959
- return Promise.reject(err);
960
- });
961
- }
962
- return new Promise((resolve, reject)=>{
963
- stream.on('open', ()=>{
964
- stream.pipe(res);
965
- });
966
- /* istanbul ignore next */ stream.on('error', (err)=>{
967
- if (next) {
968
- Promise.resolve().then(()=>next(err)).then(()=>resolve()).catch((e)=>reject(e));
969
- return;
970
- }
971
- res.end();
972
- reject(err);
973
- });
974
- stream.on('close', ()=>{
975
- if (next) {
976
- Promise.resolve().then(()=>next()).then(()=>resolve()).catch((e)=>reject(e));
977
- return;
978
- }
979
- res.end();
980
- resolve();
981
- });
982
- });
983
- }
984
-
985
1128
  async function sendFile(res, options, next) {
986
1129
  let stats;
987
1130
  try {
@@ -1071,36 +1214,6 @@ function sendRedirect(res, location, statusCode = 302) {
1071
1214
  return send(res, html);
1072
1215
  }
1073
1216
 
1074
- async function sendWebResponse(res, webResponse) {
1075
- if (webResponse.redirected) {
1076
- res.setHeader(HeaderName.LOCATION, webResponse.url);
1077
- }
1078
- if (webResponse.status) {
1079
- res.statusCode = webResponse.status;
1080
- }
1081
- if (webResponse.statusText) {
1082
- res.statusMessage = webResponse.statusText;
1083
- }
1084
- webResponse.headers.forEach((value, key)=>{
1085
- if (key === HeaderName.SET_COOKIE) {
1086
- res.appendHeader(key, splitCookiesString(value));
1087
- } else {
1088
- res.setHeader(key, value);
1089
- }
1090
- });
1091
- if (webResponse.body) {
1092
- await sendStream(res, webResponse.body);
1093
- return Promise.resolve();
1094
- }
1095
- res.end();
1096
- return Promise.resolve();
1097
- }
1098
-
1099
- async function sendWebBlob(res, blob) {
1100
- setResponseHeaderContentType(res, blob.type);
1101
- await sendStream(res, blob.stream());
1102
- }
1103
-
1104
1217
  function createResponse(request) {
1105
1218
  let output;
1106
1219
  let encoding;
@@ -1246,24 +1359,6 @@ function createResponse(request) {
1246
1359
  return writable;
1247
1360
  }
1248
1361
 
1249
- async function sendData(event, chunk) {
1250
- if (chunk instanceof Error) {
1251
- return Promise.reject(createError(chunk));
1252
- }
1253
- if (isStream(chunk)) {
1254
- await sendStream(event.response, chunk);
1255
- return Promise.resolve();
1256
- }
1257
- if (isWebBlob(chunk)) {
1258
- await sendWebBlob(event.response, chunk);
1259
- return Promise.resolve();
1260
- }
1261
- if (isWebResponse(chunk)) {
1262
- await sendWebResponse(event.response, chunk);
1263
- return Promise.resolve();
1264
- }
1265
- return send(event.response, chunk);
1266
- }
1267
1362
  function dispatch(event, target) {
1268
1363
  setRequestParams(event.request, event.params);
1269
1364
  setRequestMountPath(event.request, event.mountPath);
@@ -1297,7 +1392,7 @@ function dispatch(event, target) {
1297
1392
  handled = true;
1298
1393
  unsubscribe();
1299
1394
  if (!event.dispatched) {
1300
- await sendData(event, data);
1395
+ await send(event.response, data);
1301
1396
  }
1302
1397
  return true;
1303
1398
  };
@@ -2138,5 +2233,5 @@ class Router {
2138
2233
  }
2139
2234
  }
2140
2235
 
2141
- export { DispatchErrorEvent, DispatchEvent, Handler, HandlerSymbol, HandlerType, HeaderName, MethodName, PathMatcher, Router, RoutupError, appendResponseHeader, appendResponseHeaderDirective, coreHandler, createError, createNodeDispatcher, createRawDispatcher, createRequest, createResponse, createWebDispatcher, dispatch, dispatchNodeRequest, dispatchRawRequest, dispatchWebRequest, errorHandler, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, isDispatcherErrorEvent, isError, isHandler, isHandlerConfig, isPath, isPlugin, isRequestCacheable, isResponseGone, matchRequestContentType, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, sendWebBlob, sendWebResponse, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestRouterPath, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseHeaderAttachment, setResponseHeaderContentType, transformHeaderToTuples, transformHeadersToTuples, unsetRequestEnv, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestRouterPath };
2236
+ export { DispatchErrorEvent, DispatchEvent, EventStream, Handler, HandlerSymbol, HandlerType, HeaderName, MethodName, PathMatcher, Router, RoutupError, appendResponseHeader, appendResponseHeaderDirective, coreHandler, createError, createEventStream, createNodeDispatcher, createRawDispatcher, createRequest, createResponse, createWebDispatcher, dispatch, dispatchNodeRequest, dispatchRawRequest, dispatchWebRequest, errorHandler, getRequestAcceptableCharset, getRequestAcceptableCharsets, getRequestAcceptableContentType, getRequestAcceptableContentTypes, getRequestAcceptableEncoding, getRequestAcceptableEncodings, getRequestAcceptableLanguage, getRequestAcceptableLanguages, getRequestHeader, getRequestHostName, getRequestIP, getRequestProtocol, isDispatcherErrorEvent, isError, isHandler, isHandlerConfig, isPath, isPlugin, isRequestCacheable, isRequestHTTP2, isResponseGone, matchRequestContentType, send, sendAccepted, sendCreated, sendFile, sendFormat, sendRedirect, sendStream, sendWebBlob, sendWebResponse, setRequestEnv, setRequestHeader, setRequestMountPath, setRequestParam, setRequestParams, setRequestRouterPath, setResponseCacheHeaders, setResponseContentTypeByFileName, setResponseGone, setResponseHeaderAttachment, setResponseHeaderContentType, transformHeaderToTuples, transformHeadersToTuples, unsetRequestEnv, useRequestEnv, useRequestMountPath, useRequestNegotiator, useRequestParam, useRequestParams, useRequestPath, useRequestRouterPath };
2142
2237
  //# sourceMappingURL=index.mjs.map