@gjsify/http2 0.4.21 → 0.4.23

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 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{Http2SecureServer as s,Http2Server as c,Http2ServerRequest as l,Http2ServerResponse as u,ServerHttp2Session as d,ServerHttp2Stream as f}from"./server.js";function createServer(e,t){return new c(e,t)}function createSecureServer(e,t){return new s(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:c,Http2SecureServer:s,Http2ServerRequest:l,Http2ServerResponse:u,ServerHttp2Session:d,ServerHttp2Stream:f,ClientHttp2Session:i,ClientHttp2Stream:a};export{i as ClientHttp2Session,a as ClientHttp2Stream,s as Http2SecureServer,c as Http2Server,l as Http2ServerRequest,u as Http2ServerResponse,o as Http2Session,d as ServerHttp2Session,f as ServerHttp2Stream,connect,e as constants,createSecureServer,createServer,m as default,t as getDefaultSettings,n as getPackedSettings,r as getUnpackedSettings,performServerHandshake,p as sensitiveHeaders};
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
+ }
@@ -1,343 +1,7 @@
1
- import Soup from '@girs/soup-3.0';
2
- import { EventEmitter } from 'node:events';
3
- import { Readable, Writable } from 'node:stream';
4
- import { Buffer } from 'node:buffer';
5
- import { type Http2Settings } from './protocol.js';
6
- export type { Http2Settings };
7
- export declare class Http2ServerRequest extends Readable {
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.21",
3
+ "version": "0.4.23",
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.21",
37
- "@gjsify/unit": "^0.4.21",
38
- "@types/node": "^25.6.2",
36
+ "@gjsify/cli": "^0.4.23",
37
+ "@gjsify/unit": "^0.4.23",
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.0-rc.15",
43
- "@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
44
- "@girs/gobject-2.0": "2.88.0-4.0.0-rc.15",
45
- "@girs/soup-3.0": "3.6.6-4.0.0-rc.15",
46
- "@gjsify/events": "^0.4.21",
47
- "@gjsify/http2-native": "^0.4.21",
48
- "@gjsify/utils": "^0.4.21"
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.23",
47
+ "@gjsify/http2-native": "^0.4.23",
48
+ "@gjsify/utils": "^0.4.23"
49
49
  }
50
50
  }