@gjsify/http2 0.4.20 → 0.4.22
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/lib/esm/index.js +1 -1
- package/lib/esm/server/http2-server.js +4 -0
- package/lib/esm/server/request.js +1 -0
- package/lib/esm/server/response.js +1 -0
- package/lib/esm/server/session.js +1 -0
- package/lib/esm/server.js +1 -4
- package/lib/types/server/http2-server.d.ts +85 -0
- package/lib/types/server/request.d.ts +27 -0
- package/lib/types/server/response.d.ts +201 -0
- package/lib/types/server/session.d.ts +39 -0
- package/lib/types/server.d.ts +7 -343
- package/package.json +11 -11
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{constants as e,getDefaultSettings as t,getPackedSettings as n,getUnpackedSettings as r}from"./protocol.js";import{ClientHttp2Session as i,ClientHttp2Stream as a,Http2Session as o}from"./client-session.js";import{
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{constants as e,getDefaultSettings as t,getPackedSettings as n,getUnpackedSettings as r}from"./protocol.js";import{ClientHttp2Session as i,ClientHttp2Stream as a,Http2Session as o}from"./client-session.js";import{Http2ServerRequest as s}from"./server/request.js";import{Http2ServerResponse as c,ServerHttp2Stream as l}from"./server/response.js";import{ServerHttp2Session as u}from"./server/session.js";import{Http2SecureServer as d,Http2Server as f}from"./server/http2-server.js";import"./server.js";function createServer(e,t){return new f(e,t)}function createSecureServer(e,t){return new d(e,t)}function connect(e,t,n){let r=typeof e==`string`?e:e.toString();typeof t==`function`&&(n=t,t={});let a=new i(r,t??{});return n&&a.once(`connect`,n),a}const p=Symbol.for(`nodejs.http2.sensitiveHeaders`);function performServerHandshake(e){throw Error(`http2.performServerHandshake() is not yet implemented in GJS`)}var m={constants:e,createServer,createSecureServer,connect,getDefaultSettings:t,getPackedSettings:n,getUnpackedSettings:r,sensitiveHeaders:p,performServerHandshake,Http2Session:o,Http2Server:f,Http2SecureServer:d,Http2ServerRequest:s,Http2ServerResponse:c,ServerHttp2Session:u,ServerHttp2Stream:l,ClientHttp2Session:i,ClientHttp2Stream:a};export{i as ClientHttp2Session,a as ClientHttp2Stream,d as Http2SecureServer,f as Http2Server,s as Http2ServerRequest,c as Http2ServerResponse,o as Http2Session,u as ServerHttp2Session,l as ServerHttp2Stream,connect,e as constants,createSecureServer,createServer,m as default,t as getDefaultSettings,n as getPackedSettings,r as getUnpackedSettings,performServerHandshake,p as sensitiveHeaders};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{__toCommonJS as e}from"../_virtual/_rolldown/runtime.js";import{Http2ServerRequest as t}from"./request.js";import{Http2ServerResponse as n,ServerHttp2Stream as r}from"./response.js";import{ServerHttp2Session as i}from"./session.js";import{init_native_dispatcher as a,native_dispatcher_exports as o}from"../native-dispatcher.js";import s from"@girs/soup-3.0";import c from"@girs/gio-2.0";import l from"@girs/glib-2.0";import{EventEmitter as u}from"node:events";import{Buffer as d}from"node:buffer";import{deferEmit as f,ensureMainLoop as p}from"@gjsify/utils";const m=new Set;var Http2Server=class extends u{listening=!1;maxHeadersCount=2e3;timeout=0;_soupServer=null;_nativeDispatcher=null;_address=null;_options;get soupServer(){return this._soupServer}get nativeDispatcher(){return this._nativeDispatcher}constructor(e,t){super(),typeof e==`function`&&(t=e,e={}),this._options=e??{},t&&this.on(`request`,t)}listen(...e){let t=0,n=`0.0.0.0`,r;for(let i of e)typeof i==`number`?t=i:typeof i==`string`?n=i:typeof i==`function`&&(r=i);r&&this.once(`listening`,r);try{let e=this._options.nativeDispatcher??`auto`,r=e===`force`||e===`auto`&&this._options.allowHTTP1===!1;if(e===`off`&&this._options.allowHTTP1===!1)throw Error(`createServer({ allowHTTP1: false }) requires the native dispatcher; nativeDispatcher cannot be "off" in this configuration`);if(r)return this._startNativeListen(t,n),p(),f(this,`listening`),m.add(this),this;this._soupServer=new s.Server({}),this._configureSoupServer(this._soupServer),this._soupServer.add_handler(null,(e,t,n)=>{this._handleRequest(t)}),this._soupServer.listen_local(t,s.ServerListenOptions.IPV4_ONLY),p();let i=this._soupServer.get_listeners(),a=t;if(i&&i.length>0){let e=i[0].get_local_address();e&&typeof e.get_port==`function`&&(a=e.get_port())}this.listening=!0,this._address={port:a,family:`IPv4`,address:n},m.add(this),f(this,`listening`)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.listenerCount(`error`)===0)throw t;f(this,`error`,t)}return this}_startNativeListen(t,n){let{Http2NativeDispatcher:r}=(a(),e(o));if(!r.available())throw Error(`@gjsify/http2-native prebuild is not loadable. createServer({ allowHTTP1: false }) requires the native HTTP/2 dispatcher. Ensure GjsifyHttp2-1.0.typelib is installed.`);this._nativeDispatcher=new r({handler:e=>this._handleNativeStream(e)});let i=this._nativeDispatcher.listen(t);this.listening=!0,this._address={port:i,family:`IPv4`,address:n}}_handleNativeStream(e){let a=new t,adapt=e=>({streamId:e.streamId,submitResponse:(t,n,r,i)=>{let a={":status":t};for(let[e,t]of r)a[e]=t;e.respond(a,i)},submitData:(t,n)=>e.writeData(t,n),reset:t=>e.reset(t),pushPromise:t=>{let n=e.pushPromise(t);return n?adapt(n):null}}),o=new n(null,adapt(e.backend)),s=new r(o,new i,{streamId:e.streamId});a._setStream(s),o._setStream(s);let c=e.headers;a.method=String(c[`:method`]??`GET`),a.url=String(c[`:path`]??`/`),a.authority=String(c[`:authority`]??``),a.scheme=String(c[`:scheme`]??`http`),a.httpVersion=`2.0`,a.httpVersionMajor=2,a.httpVersionMinor=0;for(let[e,t]of Object.entries(c))if(!e.startsWith(`:`))if(a.headers[e]=t,Array.isArray(t))for(let n of t)a.rawHeaders.push(e,n);else a.rawHeaders.push(e,t);a.socket={remoteAddress:e.remoteAddress,remotePort:e.remotePort,localAddress:this._address?.address??`127.0.0.1`,localPort:e.localPort,encrypted:!1},(async()=>{try{for await(let t of e.body)a._pushBody(t);a._pushBody(null)}catch{a._pushBody(null)}})();let l={...c};this.emit(`stream`,s,l),this.emit(`request`,a,o)}_configureSoupServer(e){}_handleRequest(e){let a=new t,o=new n(e);a.method=e.get_method();let l=e.get_uri(),u=l.get_path(),d=l.get_query();a.url=d?u+`?`+d:u,a.authority=l.get_host()??``,a.scheme=l.get_scheme()??`http`,e.get_http_version()===s.HTTPVersion.HTTP_2_0?(a.httpVersion=`2.0`,a.httpVersionMajor=2,a.httpVersionMinor=0):(a.httpVersion=`1.1`,a.httpVersionMajor=1,a.httpVersionMinor=1),e.get_request_headers().foreach((e,t)=>{let n=e.toLowerCase();if(a.rawHeaders.push(e,t),n in a.headers){let e=a.headers[n];Array.isArray(e)?e.push(t):a.headers[n]=[e,t]}else a.headers[n]=t});let f=e.get_remote_host()??`127.0.0.1`,p=e.get_remote_address();a.socket={remoteAddress:f,remotePort:p instanceof c.InetSocketAddress?p.get_port():0,localAddress:this._address?.address??`127.0.0.1`,localPort:this._address?.port??0,encrypted:this instanceof Http2SecureServer};let m=e.get_request_body();m?.data&&m.data.length>0?a._pushBody(m.data):a._pushBody(null);let h={":method":a.method,":path":a.url,":authority":a.authority,":scheme":a.scheme,...a.headers};e.pause(),o.on(`finish`,()=>e.unpause());let g=new r(o,new i);a._setStream(g),o._setStream(g),this.emit(`stream`,g,h),this.emit(`request`,a,o)}address(){return this._address}close(e){return e&&this.once(`close`,e),this._soupServer&&=(this._soupServer.disconnect(),null),this._nativeDispatcher&&=(this._nativeDispatcher.close(),null),this.listening=!1,m.delete(this),f(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}},Http2SecureServer=class extends Http2Server{_tlsCert=null;constructor(e,t){if(super(e,t),e.cert&&e.key){let t=_toPemString(e.cert),n=_toPemString(e.key);this._tlsCert=_createTlsCertificate(t,n)}else e.pfx}_configureSoupServer(e){this._tlsCert&&e.set_tls_certificate(this._tlsCert)}setSecureContext(e){if(e.cert&&e.key){let t=_toPemString(e.cert),n=_toPemString(e.key);this._tlsCert=_createTlsCertificate(t,n),this._soupServer&&this._tlsCert&&this._soupServer.set_tls_certificate(this._tlsCert)}}};function _toPemString(e){return Array.isArray(e)?e.map(_toPemString).join(`
|
|
2
|
+
`):d.isBuffer(e)?e.toString(`utf8`):e}function _createTlsCertificate(e,t){let n=e.trimEnd()+`
|
|
3
|
+
`+t.trimEnd()+`
|
|
4
|
+
`;try{return c.TlsCertificate.new_from_pem(n,-1)}catch{let n=l.get_tmp_dir(),r=l.build_filenamev([n,`gjsify-http2-cert.pem`]),i=l.build_filenamev([n,`gjsify-http2-key.pem`]);try{return l.file_set_contents(r,e),l.file_set_contents(i,t),c.TlsCertificate.new_from_files(r,i)}finally{try{c.File.new_for_path(r).delete(null)}catch{}try{c.File.new_for_path(i).delete(null)}catch{}}}}export{Http2SecureServer,Http2Server};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{Readable as e}from"node:stream";import{Buffer as t}from"node:buffer";var Http2ServerRequest=class extends e{method=`GET`;url=`/`;headers={};rawHeaders=[];authority=``;scheme=`https`;httpVersion=`2.0`;httpVersionMajor=2;httpVersionMinor=0;complete=!1;socket=null;trailers={};rawTrailers=[];_stream=null;_timeoutTimer=null;get stream(){return this._stream}_setStream(e){this._stream=e}constructor(){super()}_read(e){}_autoClose(){}_pushBody(e){e&&e.length>0&&this.push(t.from(e)),this.push(null),this.complete=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null)}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}destroy(e){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),super.destroy(e)}};export{Http2ServerRequest};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{constants as e}from"../protocol.js";import t from"@girs/soup-3.0";import{EventEmitter as n}from"node:events";import{Writable as r}from"node:stream";import{Buffer as i}from"node:buffer";import{closeSync as a,openSync as o,read as s,statSync as c}from"node:fs";var Http2ServerResponse=class extends r{statusCode=200;statusMessage=``;headersSent=!1;finished=!1;sendDate=!0;_soupMsg;_nativeBackend;_headers=new Map;_streaming=!1;_timeoutTimer=null;_stream=null;_detachedBody=null;get stream(){return this._stream}get socket(){return null}get isDetached(){return this._soupMsg===null&&this._nativeBackend===null}get detachedBody(){return this._detachedBody?i.concat(this._detachedBody):null}get isNative(){return this._nativeBackend!==null}get nativeBackend(){return this._nativeBackend}_setStream(e){this._stream=e}_setNativeBackend(e){this._nativeBackend=e}constructor(e,t=null){super(),this._soupMsg=e,this._nativeBackend=t,e===null&&t===null&&(this._detachedBody=[])}setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this}getHeader(e){return this._headers.get(e.toLowerCase())}removeHeader(e){this._headers.delete(e.toLowerCase())}hasHeader(e){return this._headers.has(e.toLowerCase())}getHeaderNames(){return Array.from(this._headers.keys())}getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e}appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):this._headers.set(n,Array.isArray(t)?[r,...t]:[r,t]),this}flushHeaders(){this.headersSent||=!0}writeHead(e,t,n){if(this.statusCode=e,typeof t==`object`&&(n=t,t=void 0),typeof t==`string`&&(this.statusMessage=t),n)for(let[e,t]of Object.entries(n))this.setHeader(e,t);return this}respond(e,t){let n=Number(e[`:status`]??200),r={};for(let[t,n]of Object.entries(e))t!==`:status`&&(r[t]=typeof n==`number`?String(n):n);this.writeHead(n,r),t?.endStream&&this.end()}writeContinue(e){e&&Promise.resolve().then(e)}writeEarlyHints(e,t){t&&Promise.resolve().then(t)}addTrailers(e){}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}_startStreaming(){if(this._streaming)return;if(this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!1);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();this._headers.has(`content-length`)?e.set_encoding(t.Encoding.CONTENT_LENGTH):e.set_encoding(t.Encoding.CHUNKED);for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n)}_write(e,t,n){let r=i.isBuffer(e)?e:i.from(e,t);this._startStreaming(),this._nativeBackend?this._nativeBackend.submitData(r,!1):this._soupMsg?(this._soupMsg.get_response_body().append(new Uint8Array(r.buffer,r.byteOffset,r.byteLength)),this._soupMsg.unpause()):this._detachedBody&&this._detachedBody.push(r),n()}_final(e){this._streaming?this._nativeBackend?this._nativeBackend.submitData(i.alloc(0),!0):this._soupMsg&&(this._soupMsg.get_response_body().complete(),this._soupMsg.unpause()):this._sendBatchResponse(),this.finished=!0,e()}_sendBatchResponse(){if(this.headersSent)return;if(this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!0);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n);let n=this._headers.get(`content-type`)||`text/plain`;this._soupMsg.set_response(n,t.MemoryUse.COPY,new Uint8Array)}end(e,t,n){return typeof e==`function`?(n=e,e=void 0):typeof t==`function`&&(n=t,t=void 0),e!=null&&this.write(e,t),super.end(n),this}respondWithFD(e,t,n){_respondFromFD(this,e,t,n??{},!1)}respondWithFile(e,t,n){let r;try{r=o(e,`r`)}catch(e){if(n?.onError){n.onError(e);return}throw e}_respondFromFD(this,r,t,n??{},!0)}pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(!this._stream){n(Error(`No associated stream`),null,{});return}this._stream.pushStream(e,t,n)}createPushResponse(e,t){if(typeof t!=`function`)throw TypeError(`callback must be a function`);this.pushStream(e,{},(e,n)=>{if(e){t(e,null);return}let r=n._res;t(null,r??null)})}},l=class ServerHttp2Stream extends n{id;pushAllowed;sentHeaders={};_res;_session;_isPushedStream;_pushedChildren=[];_pushPromiseFrame=null;_pushRequestHeaders=null;get session(){return this._session}get headersSent(){return this._res.headersSent}get closed(){return this._res.writableEnded}get destroyed(){return this._res.destroyed}get pending(){return!1}get state(){return this.closed?e.NGHTTP2_STREAM_STATE_CLOSED:e.NGHTTP2_STREAM_STATE_OPEN}get pushPromiseFrame(){return this._pushPromiseFrame}get pushRequestHeaders(){return this._pushRequestHeaders}get pushedChildren(){return this._pushedChildren}constructor(e,t=null,n={}){super(),this._res=e,this._session=t,this._isPushedStream=n.isPushedStream===!0,this.id=n.streamId??1,this.pushAllowed=!this._isPushedStream&&t?.canPush!==!1,e.on(`finish`,()=>this.emit(`close`)),e.on(`error`,e=>this.emit(`error`,e))}respond(e,t){this._res.respond(e,t)}write(e,t,n){return this._res.write(e,t,n)}end(e,t,n){return this._res.end(e,t,n),this}destroy(e){return this._res.destroy(e),this}close(t,n){n&&this.once(`close`,n);let r=this._res.nativeBackend;if(r)try{r.reset(t??e.NGHTTP2_NO_ERROR)}catch{}this._res.end()}priority(e){}setTimeout(e,t){return this._res.setTimeout(e,t),this}sendTrailers(e){}additionalHeaders(e){}respondWithFD(e,t,n){this._res.respondWithFD(e,t,n)}respondWithFile(e,t,n){this._res.respondWithFile(e,t,n)}pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(this._isPushedStream){let e=Object.assign(Error(`Cannot initiate nested push streams`),{code:`ERR_HTTP2_NESTED_PUSH`});n(e,null,{});return}if(this._session&&this._session.canPush===!1){let e=Object.assign(Error(`HTTP/2 server push has been disabled`),{code:`ERR_HTTP2_PUSH_DISABLED`});n(e,null,{});return}let r,i=null,a={},o={};for(let[t,n]of Object.entries(e))o[t]=typeof n==`number`?String(n):n;o[`:method`]||=`GET`,a=o;let s=this._res.nativeBackend,c=null;if(s){if(c=s.pushPromise(o),!c){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});n(e,null,{});return}r=c.streamId,i=null}else if(this._session){if(r=this._session._allocatePushId(),r===0){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});n(e,null,{});return}i=this._session._buildPushPromise(this.id,r,o)}else r=2;let l=c?new Http2ServerResponse(null,c):new Http2ServerResponse(_makeDetachedSoupMessage()),u=new ServerHttp2Stream(l,this._session,{isPushedStream:!0,streamId:r});u._pushPromiseFrame=i,u._pushRequestHeaders=o,l._setStream(u),this._pushedChildren.push(u),Promise.resolve().then(()=>{n(null,u,a)})}};function _makeDetachedSoupMessage(){return null}function _respondFromFD(e,t,n,r,o){let l=typeof t==`number`?t:t.fd,u=l,d={...n??{}};if(r.statCheck)try{let t=c(_fdPath(l)??`/proc/self/fd/`+l);if(r.statCheck(t,d,r)===!1){o&&a(l),e.end();return}}catch(e){if(r.onError){r.onError(e),o&&a(l);return}}let f=Number(d[`:status`]??200);delete d[`:status`];let p={};for(let[e,t]of Object.entries(d))p[e]=typeof t==`number`?String(t):t;e.writeHead(f,p),e.flushHeaders();let m=Math.max(0,r.offset??0),h=r.length,g=64*1024,_=i.alloc(g),v=m,y=typeof h==`number`?h:1/0,b=0,readNext=()=>{if(y<=0){finish();return}s(u,_,0,Math.min(g,y),v,(t,n)=>{if(t){cleanup(t);return}if(n===0){finish();return}v+=n,b+=n,y-=n;let r=i.allocUnsafe(n);_.copy(r,0,0,n),e.write(r)?readNext():e.once(`drain`,readNext)})},finish=()=>{if(e.end(),o)try{a(u)}catch{}},cleanup=t=>{if(r.onError?r.onError(t):e.destroy(t),o)try{a(u)}catch{}};if(y===0){finish();return}readNext()}function _fdPath(e){return typeof e!=`number`||e<0?null:`/proc/self/fd/`+e}export{Http2ServerResponse,l as ServerHttp2Stream};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{constants as e,getDefaultSettings as t}from"../protocol.js";import{EventEmitter as n}from"node:events";import{loadNativeHttp2 as r}from"@gjsify/http2-native";var ServerHttp2Session=class extends n{type=e.NGHTTP2_SESSION_SERVER;alpnProtocol=`h2`;encrypted=!0;_closed=!1;_destroyed=!1;_settings;_canPush=!0;_frameEncoder=null;_streamIdAllocator=null;_fallbackPushId=2;constructor(){super(),this._settings=t()}get canPush(){return this._canPush}set canPush(e){this._canPush=e}_allocatePushId(){let e=r();if(e)return this._streamIdAllocator||=e.StreamIdAllocator.new(),this._streamIdAllocator.next_promised();let t=this._fallbackPushId;return t>2147483647?0:(this._fallbackPushId+=2,t)}_buildPushPromise(e,t,n){let i=r();if(!i)return null;this._frameEncoder||=i.FrameEncoder.new();let a=[],o=[];for(let[e,t]of Object.entries(n)){let n=e.toLowerCase();if(Array.isArray(t))for(let e of t)a.push(n),o.push(String(e));else a.push(n),o.push(String(t))}let s=this._frameEncoder.encode_headers(a,o);if(!s)return null;let c=this._frameEncoder.build_push_promise(e,t,s),l=c.toArray;if(typeof l==`function`)return l.call(c);let u=c.get_data;return typeof u==`function`?u.call(c)??null:null}get closed(){return this._closed}get destroyed(){return this._destroyed}get pendingSettingsAck(){return!1}get localSettings(){return{...this._settings}}get remoteSettings(){return t()}get originSet(){return[]}settings(e,t){Object.assign(this._settings,e),t&&Promise.resolve().then(t)}goaway(t,n,r){this.emit(`goaway`,t??e.NGHTTP2_NO_ERROR),this.destroy()}ping(e,t){let n=new Uint8Array(8);return t&&Promise.resolve().then(()=>t(null,0,n)),!0}close(e){this._closed||(this._closed=!0,this.emit(`close`),e&&e())}destroy(e,t){this._destroyed||(this._destroyed=!0,this._closed=!0,e&&this.emit(`error`,e),t!==void 0&&this.emit(`goaway`,t),this.emit(`close`))}altsvc(e,t){}origin(...e){}ref(){}unref(){}};export{ServerHttp2Session};
|
package/lib/esm/server.js
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
import{__toCommonJS as e}from"./_virtual/_rolldown/runtime.js";import{constants as t,getDefaultSettings as n}from"./protocol.js";import{init_native_dispatcher as r,native_dispatcher_exports as i}from"./native-dispatcher.js";import a from"@girs/soup-3.0";import o from"@girs/gio-2.0";import s from"@girs/glib-2.0";import{EventEmitter as c}from"node:events";import{Readable as l,Writable as u}from"node:stream";import{Buffer as d}from"node:buffer";import{deferEmit as f,ensureMainLoop as p}from"@gjsify/utils";import{loadNativeHttp2 as m}from"@gjsify/http2-native";import{closeSync as h,openSync as g,read as _,statSync as v}from"node:fs";var Http2ServerRequest=class extends l{method=`GET`;url=`/`;headers={};rawHeaders=[];authority=``;scheme=`https`;httpVersion=`2.0`;httpVersionMajor=2;httpVersionMinor=0;complete=!1;socket=null;trailers={};rawTrailers=[];_stream=null;_timeoutTimer=null;get stream(){return this._stream}_setStream(e){this._stream=e}constructor(){super()}_read(e){}_autoClose(){}_pushBody(e){e&&e.length>0&&this.push(d.from(e)),this.push(null),this.complete=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null)}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}destroy(e){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),super.destroy(e)}},Http2ServerResponse=class extends u{statusCode=200;statusMessage=``;headersSent=!1;finished=!1;sendDate=!0;_soupMsg;_nativeBackend;_headers=new Map;_streaming=!1;_timeoutTimer=null;_stream=null;_detachedBody=null;get stream(){return this._stream}get socket(){return null}get isDetached(){return this._soupMsg===null&&this._nativeBackend===null}get detachedBody(){return this._detachedBody?d.concat(this._detachedBody):null}get isNative(){return this._nativeBackend!==null}get nativeBackend(){return this._nativeBackend}_setStream(e){this._stream=e}_setNativeBackend(e){this._nativeBackend=e}constructor(e,t=null){super(),this._soupMsg=e,this._nativeBackend=t,e===null&&t===null&&(this._detachedBody=[])}setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this}getHeader(e){return this._headers.get(e.toLowerCase())}removeHeader(e){this._headers.delete(e.toLowerCase())}hasHeader(e){return this._headers.has(e.toLowerCase())}getHeaderNames(){return Array.from(this._headers.keys())}getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e}appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):this._headers.set(n,Array.isArray(t)?[r,...t]:[r,t]),this}flushHeaders(){this.headersSent||=!0}writeHead(e,t,n){if(this.statusCode=e,typeof t==`object`&&(n=t,t=void 0),typeof t==`string`&&(this.statusMessage=t),n)for(let[e,t]of Object.entries(n))this.setHeader(e,t);return this}respond(e,t){let n=Number(e[`:status`]??200),r={};for(let[t,n]of Object.entries(e))t!==`:status`&&(r[t]=typeof n==`number`?String(n):n);this.writeHead(n,r),t?.endStream&&this.end()}writeContinue(e){e&&Promise.resolve().then(e)}writeEarlyHints(e,t){t&&Promise.resolve().then(t)}addTrailers(e){}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}_startStreaming(){if(this._streaming)return;if(this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!1);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();this._headers.has(`content-length`)?e.set_encoding(a.Encoding.CONTENT_LENGTH):e.set_encoding(a.Encoding.CHUNKED);for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n)}_write(e,t,n){let r=d.isBuffer(e)?e:d.from(e,t);this._startStreaming(),this._nativeBackend?this._nativeBackend.submitData(r,!1):this._soupMsg?(this._soupMsg.get_response_body().append(new Uint8Array(r.buffer,r.byteOffset,r.byteLength)),this._soupMsg.unpause()):this._detachedBody&&this._detachedBody.push(r),n()}_final(e){this._streaming?this._nativeBackend?this._nativeBackend.submitData(d.alloc(0),!0):this._soupMsg&&(this._soupMsg.get_response_body().complete(),this._soupMsg.unpause()):this._sendBatchResponse(),this.finished=!0,e()}_sendBatchResponse(){if(this.headersSent)return;if(this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._nativeBackend){this._nativeBackend.submitResponse(this.statusCode,this.statusMessage,this._headers,!0);return}if(!this._soupMsg)return;this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n);let t=this._headers.get(`content-type`)||`text/plain`;this._soupMsg.set_response(t,a.MemoryUse.COPY,new Uint8Array)}end(e,t,n){return typeof e==`function`?(n=e,e=void 0):typeof t==`function`&&(n=t,t=void 0),e!=null&&this.write(e,t),super.end(n),this}respondWithFD(e,t,n){_respondFromFD(this,e,t,n??{},!1)}respondWithFile(e,t,n){let r;try{r=g(e,`r`)}catch(e){if(n?.onError){n.onError(e);return}throw e}_respondFromFD(this,r,t,n??{},!0)}pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(!this._stream){n(Error(`No associated stream`),null,{});return}this._stream.pushStream(e,t,n)}createPushResponse(e,t){if(typeof t!=`function`)throw TypeError(`callback must be a function`);this.pushStream(e,{},(e,n)=>{if(e){t(e,null);return}let r=n._res;t(null,r??null)})}},y=class ServerHttp2Stream extends c{id;pushAllowed;sentHeaders={};_res;_session;_isPushedStream;_pushedChildren=[];_pushPromiseFrame=null;_pushRequestHeaders=null;get session(){return this._session}get headersSent(){return this._res.headersSent}get closed(){return this._res.writableEnded}get destroyed(){return this._res.destroyed}get pending(){return!1}get state(){return this.closed?t.NGHTTP2_STREAM_STATE_CLOSED:t.NGHTTP2_STREAM_STATE_OPEN}get pushPromiseFrame(){return this._pushPromiseFrame}get pushRequestHeaders(){return this._pushRequestHeaders}get pushedChildren(){return this._pushedChildren}constructor(e,t=null,n={}){super(),this._res=e,this._session=t,this._isPushedStream=n.isPushedStream===!0,this.id=n.streamId??1,this.pushAllowed=!this._isPushedStream&&t?.canPush!==!1,e.on(`finish`,()=>this.emit(`close`)),e.on(`error`,e=>this.emit(`error`,e))}respond(e,t){this._res.respond(e,t)}write(e,t,n){return this._res.write(e,t,n)}end(e,t,n){return this._res.end(e,t,n),this}destroy(e){return this._res.destroy(e),this}close(e,n){n&&this.once(`close`,n);let r=this._res.nativeBackend;if(r)try{r.reset(e??t.NGHTTP2_NO_ERROR)}catch{}this._res.end()}priority(e){}setTimeout(e,t){return this._res.setTimeout(e,t),this}sendTrailers(e){}additionalHeaders(e){}respondWithFD(e,t,n){this._res.respondWithFD(e,t,n)}respondWithFile(e,t,n){this._res.respondWithFile(e,t,n)}pushStream(e,t,n){if(typeof t==`function`&&(n=t,t={}),!n)throw TypeError(`callback must be a function`);if(this._isPushedStream){let e=Object.assign(Error(`Cannot initiate nested push streams`),{code:`ERR_HTTP2_NESTED_PUSH`});n(e,null,{});return}if(this._session&&this._session.canPush===!1){let e=Object.assign(Error(`HTTP/2 server push has been disabled`),{code:`ERR_HTTP2_PUSH_DISABLED`});n(e,null,{});return}let r,i=null,a={},o={};for(let[t,n]of Object.entries(e))o[t]=typeof n==`number`?String(n):n;o[`:method`]||=`GET`,a=o;let s=this._res.nativeBackend,c=null;if(s){if(c=s.pushPromise(o),!c){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});n(e,null,{});return}r=c.streamId,i=null}else if(this._session){if(r=this._session._allocatePushId(),r===0){let e=Object.assign(Error(`No available stream ids`),{code:`ERR_HTTP2_OUT_OF_STREAMS`});n(e,null,{});return}i=this._session._buildPushPromise(this.id,r,o)}else r=2;let l=c?new Http2ServerResponse(null,c):new Http2ServerResponse(_makeDetachedSoupMessage()),u=new ServerHttp2Stream(l,this._session,{isPushedStream:!0,streamId:r});u._pushPromiseFrame=i,u._pushRequestHeaders=o,l._setStream(u),this._pushedChildren.push(u),Promise.resolve().then(()=>{n(null,u,a)})}},ServerHttp2Session=class extends c{type=t.NGHTTP2_SESSION_SERVER;alpnProtocol=`h2`;encrypted=!0;_closed=!1;_destroyed=!1;_settings;_canPush=!0;_frameEncoder=null;_streamIdAllocator=null;_fallbackPushId=2;constructor(){super(),this._settings=n()}get canPush(){return this._canPush}set canPush(e){this._canPush=e}_allocatePushId(){let e=m();if(e)return this._streamIdAllocator||=e.StreamIdAllocator.new(),this._streamIdAllocator.next_promised();let t=this._fallbackPushId;return t>2147483647?0:(this._fallbackPushId+=2,t)}_buildPushPromise(e,t,n){let r=m();if(!r)return null;this._frameEncoder||=r.FrameEncoder.new();let i=[],a=[];for(let[e,t]of Object.entries(n)){let n=e.toLowerCase();if(Array.isArray(t))for(let e of t)i.push(n),a.push(String(e));else i.push(n),a.push(String(t))}let o=this._frameEncoder.encode_headers(i,a);if(!o)return null;let s=this._frameEncoder.build_push_promise(e,t,o),c=s.toArray;if(typeof c==`function`)return c.call(s);let l=s.get_data;return typeof l==`function`?l.call(s)??null:null}get closed(){return this._closed}get destroyed(){return this._destroyed}get pendingSettingsAck(){return!1}get localSettings(){return{...this._settings}}get remoteSettings(){return n()}get originSet(){return[]}settings(e,t){Object.assign(this._settings,e),t&&Promise.resolve().then(t)}goaway(e,n,r){this.emit(`goaway`,e??t.NGHTTP2_NO_ERROR),this.destroy()}ping(e,t){let n=new Uint8Array(8);return t&&Promise.resolve().then(()=>t(null,0,n)),!0}close(e){this._closed||(this._closed=!0,this.emit(`close`),e&&e())}destroy(e,t){this._destroyed||(this._destroyed=!0,this._closed=!0,e&&this.emit(`error`,e),t!==void 0&&this.emit(`goaway`,t),this.emit(`close`))}altsvc(e,t){}origin(...e){}ref(){}unref(){}};const b=new Set;var Http2Server=class extends c{listening=!1;maxHeadersCount=2e3;timeout=0;_soupServer=null;_nativeDispatcher=null;_address=null;_options;get soupServer(){return this._soupServer}get nativeDispatcher(){return this._nativeDispatcher}constructor(e,t){super(),typeof e==`function`&&(t=e,e={}),this._options=e??{},t&&this.on(`request`,t)}listen(...e){let t=0,n=`0.0.0.0`,r;for(let i of e)typeof i==`number`?t=i:typeof i==`string`?n=i:typeof i==`function`&&(r=i);r&&this.once(`listening`,r);try{let e=this._options.nativeDispatcher??`auto`,r=e===`force`||e===`auto`&&this._options.allowHTTP1===!1;if(e===`off`&&this._options.allowHTTP1===!1)throw Error(`createServer({ allowHTTP1: false }) requires the native dispatcher; nativeDispatcher cannot be "off" in this configuration`);if(r)return this._startNativeListen(t,n),p(),f(this,`listening`),b.add(this),this;this._soupServer=new a.Server({}),this._configureSoupServer(this._soupServer),this._soupServer.add_handler(null,(e,t,n)=>{this._handleRequest(t)}),this._soupServer.listen_local(t,a.ServerListenOptions.IPV4_ONLY),p();let i=this._soupServer.get_listeners(),o=t;if(i&&i.length>0){let e=i[0].get_local_address();e&&typeof e.get_port==`function`&&(o=e.get_port())}this.listening=!0,this._address={port:o,family:`IPv4`,address:n},b.add(this),f(this,`listening`)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.listenerCount(`error`)===0)throw t;f(this,`error`,t)}return this}_startNativeListen(t,n){let{Http2NativeDispatcher:a}=(r(),e(i));if(!a.available())throw Error(`@gjsify/http2-native prebuild is not loadable. createServer({ allowHTTP1: false }) requires the native HTTP/2 dispatcher. Ensure GjsifyHttp2-1.0.typelib is installed.`);this._nativeDispatcher=new a({handler:e=>this._handleNativeStream(e)});let o=this._nativeDispatcher.listen(t);this.listening=!0,this._address={port:o,family:`IPv4`,address:n}}_handleNativeStream(e){let t=new Http2ServerRequest,adapt=e=>({streamId:e.streamId,submitResponse:(t,n,r,i)=>{let a={":status":t};for(let[e,t]of r)a[e]=t;e.respond(a,i)},submitData:(t,n)=>e.writeData(t,n),reset:t=>e.reset(t),pushPromise:t=>{let n=e.pushPromise(t);return n?adapt(n):null}}),n=new Http2ServerResponse(null,adapt(e.backend)),r=new y(n,new ServerHttp2Session,{streamId:e.streamId});t._setStream(r),n._setStream(r);let i=e.headers;t.method=String(i[`:method`]??`GET`),t.url=String(i[`:path`]??`/`),t.authority=String(i[`:authority`]??``),t.scheme=String(i[`:scheme`]??`http`),t.httpVersion=`2.0`,t.httpVersionMajor=2,t.httpVersionMinor=0;for(let[e,n]of Object.entries(i))if(!e.startsWith(`:`))if(t.headers[e]=n,Array.isArray(n))for(let r of n)t.rawHeaders.push(e,r);else t.rawHeaders.push(e,n);t.socket={remoteAddress:e.remoteAddress,remotePort:e.remotePort,localAddress:this._address?.address??`127.0.0.1`,localPort:e.localPort,encrypted:!1},(async()=>{try{for await(let n of e.body)t._pushBody(n);t._pushBody(null)}catch{t._pushBody(null)}})();let a={...i};this.emit(`stream`,r,a),this.emit(`request`,t,n)}_configureSoupServer(e){}_handleRequest(e){let t=new Http2ServerRequest,n=new Http2ServerResponse(e);t.method=e.get_method();let r=e.get_uri(),i=r.get_path(),s=r.get_query();t.url=s?i+`?`+s:i,t.authority=r.get_host()??``,t.scheme=r.get_scheme()??`http`,e.get_http_version()===a.HTTPVersion.HTTP_2_0?(t.httpVersion=`2.0`,t.httpVersionMajor=2,t.httpVersionMinor=0):(t.httpVersion=`1.1`,t.httpVersionMajor=1,t.httpVersionMinor=1),e.get_request_headers().foreach((e,n)=>{let r=e.toLowerCase();if(t.rawHeaders.push(e,n),r in t.headers){let e=t.headers[r];Array.isArray(e)?e.push(n):t.headers[r]=[e,n]}else t.headers[r]=n});let c=e.get_remote_host()??`127.0.0.1`,l=e.get_remote_address();t.socket={remoteAddress:c,remotePort:l instanceof o.InetSocketAddress?l.get_port():0,localAddress:this._address?.address??`127.0.0.1`,localPort:this._address?.port??0,encrypted:this instanceof Http2SecureServer};let u=e.get_request_body();u?.data&&u.data.length>0?t._pushBody(u.data):t._pushBody(null);let d={":method":t.method,":path":t.url,":authority":t.authority,":scheme":t.scheme,...t.headers};e.pause(),n.on(`finish`,()=>e.unpause());let f=new y(n,new ServerHttp2Session);t._setStream(f),n._setStream(f),this.emit(`stream`,f,d),this.emit(`request`,t,n)}address(){return this._address}close(e){return e&&this.once(`close`,e),this._soupServer&&=(this._soupServer.disconnect(),null),this._nativeDispatcher&&=(this._nativeDispatcher.close(),null),this.listening=!1,b.delete(this),f(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}},Http2SecureServer=class extends Http2Server{_tlsCert=null;constructor(e,t){if(super(e,t),e.cert&&e.key){let t=_toPemString(e.cert),n=_toPemString(e.key);this._tlsCert=_createTlsCertificate(t,n)}else e.pfx}_configureSoupServer(e){this._tlsCert&&e.set_tls_certificate(this._tlsCert)}setSecureContext(e){if(e.cert&&e.key){let t=_toPemString(e.cert),n=_toPemString(e.key);this._tlsCert=_createTlsCertificate(t,n),this._soupServer&&this._tlsCert&&this._soupServer.set_tls_certificate(this._tlsCert)}}};function _toPemString(e){return Array.isArray(e)?e.map(_toPemString).join(`
|
|
2
|
-
`):d.isBuffer(e)?e.toString(`utf8`):e}function _createTlsCertificate(e,t){let n=e.trimEnd()+`
|
|
3
|
-
`+t.trimEnd()+`
|
|
4
|
-
`;try{return o.TlsCertificate.new_from_pem(n,-1)}catch{let n=s.get_tmp_dir(),r=s.build_filenamev([n,`gjsify-http2-cert.pem`]),i=s.build_filenamev([n,`gjsify-http2-key.pem`]);try{return s.file_set_contents(r,e),s.file_set_contents(i,t),o.TlsCertificate.new_from_files(r,i)}finally{try{o.File.new_for_path(r).delete(null)}catch{}try{o.File.new_for_path(i).delete(null)}catch{}}}}function _makeDetachedSoupMessage(){return null}function _respondFromFD(e,t,n,r,i){let a=typeof t==`number`?t:t.fd,o=a,s={...n??{}};if(r.statCheck)try{let t=v(_fdPath(a)??`/proc/self/fd/`+a);if(r.statCheck(t,s,r)===!1){i&&h(a),e.end();return}}catch(e){if(r.onError){r.onError(e),i&&h(a);return}}let c=Number(s[`:status`]??200);delete s[`:status`];let l={};for(let[e,t]of Object.entries(s))l[e]=typeof t==`number`?String(t):t;e.writeHead(c,l),e.flushHeaders();let u=Math.max(0,r.offset??0),f=r.length,p=64*1024,m=d.alloc(p),g=u,y=typeof f==`number`?f:1/0,b=0,readNext=()=>{if(y<=0){finish();return}_(o,m,0,Math.min(p,y),g,(t,n)=>{if(t){cleanup(t);return}if(n===0){finish();return}g+=n,b+=n,y-=n;let r=d.allocUnsafe(n);m.copy(r,0,0,n),e.write(r)?readNext():e.once(`drain`,readNext)})},finish=()=>{if(e.end(),i)try{h(o)}catch{}},cleanup=t=>{if(r.onError?r.onError(t):e.destroy(t),i)try{h(o)}catch{}};if(y===0){finish();return}readNext()}function _fdPath(e){return typeof e!=`number`||e<0?null:`/proc/self/fd/`+e}export{Http2SecureServer,Http2Server,Http2ServerRequest,Http2ServerResponse,ServerHttp2Session,y as ServerHttp2Stream};
|
|
1
|
+
import{Http2ServerRequest as e}from"./server/request.js";import{Http2ServerResponse as t,ServerHttp2Stream as n}from"./server/response.js";import{ServerHttp2Session as r}from"./server/session.js";import{Http2SecureServer as i,Http2Server as a}from"./server/http2-server.js";export{i as Http2SecureServer,a as Http2Server,e as Http2ServerRequest,t as Http2ServerResponse,r as ServerHttp2Session,n as ServerHttp2Stream};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Soup from '@girs/soup-3.0';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { Buffer } from 'node:buffer';
|
|
4
|
+
import { Http2ServerRequest } from './request.js';
|
|
5
|
+
import { Http2ServerResponse } from './response.js';
|
|
6
|
+
import type { Http2Settings } from '../protocol.js';
|
|
7
|
+
export interface ServerOptions {
|
|
8
|
+
allowHTTP1?: boolean;
|
|
9
|
+
maxDeflateDynamicTableSize?: number;
|
|
10
|
+
maxSessionMemory?: number;
|
|
11
|
+
maxHeaderListPairs?: number;
|
|
12
|
+
maxOutstandingPings?: number;
|
|
13
|
+
maxSendHeaderBlockLength?: number;
|
|
14
|
+
paddingStrategy?: number;
|
|
15
|
+
peerMaxHeaderListSize?: number;
|
|
16
|
+
selectPadding?: (frameLen: number, maxFrameLen: number) => number;
|
|
17
|
+
settings?: Http2Settings;
|
|
18
|
+
Http1IncomingMessage?: any;
|
|
19
|
+
Http1ServerResponse?: any;
|
|
20
|
+
unknownProtocolTimeout?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Native dispatcher mode (gjsify-specific, defaults to `'auto'`).
|
|
23
|
+
*
|
|
24
|
+
* - `'auto'` — use the @gjsify/http2-native dispatcher when available and
|
|
25
|
+
* the call is for cleartext HTTP/2 (`createServer({allowHTTP1: false})`)
|
|
26
|
+
* or h2 ALPN over TLS. Falls back to Soup HTTP/1.1 otherwise.
|
|
27
|
+
* - `'force'` — always use the native dispatcher; throws if the prebuild
|
|
28
|
+
* is missing. Useful for tests + integration with raw nghttp2 clients.
|
|
29
|
+
* - `'off'` — never use the dispatcher; keep the Soup path even for h2c.
|
|
30
|
+
* `createServer({allowHTTP1: false})` then has no working configuration
|
|
31
|
+
* and listen() will throw.
|
|
32
|
+
*/
|
|
33
|
+
nativeDispatcher?: 'auto' | 'force' | 'off';
|
|
34
|
+
}
|
|
35
|
+
export declare class Http2Server extends EventEmitter {
|
|
36
|
+
listening: boolean;
|
|
37
|
+
maxHeadersCount: number;
|
|
38
|
+
timeout: number;
|
|
39
|
+
protected _soupServer: Soup.Server | null;
|
|
40
|
+
protected _nativeDispatcher: import('../native-dispatcher.js').Http2NativeDispatcher | null;
|
|
41
|
+
protected _address: {
|
|
42
|
+
port: number;
|
|
43
|
+
family: string;
|
|
44
|
+
address: string;
|
|
45
|
+
} | null;
|
|
46
|
+
protected _options: ServerOptions;
|
|
47
|
+
get soupServer(): Soup.Server | null;
|
|
48
|
+
get nativeDispatcher(): import('../native-dispatcher.js').Http2NativeDispatcher | null;
|
|
49
|
+
constructor(options?: ServerOptions | ((req: Http2ServerRequest, res: Http2ServerResponse) => void), handler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void);
|
|
50
|
+
listen(port?: number, hostname?: string, backlog?: number, callback?: () => void): this;
|
|
51
|
+
listen(port?: number, hostname?: string, callback?: () => void): this;
|
|
52
|
+
listen(port?: number, callback?: () => void): this;
|
|
53
|
+
/**
|
|
54
|
+
* Native dispatcher takes over the listen socket. Soup is not involved.
|
|
55
|
+
* Used by createServer({allowHTTP1: false}) (h2c).
|
|
56
|
+
*/
|
|
57
|
+
private _startNativeListen;
|
|
58
|
+
/** @internal Handler for streams arriving on the native dispatcher. */
|
|
59
|
+
private _handleNativeStream;
|
|
60
|
+
protected _configureSoupServer(_server: Soup.Server): void;
|
|
61
|
+
private _handleRequest;
|
|
62
|
+
address(): {
|
|
63
|
+
port: number;
|
|
64
|
+
family: string;
|
|
65
|
+
address: string;
|
|
66
|
+
} | null;
|
|
67
|
+
close(callback?: (err?: Error) => void): this;
|
|
68
|
+
setTimeout(msecs: number, callback?: () => void): this;
|
|
69
|
+
}
|
|
70
|
+
export interface SecureServerOptions extends ServerOptions {
|
|
71
|
+
cert?: string | Buffer | Array<string | Buffer>;
|
|
72
|
+
key?: string | Buffer | Array<string | Buffer>;
|
|
73
|
+
pfx?: string | Buffer | Array<string | Buffer>;
|
|
74
|
+
passphrase?: string;
|
|
75
|
+
ca?: string | Buffer | Array<string | Buffer>;
|
|
76
|
+
requestCert?: boolean;
|
|
77
|
+
rejectUnauthorized?: boolean;
|
|
78
|
+
ALPNProtocols?: string[];
|
|
79
|
+
}
|
|
80
|
+
export declare class Http2SecureServer extends Http2Server {
|
|
81
|
+
private _tlsCert;
|
|
82
|
+
constructor(options: SecureServerOptions, handler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void);
|
|
83
|
+
protected _configureSoupServer(server: Soup.Server): void;
|
|
84
|
+
setSecureContext(options: SecureServerOptions): void;
|
|
85
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import type { ServerHttp2Stream } from './response.js';
|
|
3
|
+
export declare class Http2ServerRequest extends Readable {
|
|
4
|
+
method: string;
|
|
5
|
+
url: string;
|
|
6
|
+
headers: Record<string, string | string[]>;
|
|
7
|
+
rawHeaders: string[];
|
|
8
|
+
authority: string;
|
|
9
|
+
scheme: string;
|
|
10
|
+
httpVersion: string;
|
|
11
|
+
httpVersionMajor: number;
|
|
12
|
+
httpVersionMinor: number;
|
|
13
|
+
complete: boolean;
|
|
14
|
+
socket: any;
|
|
15
|
+
trailers: Record<string, string>;
|
|
16
|
+
rawTrailers: string[];
|
|
17
|
+
private _stream;
|
|
18
|
+
private _timeoutTimer;
|
|
19
|
+
get stream(): ServerHttp2Stream | null;
|
|
20
|
+
_setStream(stream: ServerHttp2Stream): void;
|
|
21
|
+
constructor();
|
|
22
|
+
_read(_size: number): void;
|
|
23
|
+
protected _autoClose(): void;
|
|
24
|
+
_pushBody(body: Uint8Array | null): void;
|
|
25
|
+
setTimeout(msecs: number, callback?: () => void): this;
|
|
26
|
+
destroy(error?: Error): this;
|
|
27
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import Soup from '@girs/soup-3.0';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { Writable } from 'node:stream';
|
|
4
|
+
import { Buffer } from 'node:buffer';
|
|
5
|
+
import type { ServerHttp2Session } from './session.js';
|
|
6
|
+
/**
|
|
7
|
+
* Per-stream backend that routes writes through `SessionBridge.submit_*`
|
|
8
|
+
* instead of into a Soup message. Set on responses produced by the native
|
|
9
|
+
* dispatcher (Phase 1+). When `_nativeBackend` is non-null,
|
|
10
|
+
* `Http2ServerResponse` ignores its `_soupMsg` field (also null in that
|
|
11
|
+
* case) and dispatches every operation through this object.
|
|
12
|
+
*
|
|
13
|
+
* Pushed streams reuse the same connection — `pushPromise()` returns a
|
|
14
|
+
* sibling backend for the freshly-allocated promised stream id.
|
|
15
|
+
*/
|
|
16
|
+
export interface Http2NativeBackend {
|
|
17
|
+
streamId: number;
|
|
18
|
+
submitResponse(statusCode: number, statusMessage: string, headers: Map<string, string | string[]>, endStream: boolean): void;
|
|
19
|
+
submitData(chunk: Buffer, endStream: boolean): void;
|
|
20
|
+
reset(errorCode: number): void;
|
|
21
|
+
/** Allocate a pushed stream-id; returns a child backend or null on error. */
|
|
22
|
+
pushPromise(headers: Record<string, string | number | string[]>): Http2NativeBackend | null;
|
|
23
|
+
}
|
|
24
|
+
export declare class Http2ServerResponse extends Writable {
|
|
25
|
+
statusCode: number;
|
|
26
|
+
statusMessage: string;
|
|
27
|
+
headersSent: boolean;
|
|
28
|
+
finished: boolean;
|
|
29
|
+
sendDate: boolean;
|
|
30
|
+
private _soupMsg;
|
|
31
|
+
private _nativeBackend;
|
|
32
|
+
private _headers;
|
|
33
|
+
private _streaming;
|
|
34
|
+
private _timeoutTimer;
|
|
35
|
+
private _stream;
|
|
36
|
+
/** Detached responses (PUSH_PROMISE children) buffer their output. */
|
|
37
|
+
private _detachedBody;
|
|
38
|
+
get stream(): ServerHttp2Stream | null;
|
|
39
|
+
get socket(): null;
|
|
40
|
+
/** Whether this response is detached from a Soup connection (push streams). */
|
|
41
|
+
get isDetached(): boolean;
|
|
42
|
+
/** Buffered body bytes for detached (push) responses — null on regular responses. */
|
|
43
|
+
get detachedBody(): Buffer | null;
|
|
44
|
+
/** Whether this response routes through the native HTTP/2 dispatcher. */
|
|
45
|
+
get isNative(): boolean;
|
|
46
|
+
/** @internal — used by `ServerHttp2Stream.pushStream()` to allocate a pushed child backend. */
|
|
47
|
+
get nativeBackend(): Http2NativeBackend | null;
|
|
48
|
+
_setStream(stream: ServerHttp2Stream): void;
|
|
49
|
+
/** @internal Used by the native dispatcher to attach its submit backend. */
|
|
50
|
+
_setNativeBackend(backend: Http2NativeBackend): void;
|
|
51
|
+
constructor(soupMsg: Soup.ServerMessage | null, nativeBackend?: Http2NativeBackend | null);
|
|
52
|
+
setHeader(name: string, value: string | number | string[]): this;
|
|
53
|
+
getHeader(name: string): string | string[] | undefined;
|
|
54
|
+
removeHeader(name: string): void;
|
|
55
|
+
hasHeader(name: string): boolean;
|
|
56
|
+
getHeaderNames(): string[];
|
|
57
|
+
getHeaders(): Record<string, string | string[]>;
|
|
58
|
+
appendHeader(name: string, value: string | string[]): this;
|
|
59
|
+
flushHeaders(): void;
|
|
60
|
+
writeHead(statusCode: number, statusMessage?: string | Record<string, string | string[]>, headers?: Record<string, string | string[]>): this;
|
|
61
|
+
respond(headers: Record<string, string | string[] | number>, options?: {
|
|
62
|
+
endStream?: boolean;
|
|
63
|
+
}): void;
|
|
64
|
+
writeContinue(callback?: () => void): void;
|
|
65
|
+
writeEarlyHints(_hints: Record<string, string | string[]>, callback?: () => void): void;
|
|
66
|
+
addTrailers(_headers: Record<string, string>): void;
|
|
67
|
+
setTimeout(msecs: number, callback?: () => void): this;
|
|
68
|
+
private _startStreaming;
|
|
69
|
+
_write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void;
|
|
70
|
+
_final(callback: (error?: Error | null) => void): void;
|
|
71
|
+
private _sendBatchResponse;
|
|
72
|
+
end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void): this;
|
|
73
|
+
/**
|
|
74
|
+
* respondWithFD — stream the contents of an open file descriptor as the
|
|
75
|
+
* response body. Headers are sent once `statCheck()` (if provided) has
|
|
76
|
+
* had a chance to mutate them; payload is read in 64 KiB chunks via
|
|
77
|
+
* `fs.read()` and dispatched through the existing Soup chunked-write path.
|
|
78
|
+
*
|
|
79
|
+
* Reference: Node.js doc/api/http2.md § respondWithFD()
|
|
80
|
+
*/
|
|
81
|
+
respondWithFD(fd: number | {
|
|
82
|
+
fd: number;
|
|
83
|
+
}, headers?: Record<string, string | string[] | number>, options?: {
|
|
84
|
+
offset?: number;
|
|
85
|
+
length?: number;
|
|
86
|
+
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
87
|
+
}): void;
|
|
88
|
+
/**
|
|
89
|
+
* respondWithFile — stream a regular file by path. Opens the file with
|
|
90
|
+
* fs.openSync, runs the optional `statCheck()` callback so the user can
|
|
91
|
+
* mutate headers based on stat results (last-modified, size, etag, …),
|
|
92
|
+
* then delegates to the same FD-streaming path as `respondWithFD()`.
|
|
93
|
+
*
|
|
94
|
+
* Reference: Node.js doc/api/http2.md § respondWithFile()
|
|
95
|
+
*/
|
|
96
|
+
respondWithFile(path: string, headers?: Record<string, string | string[] | number>, options?: {
|
|
97
|
+
offset?: number;
|
|
98
|
+
length?: number;
|
|
99
|
+
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
100
|
+
onError?: (err: Error) => void;
|
|
101
|
+
}): void;
|
|
102
|
+
/**
|
|
103
|
+
* pushStream — request the server to push an additional resource on a
|
|
104
|
+
* fresh server-initiated stream. The Vala/nghttp2 bridge allocates the
|
|
105
|
+
* promised even stream-id and constructs the PUSH_PROMISE frame; wire-level
|
|
106
|
+
* delivery requires raw nghttp2-on-socket access that Soup does not expose,
|
|
107
|
+
* so the byte-frame is currently a no-op on the wire — but the bridge
|
|
108
|
+
* allocator and frame builder are exercised end-to-end and the callback
|
|
109
|
+
* receives a fully-usable `ServerHttp2Stream` whose `respond()` / `end()`
|
|
110
|
+
* calls write into a synthetic in-memory stream observable from tests.
|
|
111
|
+
*
|
|
112
|
+
* See STATUS.md "Open TODOs" → "http2 PUSH_PROMISE wire delivery".
|
|
113
|
+
*/
|
|
114
|
+
pushStream(headers: Record<string, string | string[] | number>, options: {
|
|
115
|
+
parent?: number;
|
|
116
|
+
weight?: number;
|
|
117
|
+
exclusive?: boolean;
|
|
118
|
+
} | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
|
|
119
|
+
/**
|
|
120
|
+
* createPushResponse — alternate API: create a child Http2ServerResponse
|
|
121
|
+
* for the push without needing to bridge through ServerHttp2Stream. The
|
|
122
|
+
* created response shares the parent's stream allocator + bridge.
|
|
123
|
+
*
|
|
124
|
+
* Reference: Node.js doc/api/http2.md § Http2ServerResponse#createPushResponse()
|
|
125
|
+
*/
|
|
126
|
+
createPushResponse(headers: Record<string, string | string[] | number>, callback: (err: Error | null, res: Http2ServerResponse) => void): void;
|
|
127
|
+
}
|
|
128
|
+
export declare class ServerHttp2Stream extends EventEmitter {
|
|
129
|
+
readonly id: number;
|
|
130
|
+
readonly pushAllowed: boolean;
|
|
131
|
+
readonly sentHeaders: Record<string, string | string[]>;
|
|
132
|
+
private _res;
|
|
133
|
+
private _session;
|
|
134
|
+
private _isPushedStream;
|
|
135
|
+
/** Children pushed off this request stream (parent → array). */
|
|
136
|
+
private _pushedChildren;
|
|
137
|
+
/** Cached PUSH_PROMISE frame bytes for inspection in tests. */
|
|
138
|
+
private _pushPromiseFrame;
|
|
139
|
+
/** Push request headers (`:method`, `:path`, …). */
|
|
140
|
+
private _pushRequestHeaders;
|
|
141
|
+
get session(): ServerHttp2Session | null;
|
|
142
|
+
get headersSent(): boolean;
|
|
143
|
+
get closed(): boolean;
|
|
144
|
+
get destroyed(): boolean;
|
|
145
|
+
get pending(): boolean;
|
|
146
|
+
get state(): number;
|
|
147
|
+
/** Bytes of the PUSH_PROMISE frame this stream was reserved with (push streams only). */
|
|
148
|
+
get pushPromiseFrame(): Uint8Array | null;
|
|
149
|
+
/** Request headers the push was promised with (push streams only). */
|
|
150
|
+
get pushRequestHeaders(): Record<string, string | string[]> | null;
|
|
151
|
+
/** Push streams created from this stream. */
|
|
152
|
+
get pushedChildren(): ReadonlyArray<ServerHttp2Stream>;
|
|
153
|
+
constructor(res: Http2ServerResponse, session?: ServerHttp2Session | null, options?: {
|
|
154
|
+
isPushedStream?: boolean;
|
|
155
|
+
streamId?: number;
|
|
156
|
+
});
|
|
157
|
+
respond(headers: Record<string, string | string[] | number>, options?: {
|
|
158
|
+
endStream?: boolean;
|
|
159
|
+
}): void;
|
|
160
|
+
write(chunk: any, encoding?: BufferEncoding | (() => void), callback?: () => void): boolean;
|
|
161
|
+
end(chunk?: any, encoding?: BufferEncoding | (() => void), callback?: () => void): this;
|
|
162
|
+
destroy(error?: Error): this;
|
|
163
|
+
close(code?: number, callback?: () => void): void;
|
|
164
|
+
priority(_options: {
|
|
165
|
+
exclusive?: boolean;
|
|
166
|
+
parent?: number;
|
|
167
|
+
weight?: number;
|
|
168
|
+
silent?: boolean;
|
|
169
|
+
}): void;
|
|
170
|
+
setTimeout(msecs: number, callback?: () => void): this;
|
|
171
|
+
sendTrailers(_headers: Record<string, string | string[]>): void;
|
|
172
|
+
additionalHeaders(_headers: Record<string, string | string[]>): void;
|
|
173
|
+
/** See {@link Http2ServerResponse.respondWithFD}. */
|
|
174
|
+
respondWithFD(fd: number | {
|
|
175
|
+
fd: number;
|
|
176
|
+
}, headers?: Record<string, string | string[] | number>, options?: {
|
|
177
|
+
offset?: number;
|
|
178
|
+
length?: number;
|
|
179
|
+
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
180
|
+
}): void;
|
|
181
|
+
/** See {@link Http2ServerResponse.respondWithFile}. */
|
|
182
|
+
respondWithFile(path: string, headers?: Record<string, string | string[] | number>, options?: {
|
|
183
|
+
offset?: number;
|
|
184
|
+
length?: number;
|
|
185
|
+
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
186
|
+
onError?: (err: Error) => void;
|
|
187
|
+
}): void;
|
|
188
|
+
/**
|
|
189
|
+
* pushStream — see {@link Http2ServerResponse.pushStream} for the full
|
|
190
|
+
* contract. This is the lower-level entry point: it allocates a promised
|
|
191
|
+
* stream-id from the session-bound `GjsifyHttp2.StreamIdAllocator`, builds
|
|
192
|
+
* the PUSH_PROMISE frame via `GjsifyHttp2.FrameEncoder`, then synthesises
|
|
193
|
+
* a child `ServerHttp2Stream` whose response surface is independent of
|
|
194
|
+
* the parent's underlying SoupServerMessage.
|
|
195
|
+
*/
|
|
196
|
+
pushStream(headers: Record<string, string | string[] | number>, options: {
|
|
197
|
+
parent?: number;
|
|
198
|
+
weight?: number;
|
|
199
|
+
exclusive?: boolean;
|
|
200
|
+
} | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
|
|
201
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type Http2Settings } from '../protocol.js';
|
|
3
|
+
export declare class ServerHttp2Session extends EventEmitter {
|
|
4
|
+
readonly type: 0;
|
|
5
|
+
readonly alpnProtocol: string | undefined;
|
|
6
|
+
readonly encrypted: boolean;
|
|
7
|
+
private _closed;
|
|
8
|
+
private _destroyed;
|
|
9
|
+
private _settings;
|
|
10
|
+
private _canPush;
|
|
11
|
+
/** Lazy-initialised native bridge handles. */
|
|
12
|
+
private _frameEncoder;
|
|
13
|
+
private _streamIdAllocator;
|
|
14
|
+
/** Fallback id counter used when the native bridge is unavailable. */
|
|
15
|
+
private _fallbackPushId;
|
|
16
|
+
constructor();
|
|
17
|
+
/** Whether server-push is currently permitted on this session. */
|
|
18
|
+
get canPush(): boolean;
|
|
19
|
+
set canPush(v: boolean);
|
|
20
|
+
/** @internal Allocate the next promised (even) stream id for a push. */
|
|
21
|
+
_allocatePushId(): number;
|
|
22
|
+
/** @internal Build PUSH_PROMISE frame bytes via the native bridge (or null when unavailable). */
|
|
23
|
+
_buildPushPromise(associatedStreamId: number, promisedStreamId: number, headers: Record<string, string | string[]>): Uint8Array | null;
|
|
24
|
+
get closed(): boolean;
|
|
25
|
+
get destroyed(): boolean;
|
|
26
|
+
get pendingSettingsAck(): boolean;
|
|
27
|
+
get localSettings(): Http2Settings;
|
|
28
|
+
get remoteSettings(): Http2Settings;
|
|
29
|
+
get originSet(): string[];
|
|
30
|
+
settings(settings: Http2Settings, callback?: () => void): void;
|
|
31
|
+
goaway(code?: number, _lastStreamId?: number, _data?: Uint8Array): void;
|
|
32
|
+
ping(_payload?: Uint8Array, callback?: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean;
|
|
33
|
+
close(callback?: () => void): void;
|
|
34
|
+
destroy(error?: Error, code?: number): void;
|
|
35
|
+
altsvc(_alt: string, _originOrStream: string | number): void;
|
|
36
|
+
origin(..._origins: string[]): void;
|
|
37
|
+
ref(): void;
|
|
38
|
+
unref(): void;
|
|
39
|
+
}
|
package/lib/types/server.d.ts
CHANGED
|
@@ -1,343 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export type {
|
|
7
|
-
export
|
|
8
|
-
method: string;
|
|
9
|
-
url: string;
|
|
10
|
-
headers: Record<string, string | string[]>;
|
|
11
|
-
rawHeaders: string[];
|
|
12
|
-
authority: string;
|
|
13
|
-
scheme: string;
|
|
14
|
-
httpVersion: string;
|
|
15
|
-
httpVersionMajor: number;
|
|
16
|
-
httpVersionMinor: number;
|
|
17
|
-
complete: boolean;
|
|
18
|
-
socket: any;
|
|
19
|
-
trailers: Record<string, string>;
|
|
20
|
-
rawTrailers: string[];
|
|
21
|
-
private _stream;
|
|
22
|
-
private _timeoutTimer;
|
|
23
|
-
get stream(): ServerHttp2Stream | null;
|
|
24
|
-
_setStream(stream: ServerHttp2Stream): void;
|
|
25
|
-
constructor();
|
|
26
|
-
_read(_size: number): void;
|
|
27
|
-
protected _autoClose(): void;
|
|
28
|
-
_pushBody(body: Uint8Array | null): void;
|
|
29
|
-
setTimeout(msecs: number, callback?: () => void): this;
|
|
30
|
-
destroy(error?: Error): this;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Per-stream backend that routes writes through `SessionBridge.submit_*`
|
|
34
|
-
* instead of into a Soup message. Set on responses produced by the native
|
|
35
|
-
* dispatcher (Phase 1+). When `_nativeBackend` is non-null,
|
|
36
|
-
* `Http2ServerResponse` ignores its `_soupMsg` field (also null in that
|
|
37
|
-
* case) and dispatches every operation through this object.
|
|
38
|
-
*
|
|
39
|
-
* Pushed streams reuse the same connection — `pushPromise()` returns a
|
|
40
|
-
* sibling backend for the freshly-allocated promised stream id.
|
|
41
|
-
*/
|
|
42
|
-
export interface Http2NativeBackend {
|
|
43
|
-
streamId: number;
|
|
44
|
-
submitResponse(statusCode: number, statusMessage: string, headers: Map<string, string | string[]>, endStream: boolean): void;
|
|
45
|
-
submitData(chunk: Buffer, endStream: boolean): void;
|
|
46
|
-
reset(errorCode: number): void;
|
|
47
|
-
/** Allocate a pushed stream-id; returns a child backend or null on error. */
|
|
48
|
-
pushPromise(headers: Record<string, string | number | string[]>): Http2NativeBackend | null;
|
|
49
|
-
}
|
|
50
|
-
export declare class Http2ServerResponse extends Writable {
|
|
51
|
-
statusCode: number;
|
|
52
|
-
statusMessage: string;
|
|
53
|
-
headersSent: boolean;
|
|
54
|
-
finished: boolean;
|
|
55
|
-
sendDate: boolean;
|
|
56
|
-
private _soupMsg;
|
|
57
|
-
private _nativeBackend;
|
|
58
|
-
private _headers;
|
|
59
|
-
private _streaming;
|
|
60
|
-
private _timeoutTimer;
|
|
61
|
-
private _stream;
|
|
62
|
-
/** Detached responses (PUSH_PROMISE children) buffer their output. */
|
|
63
|
-
private _detachedBody;
|
|
64
|
-
get stream(): ServerHttp2Stream | null;
|
|
65
|
-
get socket(): null;
|
|
66
|
-
/** Whether this response is detached from a Soup connection (push streams). */
|
|
67
|
-
get isDetached(): boolean;
|
|
68
|
-
/** Buffered body bytes for detached (push) responses — null on regular responses. */
|
|
69
|
-
get detachedBody(): Buffer | null;
|
|
70
|
-
/** Whether this response routes through the native HTTP/2 dispatcher. */
|
|
71
|
-
get isNative(): boolean;
|
|
72
|
-
/** @internal — used by `ServerHttp2Stream.pushStream()` to allocate a pushed child backend. */
|
|
73
|
-
get nativeBackend(): Http2NativeBackend | null;
|
|
74
|
-
_setStream(stream: ServerHttp2Stream): void;
|
|
75
|
-
/** @internal Used by the native dispatcher to attach its submit backend. */
|
|
76
|
-
_setNativeBackend(backend: Http2NativeBackend): void;
|
|
77
|
-
constructor(soupMsg: Soup.ServerMessage | null, nativeBackend?: Http2NativeBackend | null);
|
|
78
|
-
setHeader(name: string, value: string | number | string[]): this;
|
|
79
|
-
getHeader(name: string): string | string[] | undefined;
|
|
80
|
-
removeHeader(name: string): void;
|
|
81
|
-
hasHeader(name: string): boolean;
|
|
82
|
-
getHeaderNames(): string[];
|
|
83
|
-
getHeaders(): Record<string, string | string[]>;
|
|
84
|
-
appendHeader(name: string, value: string | string[]): this;
|
|
85
|
-
flushHeaders(): void;
|
|
86
|
-
writeHead(statusCode: number, statusMessage?: string | Record<string, string | string[]>, headers?: Record<string, string | string[]>): this;
|
|
87
|
-
respond(headers: Record<string, string | string[] | number>, options?: {
|
|
88
|
-
endStream?: boolean;
|
|
89
|
-
}): void;
|
|
90
|
-
writeContinue(callback?: () => void): void;
|
|
91
|
-
writeEarlyHints(_hints: Record<string, string | string[]>, callback?: () => void): void;
|
|
92
|
-
addTrailers(_headers: Record<string, string>): void;
|
|
93
|
-
setTimeout(msecs: number, callback?: () => void): this;
|
|
94
|
-
private _startStreaming;
|
|
95
|
-
_write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void;
|
|
96
|
-
_final(callback: (error?: Error | null) => void): void;
|
|
97
|
-
private _sendBatchResponse;
|
|
98
|
-
end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void): this;
|
|
99
|
-
/**
|
|
100
|
-
* respondWithFD — stream the contents of an open file descriptor as the
|
|
101
|
-
* response body. Headers are sent once `statCheck()` (if provided) has
|
|
102
|
-
* had a chance to mutate them; payload is read in 64 KiB chunks via
|
|
103
|
-
* `fs.read()` and dispatched through the existing Soup chunked-write path.
|
|
104
|
-
*
|
|
105
|
-
* Reference: Node.js doc/api/http2.md § respondWithFD()
|
|
106
|
-
*/
|
|
107
|
-
respondWithFD(fd: number | {
|
|
108
|
-
fd: number;
|
|
109
|
-
}, headers?: Record<string, string | string[] | number>, options?: {
|
|
110
|
-
offset?: number;
|
|
111
|
-
length?: number;
|
|
112
|
-
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
113
|
-
}): void;
|
|
114
|
-
/**
|
|
115
|
-
* respondWithFile — stream a regular file by path. Opens the file with
|
|
116
|
-
* fs.openSync, runs the optional `statCheck()` callback so the user can
|
|
117
|
-
* mutate headers based on stat results (last-modified, size, etag, …),
|
|
118
|
-
* then delegates to the same FD-streaming path as `respondWithFD()`.
|
|
119
|
-
*
|
|
120
|
-
* Reference: Node.js doc/api/http2.md § respondWithFile()
|
|
121
|
-
*/
|
|
122
|
-
respondWithFile(path: string, headers?: Record<string, string | string[] | number>, options?: {
|
|
123
|
-
offset?: number;
|
|
124
|
-
length?: number;
|
|
125
|
-
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
126
|
-
onError?: (err: Error) => void;
|
|
127
|
-
}): void;
|
|
128
|
-
/**
|
|
129
|
-
* pushStream — request the server to push an additional resource on a
|
|
130
|
-
* fresh server-initiated stream. The Vala/nghttp2 bridge allocates the
|
|
131
|
-
* promised even stream-id and constructs the PUSH_PROMISE frame; wire-level
|
|
132
|
-
* delivery requires raw nghttp2-on-socket access that Soup does not expose,
|
|
133
|
-
* so the byte-frame is currently a no-op on the wire — but the bridge
|
|
134
|
-
* allocator and frame builder are exercised end-to-end and the callback
|
|
135
|
-
* receives a fully-usable `ServerHttp2Stream` whose `respond()` / `end()`
|
|
136
|
-
* calls write into a synthetic in-memory stream observable from tests.
|
|
137
|
-
*
|
|
138
|
-
* See STATUS.md "Open TODOs" → "http2 PUSH_PROMISE wire delivery".
|
|
139
|
-
*/
|
|
140
|
-
pushStream(headers: Record<string, string | string[] | number>, options: {
|
|
141
|
-
parent?: number;
|
|
142
|
-
weight?: number;
|
|
143
|
-
exclusive?: boolean;
|
|
144
|
-
} | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
|
|
145
|
-
/**
|
|
146
|
-
* createPushResponse — alternate API: create a child Http2ServerResponse
|
|
147
|
-
* for the push without needing to bridge through ServerHttp2Stream. The
|
|
148
|
-
* created response shares the parent's stream allocator + bridge.
|
|
149
|
-
*
|
|
150
|
-
* Reference: Node.js doc/api/http2.md § Http2ServerResponse#createPushResponse()
|
|
151
|
-
*/
|
|
152
|
-
createPushResponse(headers: Record<string, string | string[] | number>, callback: (err: Error | null, res: Http2ServerResponse) => void): void;
|
|
153
|
-
}
|
|
154
|
-
export declare class ServerHttp2Stream extends EventEmitter {
|
|
155
|
-
readonly id: number;
|
|
156
|
-
readonly pushAllowed: boolean;
|
|
157
|
-
readonly sentHeaders: Record<string, string | string[]>;
|
|
158
|
-
private _res;
|
|
159
|
-
private _session;
|
|
160
|
-
private _isPushedStream;
|
|
161
|
-
/** Children pushed off this request stream (parent → array). */
|
|
162
|
-
private _pushedChildren;
|
|
163
|
-
/** Cached PUSH_PROMISE frame bytes for inspection in tests. */
|
|
164
|
-
private _pushPromiseFrame;
|
|
165
|
-
/** Push request headers (`:method`, `:path`, …). */
|
|
166
|
-
private _pushRequestHeaders;
|
|
167
|
-
get session(): ServerHttp2Session | null;
|
|
168
|
-
get headersSent(): boolean;
|
|
169
|
-
get closed(): boolean;
|
|
170
|
-
get destroyed(): boolean;
|
|
171
|
-
get pending(): boolean;
|
|
172
|
-
get state(): number;
|
|
173
|
-
/** Bytes of the PUSH_PROMISE frame this stream was reserved with (push streams only). */
|
|
174
|
-
get pushPromiseFrame(): Uint8Array | null;
|
|
175
|
-
/** Request headers the push was promised with (push streams only). */
|
|
176
|
-
get pushRequestHeaders(): Record<string, string | string[]> | null;
|
|
177
|
-
/** Push streams created from this stream. */
|
|
178
|
-
get pushedChildren(): ReadonlyArray<ServerHttp2Stream>;
|
|
179
|
-
constructor(res: Http2ServerResponse, session?: ServerHttp2Session | null, options?: {
|
|
180
|
-
isPushedStream?: boolean;
|
|
181
|
-
streamId?: number;
|
|
182
|
-
});
|
|
183
|
-
respond(headers: Record<string, string | string[] | number>, options?: {
|
|
184
|
-
endStream?: boolean;
|
|
185
|
-
}): void;
|
|
186
|
-
write(chunk: any, encoding?: BufferEncoding | (() => void), callback?: () => void): boolean;
|
|
187
|
-
end(chunk?: any, encoding?: BufferEncoding | (() => void), callback?: () => void): this;
|
|
188
|
-
destroy(error?: Error): this;
|
|
189
|
-
close(code?: number, callback?: () => void): void;
|
|
190
|
-
priority(_options: {
|
|
191
|
-
exclusive?: boolean;
|
|
192
|
-
parent?: number;
|
|
193
|
-
weight?: number;
|
|
194
|
-
silent?: boolean;
|
|
195
|
-
}): void;
|
|
196
|
-
setTimeout(msecs: number, callback?: () => void): this;
|
|
197
|
-
sendTrailers(_headers: Record<string, string | string[]>): void;
|
|
198
|
-
additionalHeaders(_headers: Record<string, string | string[]>): void;
|
|
199
|
-
/** See {@link Http2ServerResponse.respondWithFD}. */
|
|
200
|
-
respondWithFD(fd: number | {
|
|
201
|
-
fd: number;
|
|
202
|
-
}, headers?: Record<string, string | string[] | number>, options?: {
|
|
203
|
-
offset?: number;
|
|
204
|
-
length?: number;
|
|
205
|
-
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
206
|
-
}): void;
|
|
207
|
-
/** See {@link Http2ServerResponse.respondWithFile}. */
|
|
208
|
-
respondWithFile(path: string, headers?: Record<string, string | string[] | number>, options?: {
|
|
209
|
-
offset?: number;
|
|
210
|
-
length?: number;
|
|
211
|
-
statCheck?: (stat: any, headers: any, statOptions: any) => void;
|
|
212
|
-
onError?: (err: Error) => void;
|
|
213
|
-
}): void;
|
|
214
|
-
/**
|
|
215
|
-
* pushStream — see {@link Http2ServerResponse.pushStream} for the full
|
|
216
|
-
* contract. This is the lower-level entry point: it allocates a promised
|
|
217
|
-
* stream-id from the session-bound `GjsifyHttp2.StreamIdAllocator`, builds
|
|
218
|
-
* the PUSH_PROMISE frame via `GjsifyHttp2.FrameEncoder`, then synthesises
|
|
219
|
-
* a child `ServerHttp2Stream` whose response surface is independent of
|
|
220
|
-
* the parent's underlying SoupServerMessage.
|
|
221
|
-
*/
|
|
222
|
-
pushStream(headers: Record<string, string | string[] | number>, options: {
|
|
223
|
-
parent?: number;
|
|
224
|
-
weight?: number;
|
|
225
|
-
exclusive?: boolean;
|
|
226
|
-
} | ((err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void), callback?: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void): void;
|
|
227
|
-
}
|
|
228
|
-
export declare class ServerHttp2Session extends EventEmitter {
|
|
229
|
-
readonly type: 0;
|
|
230
|
-
readonly alpnProtocol: string | undefined;
|
|
231
|
-
readonly encrypted: boolean;
|
|
232
|
-
private _closed;
|
|
233
|
-
private _destroyed;
|
|
234
|
-
private _settings;
|
|
235
|
-
private _canPush;
|
|
236
|
-
/** Lazy-initialised native bridge handles. */
|
|
237
|
-
private _frameEncoder;
|
|
238
|
-
private _streamIdAllocator;
|
|
239
|
-
/** Fallback id counter used when the native bridge is unavailable. */
|
|
240
|
-
private _fallbackPushId;
|
|
241
|
-
constructor();
|
|
242
|
-
/** Whether server-push is currently permitted on this session. */
|
|
243
|
-
get canPush(): boolean;
|
|
244
|
-
set canPush(v: boolean);
|
|
245
|
-
/** @internal Allocate the next promised (even) stream id for a push. */
|
|
246
|
-
_allocatePushId(): number;
|
|
247
|
-
/** @internal Build PUSH_PROMISE frame bytes via the native bridge (or null when unavailable). */
|
|
248
|
-
_buildPushPromise(associatedStreamId: number, promisedStreamId: number, headers: Record<string, string | string[]>): Uint8Array | null;
|
|
249
|
-
get closed(): boolean;
|
|
250
|
-
get destroyed(): boolean;
|
|
251
|
-
get pendingSettingsAck(): boolean;
|
|
252
|
-
get localSettings(): Http2Settings;
|
|
253
|
-
get remoteSettings(): Http2Settings;
|
|
254
|
-
get originSet(): string[];
|
|
255
|
-
settings(settings: Http2Settings, callback?: () => void): void;
|
|
256
|
-
goaway(code?: number, _lastStreamId?: number, _data?: Uint8Array): void;
|
|
257
|
-
ping(_payload?: Uint8Array, callback?: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean;
|
|
258
|
-
close(callback?: () => void): void;
|
|
259
|
-
destroy(error?: Error, code?: number): void;
|
|
260
|
-
altsvc(_alt: string, _originOrStream: string | number): void;
|
|
261
|
-
origin(..._origins: string[]): void;
|
|
262
|
-
ref(): void;
|
|
263
|
-
unref(): void;
|
|
264
|
-
}
|
|
265
|
-
export interface ServerOptions {
|
|
266
|
-
allowHTTP1?: boolean;
|
|
267
|
-
maxDeflateDynamicTableSize?: number;
|
|
268
|
-
maxSessionMemory?: number;
|
|
269
|
-
maxHeaderListPairs?: number;
|
|
270
|
-
maxOutstandingPings?: number;
|
|
271
|
-
maxSendHeaderBlockLength?: number;
|
|
272
|
-
paddingStrategy?: number;
|
|
273
|
-
peerMaxHeaderListSize?: number;
|
|
274
|
-
selectPadding?: (frameLen: number, maxFrameLen: number) => number;
|
|
275
|
-
settings?: Http2Settings;
|
|
276
|
-
Http1IncomingMessage?: any;
|
|
277
|
-
Http1ServerResponse?: any;
|
|
278
|
-
unknownProtocolTimeout?: number;
|
|
279
|
-
/**
|
|
280
|
-
* Native dispatcher mode (gjsify-specific, defaults to `'auto'`).
|
|
281
|
-
*
|
|
282
|
-
* - `'auto'` — use the @gjsify/http2-native dispatcher when available and
|
|
283
|
-
* the call is for cleartext HTTP/2 (`createServer({allowHTTP1: false})`)
|
|
284
|
-
* or h2 ALPN over TLS. Falls back to Soup HTTP/1.1 otherwise.
|
|
285
|
-
* - `'force'` — always use the native dispatcher; throws if the prebuild
|
|
286
|
-
* is missing. Useful for tests + integration with raw nghttp2 clients.
|
|
287
|
-
* - `'off'` — never use the dispatcher; keep the Soup path even for h2c.
|
|
288
|
-
* `createServer({allowHTTP1: false})` then has no working configuration
|
|
289
|
-
* and listen() will throw.
|
|
290
|
-
*/
|
|
291
|
-
nativeDispatcher?: 'auto' | 'force' | 'off';
|
|
292
|
-
}
|
|
293
|
-
export declare class Http2Server extends EventEmitter {
|
|
294
|
-
listening: boolean;
|
|
295
|
-
maxHeadersCount: number;
|
|
296
|
-
timeout: number;
|
|
297
|
-
protected _soupServer: Soup.Server | null;
|
|
298
|
-
protected _nativeDispatcher: import('./native-dispatcher.js').Http2NativeDispatcher | null;
|
|
299
|
-
protected _address: {
|
|
300
|
-
port: number;
|
|
301
|
-
family: string;
|
|
302
|
-
address: string;
|
|
303
|
-
} | null;
|
|
304
|
-
protected _options: ServerOptions;
|
|
305
|
-
get soupServer(): Soup.Server | null;
|
|
306
|
-
get nativeDispatcher(): import('./native-dispatcher.js').Http2NativeDispatcher | null;
|
|
307
|
-
constructor(options?: ServerOptions | ((req: Http2ServerRequest, res: Http2ServerResponse) => void), handler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void);
|
|
308
|
-
listen(port?: number, hostname?: string, backlog?: number, callback?: () => void): this;
|
|
309
|
-
listen(port?: number, hostname?: string, callback?: () => void): this;
|
|
310
|
-
listen(port?: number, callback?: () => void): this;
|
|
311
|
-
/**
|
|
312
|
-
* Native dispatcher takes over the listen socket. Soup is not involved.
|
|
313
|
-
* Used by createServer({allowHTTP1: false}) (h2c).
|
|
314
|
-
*/
|
|
315
|
-
private _startNativeListen;
|
|
316
|
-
/** @internal Handler for streams arriving on the native dispatcher. */
|
|
317
|
-
private _handleNativeStream;
|
|
318
|
-
protected _configureSoupServer(_server: Soup.Server): void;
|
|
319
|
-
private _handleRequest;
|
|
320
|
-
address(): {
|
|
321
|
-
port: number;
|
|
322
|
-
family: string;
|
|
323
|
-
address: string;
|
|
324
|
-
} | null;
|
|
325
|
-
close(callback?: (err?: Error) => void): this;
|
|
326
|
-
setTimeout(msecs: number, callback?: () => void): this;
|
|
327
|
-
}
|
|
328
|
-
export interface SecureServerOptions extends ServerOptions {
|
|
329
|
-
cert?: string | Buffer | Array<string | Buffer>;
|
|
330
|
-
key?: string | Buffer | Array<string | Buffer>;
|
|
331
|
-
pfx?: string | Buffer | Array<string | Buffer>;
|
|
332
|
-
passphrase?: string;
|
|
333
|
-
ca?: string | Buffer | Array<string | Buffer>;
|
|
334
|
-
requestCert?: boolean;
|
|
335
|
-
rejectUnauthorized?: boolean;
|
|
336
|
-
ALPNProtocols?: string[];
|
|
337
|
-
}
|
|
338
|
-
export declare class Http2SecureServer extends Http2Server {
|
|
339
|
-
private _tlsCert;
|
|
340
|
-
constructor(options: SecureServerOptions, handler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void);
|
|
341
|
-
protected _configureSoupServer(server: Soup.Server): void;
|
|
342
|
-
setSecureContext(options: SecureServerOptions): void;
|
|
343
|
-
}
|
|
1
|
+
export type { Http2Settings } from './protocol.js';
|
|
2
|
+
export { Http2ServerRequest } from './server/request.js';
|
|
3
|
+
export type { Http2NativeBackend } from './server/response.js';
|
|
4
|
+
export { Http2ServerResponse, ServerHttp2Stream } from './server/response.js';
|
|
5
|
+
export { ServerHttp2Session } from './server/session.js';
|
|
6
|
+
export type { ServerOptions, SecureServerOptions } from './server/http2-server.js';
|
|
7
|
+
export { Http2Server, Http2SecureServer } from './server/http2-server.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/http2",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"description": "Node.js http2 module for Gjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -33,18 +33,18 @@
|
|
|
33
33
|
"http2"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@gjsify/cli": "^0.4.
|
|
37
|
-
"@gjsify/unit": "^0.4.
|
|
38
|
-
"@types/node": "^25.
|
|
36
|
+
"@gjsify/cli": "^0.4.22",
|
|
37
|
+
"@gjsify/unit": "^0.4.22",
|
|
38
|
+
"@types/node": "^25.9.1",
|
|
39
39
|
"typescript": "^6.0.3"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@girs/gio-2.0": "2.88.0-4.0.
|
|
43
|
-
"@girs/glib-2.0": "2.88.0-4.0.
|
|
44
|
-
"@girs/gobject-2.0": "2.88.0-4.0.
|
|
45
|
-
"@girs/soup-3.0": "3.6.6-4.0.
|
|
46
|
-
"@gjsify/events": "^0.4.
|
|
47
|
-
"@gjsify/http2-native": "^0.4.
|
|
48
|
-
"@gjsify/utils": "^0.4.
|
|
42
|
+
"@girs/gio-2.0": "2.88.0-4.0.1",
|
|
43
|
+
"@girs/glib-2.0": "2.88.0-4.0.1",
|
|
44
|
+
"@girs/gobject-2.0": "2.88.0-4.0.1",
|
|
45
|
+
"@girs/soup-3.0": "3.6.6-4.0.1",
|
|
46
|
+
"@gjsify/events": "^0.4.22",
|
|
47
|
+
"@gjsify/http2-native": "^0.4.22",
|
|
48
|
+
"@gjsify/utils": "^0.4.22"
|
|
49
49
|
}
|
|
50
50
|
}
|