@gjsify/http 0.3.21 → 0.4.3

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.
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
@@ -1 +1 @@
1
- import{IncomingMessage as e}from"./incoming-message.js";import{OutgoingMessage as t}from"./server.js";import n from"@girs/glib-2.0";import r from"@girs/soup-3.0";import i from"@girs/gio-2.0";import{Buffer as a}from"node:buffer";import{URL as o}from"node:url";import{readBytesAsync as s}from"@gjsify/utils";var ClientRequest=class extends t{method;path;protocol;host;hostname;port;aborted=!1;reusedSocket=!1;maxHeadersCount=2e3;_chunks=[];_session;_message;_cancellable;_timeout=0;_timeoutTimer=null;_responseCallback;constructor(e,t,s){super();let c;if(typeof e==`string`||e instanceof o){let n=typeof e==`string`?new o(e):e;c={protocol:n.protocol,hostname:n.hostname,port:n.port?Number(n.port):void 0,path:n.pathname+n.search,...typeof t==`object`?t:{}},typeof t==`function`&&(s=t)}else c=e,typeof t==`function`&&(s=t);if(this.method=(c.method||`GET`).toUpperCase(),this.protocol=c.protocol||`http:`,this.hostname=c.hostname||c.host?.split(`:`)[0]||`localhost`,this.port=Number(c.port)||(this.protocol===`https:`?443:80),this.path=c.path||`/`,this.host=c.host||`${this.hostname}:${this.port}`,this._timeout=c.timeout||0,s&&(this._responseCallback=s,this.once(`response`,s)),c.headers)for(let[e,t]of Object.entries(c.headers))this.setHeader(e,t);if(c.setHost!==!1&&!this._headers.has(`host`)){let e=this.protocol===`https:`?443:80,t=this.port===e?this.hostname:`${this.hostname}:${this.port}`;this.setHeader(`Host`,t)}c.auth&&!this._headers.has(`authorization`)&&this.setHeader(`Authorization`,`Basic `+a.from(c.auth).toString(`base64`)),c.signal&&(c.signal.aborted?this.abort():c.signal.addEventListener(`abort`,()=>this.abort(),{once:!0}));let l=n.Uri.parse(this._buildUrl(),n.UriFlags.NONE);this._session=new r.Session,this._message=new r.Message({method:this.method,uri:l}),this._cancellable=new i.Cancellable,this._timeout>0&&(this._session.timeout=Math.ceil(this._timeout/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},this._timeout))}_buildUrl(){let e=this.protocol.endsWith(`:`)?this.protocol:this.protocol+`:`,t=e===`https:`?443:80,n=this.port===t?``:`:${this.port}`;return`${e}//${this.hostname}${n}${this.path}`}getRawHeaderNames(){return Array.from(this._headers.keys())}flushHeaders(){this.headersSent||this._applyHeaders()}setTimeout(e,t){return this._timeout=e,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._session.timeout=Math.ceil(e/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}abort(){this.aborted||(this.aborted=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._cancellable.cancel(),this.emit(`abort`),this.destroy())}_write(e,t,n){let r=a.isBuffer(e)?e:a.from(e,t);this._chunks.push(r),n()}_final(e){this._sendRequest().then(()=>e()).catch(t=>e(t))}_applyHeaders(){if(this.headersSent)return;this.headersSent=!0;let e=this._message.get_request_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)}async _sendRequest(){this._applyHeaders();let t=a.concat(this._chunks);if(t.length>0){let e=this._headers.get(`content-type`)||`application/octet-stream`;this._message.set_request_body_from_bytes(e,new n.Bytes(t))}try{let t=await new Promise((e,t)=>{this._session.send_async(this._message,n.PRIORITY_DEFAULT,this._cancellable,(n,r)=>{try{e(this._session.send_finish(r))}catch(e){t(e)}})}),r=[];try{let e;for(;(e=await s(t,4096,n.PRIORITY_DEFAULT,this._cancellable))!==null;)r.push(a.from(e))}catch{}let i=new e;i.statusCode=this._message.status_code,i.statusMessage=this._message.get_reason_phrase(),i.httpVersion=`1.1`,this._message.get_response_headers().foreach((e,t)=>{let n=e.toLowerCase();if(i.rawHeaders.push(e,t),n in i.headers){let e=i.headers[n];Array.isArray(e)?e.push(t):i.headers[n]=[e,t]}else i.headers[n]=t}),this.finished=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this.emit(`response`,i),setTimeout(()=>{for(let e of r)i.push(e);i.push(null),i.complete=!0},0)}catch(e){this.aborted?this.emit(`abort`):this.emit(`error`,e instanceof Error?e:Error(String(e)))}}};export{ClientRequest};
1
+ import"./_virtual/_rolldown/runtime.js";import{IncomingMessage as e}from"./incoming-message.js";import{OutgoingMessage as t}from"./server.js";import n from"@girs/glib-2.0";import r from"@girs/soup-3.0";import i from"@girs/gio-2.0";import{Buffer as a}from"node:buffer";import{URL as o}from"node:url";import{readBytesAsync as s}from"@gjsify/utils";var ClientRequest=class extends t{method;path;protocol;host;hostname;port;aborted=!1;reusedSocket=!1;maxHeadersCount=2e3;_chunks=[];_session;_message;_cancellable;_timeout=0;_timeoutTimer=null;_responseCallback;constructor(e,t,s){super();let c;if(typeof e==`string`||e instanceof o){let n=typeof e==`string`?new o(e):e;c={protocol:n.protocol,hostname:n.hostname,port:n.port?Number(n.port):void 0,path:n.pathname+n.search,...typeof t==`object`?t:{}},typeof t==`function`&&(s=t)}else c=e,typeof t==`function`&&(s=t);if(this.method=(c.method||`GET`).toUpperCase(),this.protocol=c.protocol||`http:`,this.hostname=c.hostname||c.host?.split(`:`)[0]||`localhost`,this.port=Number(c.port)||(this.protocol===`https:`?443:80),this.path=c.path||`/`,this.host=c.host||`${this.hostname}:${this.port}`,this._timeout=c.timeout||0,s&&(this._responseCallback=s,this.once(`response`,s)),c.headers)for(let[e,t]of Object.entries(c.headers))this.setHeader(e,t);if(c.setHost!==!1&&!this._headers.has(`host`)){let e=this.protocol===`https:`?443:80,t=this.port===e?this.hostname:`${this.hostname}:${this.port}`;this.setHeader(`Host`,t)}c.auth&&!this._headers.has(`authorization`)&&this.setHeader(`Authorization`,`Basic `+a.from(c.auth).toString(`base64`)),c.signal&&(c.signal.aborted?this.abort():c.signal.addEventListener(`abort`,()=>this.abort(),{once:!0}));let l=n.Uri.parse(this._buildUrl(),n.UriFlags.NONE);this._session=new r.Session,this._message=new r.Message({method:this.method,uri:l}),this._cancellable=new i.Cancellable,this._timeout>0&&(this._session.timeout=Math.ceil(this._timeout/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},this._timeout))}_buildUrl(){let e=this.protocol.endsWith(`:`)?this.protocol:this.protocol+`:`,t=e===`https:`?443:80,n=this.port===t?``:`:${this.port}`;return`${e}//${this.hostname}${n}${this.path}`}getRawHeaderNames(){return Array.from(this._headers.keys())}flushHeaders(){this.headersSent||this._applyHeaders()}setTimeout(e,t){return this._timeout=e,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._session.timeout=Math.ceil(e/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}abort(){this.aborted||(this.aborted=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._cancellable.cancel(),this.emit(`abort`),this.destroy())}_write(e,t,n){let r=a.isBuffer(e)?e:a.from(e,t);this._chunks.push(r),n()}_final(e){this._sendRequest().then(()=>e()).catch(t=>e(t))}_applyHeaders(){if(this.headersSent)return;this.headersSent=!0;let e=this._message.get_request_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)}async _sendRequest(){this._applyHeaders();let t=a.concat(this._chunks);if(t.length>0){let e=this._headers.get(`content-type`)||`application/octet-stream`;this._message.set_request_body_from_bytes(e,new n.Bytes(t))}try{let t=await new Promise((e,t)=>{this._session.send_async(this._message,n.PRIORITY_DEFAULT,this._cancellable,(n,r)=>{try{e(this._session.send_finish(r))}catch(e){t(e)}})}),r=[];try{let e;for(;(e=await s(t,4096,n.PRIORITY_DEFAULT,this._cancellable))!==null;)r.push(a.from(e))}catch{}let i=new e;i.statusCode=this._message.status_code,i.statusMessage=this._message.get_reason_phrase(),i.httpVersion=`1.1`,this._message.get_response_headers().foreach((e,t)=>{let n=e.toLowerCase();if(i.rawHeaders.push(e,t),n in i.headers){let e=i.headers[n];Array.isArray(e)?e.push(t):i.headers[n]=[e,t]}else i.headers[n]=t}),this.finished=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this.emit(`response`,i),setTimeout(()=>{for(let e of r)i.push(e);i.push(null),i.complete=!0},0)}catch(e){this.aborted?this.emit(`abort`):this.emit(`error`,e instanceof Error?e:Error(String(e)))}}};export{ClientRequest};
@@ -1 +1 @@
1
- import{Buffer as e}from"node:buffer";import{Readable as t}from"node:stream";var IncomingMessage=class extends t{httpVersion=`1.1`;httpVersionMajor=1;httpVersionMinor=1;headers={};rawHeaders=[];method;url;statusCode;statusMessage;complete=!1;socket=null;aborted=!1;get connection(){return this.socket}_timeoutTimer=null;constructor(){super()}_read(e){}_autoClose(){}_pushBody(t){t&&t.length>0&&this.push(e.from(t)),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),this.aborted=!0,super.destroy(e)}};export{IncomingMessage};
1
+ import"./_virtual/_rolldown/runtime.js";import{Buffer as e}from"node:buffer";import{Readable as t}from"node:stream";var IncomingMessage=class extends t{httpVersion=`1.1`;httpVersionMajor=1;httpVersionMinor=1;headers={};rawHeaders=[];method;url;statusCode;statusMessage;complete=!1;socket=null;aborted=!1;get connection(){return this.socket}_timeoutTimer=null;constructor(){super()}_read(e){}_autoClose(){}_pushBody(t){t&&t.length>0&&this.push(e.from(t)),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),this.aborted=!0,super.destroy(e)}};export{IncomingMessage};
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import{METHODS as e,STATUS_CODES as t}from"./constants.js";import{IncomingMessage as n}from"./incoming-message.js";import{OutgoingMessage as r,Server as i,ServerResponse as a}from"./server.js";import{ClientRequest as o}from"./client-request.js";import{validateHeaderName as s,validateHeaderValue as c}from"./validators.js";import{URL as l}from"node:url";var Agent=class{defaultPort=80;protocol=`http:`;maxSockets;maxTotalSockets;maxFreeSockets;keepAliveMsecs;keepAlive;scheduling;requests={};sockets={};freeSockets={};constructor(e){this.keepAlive=e?.keepAlive??!1,this.keepAliveMsecs=e?.keepAliveMsecs??1e3,this.maxSockets=e?.maxSockets??1/0,this.maxTotalSockets=e?.maxTotalSockets??1/0,this.maxFreeSockets=e?.maxFreeSockets??256,this.scheduling=e?.scheduling??`lifo`}destroy(){}getName(e){let t=e.host||`localhost`;return e.port&&(t+=`:`+e.port),e.localAddress&&(t+=`:`+e.localAddress),(e.family===4||e.family===6)&&(t+=`:`+e.family),t}};const u=new Agent;function createServer(e,t){return typeof e==`function`?new i(e):new i(t)}function request(e,t,n){return new o(e,t,n)}function get(e,t,n){let r,i=n;typeof e==`string`||e instanceof l?(r=typeof t==`object`?{...t,method:`GET`}:{method:`GET`},typeof t==`function`&&(i=t)):(r={...e,method:`GET`},typeof t==`function`&&(i=t),e=r);let a=typeof e==`string`||e instanceof l?new o(e,{...r,method:`GET`},i):new o({...r,method:`GET`},i);return a.end(),a}const d=16384;function setMaxIdleHTTPParsers(e){}var f={STATUS_CODES:t,METHODS:e,Server:i,IncomingMessage:n,OutgoingMessage:r,ServerResponse:a,ClientRequest:o,Agent,globalAgent:u,createServer,request,get,validateHeaderName:s,validateHeaderValue:c,maxHeaderSize:d,setMaxIdleHTTPParsers};export{Agent,o as ClientRequest,n as IncomingMessage,e as METHODS,r as OutgoingMessage,t as STATUS_CODES,i as Server,a as ServerResponse,createServer,f as default,get,u as globalAgent,d as maxHeaderSize,request,setMaxIdleHTTPParsers,s as validateHeaderName,c as validateHeaderValue};
1
+ import"./_virtual/_rolldown/runtime.js";import{METHODS as e,STATUS_CODES as t}from"./constants.js";import{IncomingMessage as n}from"./incoming-message.js";import{OutgoingMessage as r,Server as i,ServerResponse as a}from"./server.js";import{ClientRequest as o}from"./client-request.js";import{validateHeaderName as s,validateHeaderValue as c}from"./validators.js";import{URL as l}from"node:url";var Agent=class{defaultPort=80;protocol=`http:`;maxSockets;maxTotalSockets;maxFreeSockets;keepAliveMsecs;keepAlive;scheduling;requests={};sockets={};freeSockets={};constructor(e){this.keepAlive=e?.keepAlive??!1,this.keepAliveMsecs=e?.keepAliveMsecs??1e3,this.maxSockets=e?.maxSockets??1/0,this.maxTotalSockets=e?.maxTotalSockets??1/0,this.maxFreeSockets=e?.maxFreeSockets??256,this.scheduling=e?.scheduling??`lifo`}destroy(){}getName(e){let t=e.host||`localhost`;return e.port&&(t+=`:`+e.port),e.localAddress&&(t+=`:`+e.localAddress),(e.family===4||e.family===6)&&(t+=`:`+e.family),t}};const u=new Agent;function createServer(e,t){return typeof e==`function`?new i(e):new i(t)}function request(e,t,n){return new o(e,t,n)}function get(e,t,n){let r,i=n;typeof e==`string`||e instanceof l?(r=typeof t==`object`?{...t,method:`GET`}:{method:`GET`},typeof t==`function`&&(i=t)):(r={...e,method:`GET`},typeof t==`function`&&(i=t),e=r);let a=typeof e==`string`||e instanceof l?new o(e,{...r,method:`GET`},i):new o({...r,method:`GET`},i);return a.end(),a}const d=16384;function setMaxIdleHTTPParsers(e){}var f={STATUS_CODES:t,METHODS:e,Server:i,IncomingMessage:n,OutgoingMessage:r,ServerResponse:a,ClientRequest:o,Agent,globalAgent:u,createServer,request,get,validateHeaderName:s,validateHeaderValue:c,maxHeaderSize:d,setMaxIdleHTTPParsers};export{Agent,o as ClientRequest,n as IncomingMessage,e as METHODS,r as OutgoingMessage,t as STATUS_CODES,i as Server,a as ServerResponse,createServer,f as default,get,u as globalAgent,d as maxHeaderSize,request,setMaxIdleHTTPParsers,s as validateHeaderName,c as validateHeaderValue};
@@ -1 +1 @@
1
- import{Duplex as e}from"node:stream";var ServerRequestSocket=class extends e{remoteAddress;remotePort;localAddress;localPort;remoteFamily=`IPv4`;encrypted;connecting=!1;pending=!1;bytesRead=0;bytesWritten=0;_bridgeRes;_bridgePaused=!1;constructor(e,t,n,r,i,a=!1){super({allowHalfOpen:!0}),this.remoteAddress=e,this.remotePort=t,this.localAddress=n,this.localPort=r,this.encrypted=a,this._bridgeRes=i}pause(){return this._bridgePaused?this:(this._bridgePaused=!0,super.pause())}resume(){return this._bridgePaused&&=!1,super.resume()}_read(e){}_write(e,t,n){n()}destroySoon(){this.writableEnded||this.end(),this.writableFinished?this.destroy():this.once(`finish`,()=>this.destroy())}setTimeout(e,t){return t&&this.once(`timeout`,t),this}setNoDelay(e){return this}setKeepAlive(e,t){return this}ref(){return this}unref(){return this}address(){return{address:this.localAddress,family:`IPv4`,port:this.localPort}}};export{ServerRequestSocket};
1
+ import"./_virtual/_rolldown/runtime.js";import{Duplex as e}from"node:stream";var ServerRequestSocket=class extends e{remoteAddress;remotePort;localAddress;localPort;remoteFamily=`IPv4`;encrypted;connecting=!1;pending=!1;bytesRead=0;bytesWritten=0;_bridgeRes;_bridgePaused=!1;constructor(e,t,n,r,i,a=!1){super({allowHalfOpen:!0}),this.remoteAddress=e,this.remotePort=t,this.localAddress=n,this.localPort=r,this.encrypted=a,this._bridgeRes=i}pause(){return this._bridgePaused?this:(this._bridgePaused=!0,super.pause())}resume(){return this._bridgePaused&&=!1,super.resume()}_read(e){}_write(e,t,n){n()}destroySoon(){this.writableEnded||this.end(),this.writableFinished?this.destroy():this.once(`finish`,()=>this.destroy())}setTimeout(e,t){return t&&this.once(`timeout`,t),this}setNoDelay(e){return this}setKeepAlive(e,t){return this}ref(){return this}unref(){return this}address(){return{address:this.localAddress,family:`IPv4`,port:this.localPort}}};export{ServerRequestSocket};
package/lib/esm/server.js CHANGED
@@ -1 +1 @@
1
- import{ServerRequestSocket as e}from"./server-request-socket.js";import{STATUS_CODES as t}from"./constants.js";import{IncomingMessage as n}from"./incoming-message.js";import{Buffer as r}from"node:buffer";import{createNodeError as i,deferEmit as a,ensureMainLoop as o}from"@gjsify/utils";import{EventEmitter as s}from"node:events";import{Writable as c}from"node:stream";import{Socket as l}from"@gjsify/net/socket";import{Server as u}from"@gjsify/http-soup-bridge";var OutgoingMessage=class extends c{headersSent=!1;sendDate=!0;finished=!1;socket=null;_headers=new Map;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):Array.isArray(t)?this._headers.set(n,[r,...t]):this._headers.set(n,[r,t]),this}flushHeaders(){this.headersSent=!0}_write(e,t,n){n()}},ServerResponse=class extends OutgoingMessage{statusCode=200;statusMessage=``;_streaming=!1;_bridge;_timeoutTimer=null;constructor(e){super(),this._bridge=e,e.connect(`close`,()=>{this.emit(`close`)})}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}writeHead(e,n,r){if(this.statusCode=e,typeof n==`object`&&(r=n,n=void 0),this.statusMessage=n||t[e]||``,r)for(let[e,t]of Object.entries(r))this.setHeader(e,t);return this}writeContinue(e){e&&Promise.resolve().then(e)}writeProcessing(e){e&&Promise.resolve().then(e)}flushHeaders(){this.headersSent||=!0}addTrailers(e){for(let[t,n]of Object.entries(e))this._headers.set(`trailer-`+t.toLowerCase(),n)}_startStreaming(){if(!this._streaming){this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null);for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}}_write(e,t,n){let i=r.isBuffer(e)?e:r.from(e,t);this._startStreaming(),this._bridge.write_chunk(new Uint8Array(i.buffer,i.byteOffset,i.byteLength)),n()}_final(e){if(!this._streaming){for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}this._bridge.end(),this.finished=!0,e()}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}};const d=new Set;var Server=class extends s{listening=!1;maxHeadersCount=2e3;timeout=0;keepAliveTimeout=5e3;headersTimeout=6e4;requestTimeout=3e5;_bridge=null;_address=null;get soupServer(){return this._bridge?.soup_server??null}constructor(e,t){super();let n=typeof e==`function`?e:t;n&&this.on(`request`,n)}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{this._bridge=new u,this._bridge.connect(`request-received`,(e,t,n)=>{this._handleRequest(t,n)}),this._bridge.connect(`upgrade`,(e,t,n,r)=>{this._handleUpgrade(t,n)}),this._bridge.connect(`error-occurred`,(e,t)=>{this.emit(`error`,Error(t))}),this._bridge.listen(t,n),o(),this.listening=!0,this._address={port:this._bridge.port,family:`IPv4`,address:this._bridge.address||n},d.add(this),a(this,`listening`)}catch(e){let r=i(e,`listen`,{address:n,port:t});a(this,`error`,r)}return this}_handleRequest(t,r){let i=new n,a=new ServerResponse(r);i.method=t.method,i.url=t.url,i.httpVersion=`1.1`;let o=t.header_pairs??[];for(let e=0;e+1<o.length;e+=2){let t=o[e],n=o[e+1],r=t.toLowerCase();if(i.rawHeaders.push(t,n),r in i.headers){let e=i.headers[r];Array.isArray(e)?e.push(n):i.headers[r]=[e,n]}else i.headers[r]=n}i.socket=new e(t.remote_address??`127.0.0.1`,t.remote_port??0,this._address?.address??`127.0.0.1`,this._address?.port??0,r);let s=t.get_body();s.length>0?i._pushBody(s):i._pushBody(null),t.connect(`aborted_signal`,()=>{i.aborted||(i.aborted=!0,i.emit(`aborted`))}),t.connect(`close`,()=>{i.emit(`close`)});try{let e=this.emit(`request`,i,a);(e instanceof Promise||typeof e==`object`&&e&&typeof e.then==`function`)&&e.catch(e=>{if(console.error(`[HTTP] Unhandled error in async request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}})}catch(e){if(console.error(`[HTTP] Unhandled error in request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}}}_handleUpgrade(e,t){let i=new n;i.method=e.method,i.url=e.url,i.httpVersion=`1.1`;let a=e.header_pairs??[];for(let e=0;e+1<a.length;e+=2){let t=a[e],n=a[e+1],r=t.toLowerCase();i.rawHeaders.push(t,n),i.headers[r]=n}if(this.listenerCount(`upgrade`)>0){let e=new l;e._attachOutputOnly(t),this.emit(`upgrade`,i,e,r.alloc(0))}}address(){return this._address}addWebSocketHandler(e,t){if(!this._bridge)throw Error(`Server must be listening before adding WebSocket handlers. Call listen() first.`);this._bridge.soup_server.add_websocket_handler(e,null,null,(e,n,r,i)=>{t(i)})}close(e){return e&&this.once(`close`,e),this._bridge&&=(this._bridge.close(),null),this.listening=!1,d.delete(this),a(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}};export{OutgoingMessage,Server,ServerResponse};
1
+ import"./_virtual/_rolldown/runtime.js";import{ServerRequestSocket as e}from"./server-request-socket.js";import{STATUS_CODES as t}from"./constants.js";import{IncomingMessage as n}from"./incoming-message.js";import{Buffer as r}from"node:buffer";import{createNodeError as i,deferEmit as a,ensureMainLoop as o}from"@gjsify/utils";import{EventEmitter as s}from"node:events";import{Writable as c}from"node:stream";import{Socket as l}from"@gjsify/net/socket";import{Server as u}from"@gjsify/http-soup-bridge";var OutgoingMessage=class extends c{headersSent=!1;sendDate=!0;finished=!1;socket=null;_headers=new Map;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):Array.isArray(t)?this._headers.set(n,[r,...t]):this._headers.set(n,[r,t]),this}flushHeaders(){this.headersSent=!0}_write(e,t,n){n()}},ServerResponse=class extends OutgoingMessage{statusCode=200;statusMessage=``;_streaming=!1;_bridge;_timeoutTimer=null;constructor(e){super(),this._bridge=e,e.connect(`close`,()=>{this.emit(`close`)})}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}writeHead(e,n,r){if(this.statusCode=e,typeof n==`object`&&(r=n,n=void 0),this.statusMessage=n||t[e]||``,r)for(let[e,t]of Object.entries(r))this.setHeader(e,t);return this}writeContinue(e){e&&Promise.resolve().then(e)}writeProcessing(e){e&&Promise.resolve().then(e)}flushHeaders(){this.headersSent||=!0}addTrailers(e){for(let[t,n]of Object.entries(e))this._headers.set(`trailer-`+t.toLowerCase(),n)}_startStreaming(){if(!this._streaming){this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null);for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}}_write(e,t,n){let i=r.isBuffer(e)?e:r.from(e,t);this._startStreaming(),this._bridge.write_chunk(new Uint8Array(i.buffer,i.byteOffset,i.byteLength)),n()}_final(e){if(!this._streaming){for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}this._bridge.end(),this.finished=!0,e()}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}};const d=new Set;var Server=class extends s{listening=!1;maxHeadersCount=2e3;timeout=0;keepAliveTimeout=5e3;headersTimeout=6e4;requestTimeout=3e5;_bridge=null;_address=null;get soupServer(){return this._bridge?.soup_server??null}constructor(e,t){super();let n=typeof e==`function`?e:t;n&&this.on(`request`,n)}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{this._bridge=new u,this._bridge.connect(`request-received`,(e,t,n)=>{this._handleRequest(t,n)}),this._bridge.connect(`upgrade`,(e,t,n,r)=>{this._handleUpgrade(t,n)}),this._bridge.connect(`error-occurred`,(e,t)=>{this.emit(`error`,Error(t))}),this._bridge.listen(t,n),o(),this.listening=!0,this._address={port:this._bridge.port,family:`IPv4`,address:this._bridge.address||n},d.add(this),a(this,`listening`)}catch(e){let r=i(e,`listen`,{address:n,port:t});a(this,`error`,r)}return this}_handleRequest(t,r){let i=new n,a=new ServerResponse(r);i.method=t.method,i.url=t.url,i.httpVersion=`1.1`;let o=t.header_pairs??[];for(let e=0;e+1<o.length;e+=2){let t=o[e],n=o[e+1],r=t.toLowerCase();if(i.rawHeaders.push(t,n),r in i.headers){let e=i.headers[r];Array.isArray(e)?e.push(n):i.headers[r]=[e,n]}else i.headers[r]=n}i.socket=new e(t.remote_address??`127.0.0.1`,t.remote_port??0,this._address?.address??`127.0.0.1`,this._address?.port??0,r);let s=t.get_body();s.length>0?i._pushBody(s):i._pushBody(null),t.connect(`aborted_signal`,()=>{i.aborted||(i.aborted=!0,i.emit(`aborted`))}),t.connect(`close`,()=>{i.emit(`close`)});try{let e=this.emit(`request`,i,a);(e instanceof Promise||typeof e==`object`&&e&&typeof e.then==`function`)&&e.catch(e=>{if(console.error(`[HTTP] Unhandled error in async request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}})}catch(e){if(console.error(`[HTTP] Unhandled error in request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}}}_handleUpgrade(e,t){let i=new n;i.method=e.method,i.url=e.url,i.httpVersion=`1.1`;let a=e.header_pairs??[];for(let e=0;e+1<a.length;e+=2){let t=a[e],n=a[e+1],r=t.toLowerCase();i.rawHeaders.push(t,n),i.headers[r]=n}if(this.listenerCount(`upgrade`)>0){let e=new l;e._attachOutputOnly(t),this.emit(`upgrade`,i,e,r.alloc(0))}}address(){return this._address}addWebSocketHandler(e,t){if(!this._bridge)throw Error(`Server must be listening before adding WebSocket handlers. Call listen() first.`);this._bridge.soup_server.add_websocket_handler(e,null,null,(e,n,r,i)=>{t(i)})}close(e){return e&&this.once(`close`,e),this._bridge&&=(this._bridge.close(),null),this.listening=!1,d.delete(this),a(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}};export{OutgoingMessage,Server,ServerResponse};
@@ -1 +1 @@
1
- function validateHeaderName(e){if(typeof e!=`string`||!/^[\^`\-\w!#$%&'*+.|~]+$/.test(e)){let t=TypeError(`Header name must be a valid HTTP token ["${e}"]`);throw Object.defineProperty(t,`code`,{value:`ERR_INVALID_HTTP_TOKEN`}),t}}function validateHeaderValue(e,t){if(t===void 0){let t=TypeError(`Header "${e}" value must not be undefined`);throw Object.defineProperty(t,`code`,{value:`ERR_HTTP_INVALID_HEADER_VALUE`}),t}if(typeof t==`string`&&/[^\t -~€-ÿ]/.test(t)){let t=TypeError(`Invalid character in header content ["${e}"]`);throw Object.defineProperty(t,`code`,{value:`ERR_INVALID_CHAR`}),t}}export{validateHeaderName,validateHeaderValue};
1
+ import"./_virtual/_rolldown/runtime.js";function validateHeaderName(e){if(typeof e!=`string`||!/^[\^`\-\w!#$%&'*+.|~]+$/.test(e)){let t=TypeError(`Header name must be a valid HTTP token ["${e}"]`);throw Object.defineProperty(t,`code`,{value:`ERR_INVALID_HTTP_TOKEN`}),t}}function validateHeaderValue(e,t){if(t===void 0){let t=TypeError(`Header "${e}" value must not be undefined`);throw Object.defineProperty(t,`code`,{value:`ERR_HTTP_INVALID_HEADER_VALUE`}),t}if(typeof t==`string`&&/[^\t -~€-ÿ]/.test(t)){let t=TypeError(`Invalid character in header content ["${e}"]`);throw Object.defineProperty(t,`code`,{value:`ERR_INVALID_CHAR`}),t}}export{validateHeaderName,validateHeaderValue};
package/package.json CHANGED
@@ -1,54 +1,57 @@
1
1
  {
2
- "name": "@gjsify/http",
3
- "version": "0.3.21",
4
- "description": "Node.js http module for Gjs",
5
- "module": "lib/esm/index.js",
6
- "types": "lib/types/index.d.ts",
7
- "type": "module",
8
- "exports": {
9
- ".": {
10
- "types": "./lib/types/index.d.ts",
11
- "default": "./lib/esm/index.js"
2
+ "name": "@gjsify/http",
3
+ "version": "0.4.3",
4
+ "description": "Node.js http module for Gjs",
5
+ "module": "lib/esm/index.js",
6
+ "types": "lib/types/index.d.ts",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/types/index.d.ts",
14
+ "default": "./lib/esm/index.js"
15
+ },
16
+ "./validators": {
17
+ "types": "./lib/types/validators.d.ts",
18
+ "default": "./lib/esm/validators.js"
19
+ }
12
20
  },
13
- "./validators": {
14
- "types": "./lib/types/validators.d.ts",
15
- "default": "./lib/esm/validators.js"
21
+ "scripts": {
22
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
23
+ "check": "tsc --noEmit",
24
+ "build": "gjsify run build:gjsify && gjsify run build:types",
25
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
26
+ "build:types": "tsc",
27
+ "build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
28
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
29
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
30
+ "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
31
+ "test:gjs": "gjsify run test.gjs.mjs",
32
+ "test:node": "node test.node.mjs"
33
+ },
34
+ "keywords": [
35
+ "gjs",
36
+ "node",
37
+ "http"
38
+ ],
39
+ "devDependencies": {
40
+ "@gjsify/cli": "workspace:^",
41
+ "@gjsify/unit": "workspace:^",
42
+ "@types/node": "^25.6.2",
43
+ "typescript": "^6.0.3"
44
+ },
45
+ "dependencies": {
46
+ "@girs/gio-2.0": "2.88.0-4.0.0-rc.15",
47
+ "@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
48
+ "@girs/soup-3.0": "3.6.6-4.0.0-rc.15",
49
+ "@gjsify/buffer": "workspace:^",
50
+ "@gjsify/events": "workspace:^",
51
+ "@gjsify/http-soup-bridge": "workspace:^",
52
+ "@gjsify/net": "workspace:^",
53
+ "@gjsify/stream": "workspace:^",
54
+ "@gjsify/url": "workspace:^",
55
+ "@gjsify/utils": "workspace:^"
16
56
  }
17
- },
18
- "scripts": {
19
- "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
20
- "check": "tsc --noEmit",
21
- "build": "yarn build:gjsify && yarn build:types",
22
- "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
23
- "build:types": "tsc",
24
- "build:test": "yarn build:test:gjs && yarn build:test:node",
25
- "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
26
- "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
27
- "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
28
- "test:gjs": "gjsify run test.gjs.mjs",
29
- "test:node": "node test.node.mjs"
30
- },
31
- "keywords": [
32
- "gjs",
33
- "node",
34
- "http"
35
- ],
36
- "devDependencies": {
37
- "@gjsify/cli": "^0.3.21",
38
- "@gjsify/unit": "^0.3.21",
39
- "@types/node": "^25.6.2",
40
- "typescript": "^6.0.3"
41
- },
42
- "dependencies": {
43
- "@girs/gio-2.0": "2.88.0-4.0.0-rc.14",
44
- "@girs/glib-2.0": "2.88.0-4.0.0-rc.14",
45
- "@girs/soup-3.0": "3.6.6-4.0.0-rc.14",
46
- "@gjsify/buffer": "^0.3.21",
47
- "@gjsify/events": "^0.3.21",
48
- "@gjsify/http-soup-bridge": "^0.3.21",
49
- "@gjsify/net": "^0.3.21",
50
- "@gjsify/stream": "^0.3.21",
51
- "@gjsify/url": "^0.3.21",
52
- "@gjsify/utils": "^0.3.21"
53
- }
54
- }
57
+ }
@@ -1,307 +0,0 @@
1
- // ClientRequest — Writable stream for outgoing HTTP requests via Soup.Session.
2
- // Reference: Node.js lib/_http_client.js, lib/_http_outgoing.js
3
-
4
- import GLib from '@girs/glib-2.0';
5
- import Soup from '@girs/soup-3.0';
6
- import Gio from '@girs/gio-2.0';
7
- import { Buffer } from 'node:buffer';
8
- import { URL } from 'node:url';
9
- import { readBytesAsync } from '@gjsify/utils';
10
- import { OutgoingMessage } from './server.js';
11
- import { IncomingMessage } from './incoming-message.js';
12
-
13
- export interface ClientRequestOptions {
14
- protocol?: string;
15
- hostname?: string;
16
- host?: string;
17
- port?: number | string;
18
- path?: string;
19
- method?: string;
20
- headers?: Record<string, string | number | string[]>;
21
- timeout?: number;
22
- agent?: any;
23
- setHost?: boolean;
24
- /** Basic authentication string in the format 'user:password'. */
25
- auth?: string;
26
- /** Local address to bind the request from. */
27
- localAddress?: string;
28
- /** IP address family (4 or 6). */
29
- family?: 4 | 6 | 0;
30
- /** Signal to abort the request. */
31
- signal?: AbortSignal;
32
- }
33
-
34
- /**
35
- * ClientRequest — Writable stream representing an outgoing HTTP request.
36
- *
37
- * Usage:
38
- * const req = http.request(options, (res) => { ... });
39
- * req.write(body);
40
- * req.end();
41
- */
42
- export class ClientRequest extends OutgoingMessage {
43
- method: string;
44
- path: string;
45
- protocol: string;
46
- host: string;
47
- hostname: string;
48
- port: number;
49
- aborted = false;
50
- reusedSocket = false;
51
- maxHeadersCount = 2000;
52
-
53
- private _chunks: Buffer[] = [];
54
- private _session: Soup.Session;
55
- private _message: Soup.Message;
56
- private _cancellable: Gio.Cancellable;
57
- private _timeout = 0;
58
- private _timeoutTimer: ReturnType<typeof setTimeout> | null = null;
59
- private _responseCallback?: (res: IncomingMessage) => void;
60
-
61
- constructor(url: string | URL | ClientRequestOptions, options?: ClientRequestOptions | ((res: IncomingMessage) => void), callback?: (res: IncomingMessage) => void) {
62
- super();
63
-
64
- // Parse arguments: request(url, options, cb) or request(options, cb)
65
- let opts: ClientRequestOptions;
66
-
67
- if (typeof url === 'string' || url instanceof URL) {
68
- const parsed = typeof url === 'string' ? new URL(url) : url;
69
- opts = {
70
- protocol: parsed.protocol,
71
- hostname: parsed.hostname,
72
- port: parsed.port ? Number(parsed.port) : undefined,
73
- path: parsed.pathname + parsed.search,
74
- ...(typeof options === 'object' ? options : {}),
75
- };
76
- if (typeof options === 'function') {
77
- callback = options;
78
- }
79
- } else {
80
- opts = url;
81
- if (typeof options === 'function') {
82
- callback = options;
83
- }
84
- }
85
-
86
- this.method = (opts.method || 'GET').toUpperCase();
87
- this.protocol = opts.protocol || 'http:';
88
- this.hostname = opts.hostname || opts.host?.split(':')[0] || 'localhost';
89
- this.port = Number(opts.port) || (this.protocol === 'https:' ? 443 : 80);
90
- this.path = opts.path || '/';
91
- this.host = opts.host || `${this.hostname}:${this.port}`;
92
- this._timeout = opts.timeout || 0;
93
-
94
- if (callback) {
95
- this._responseCallback = callback;
96
- this.once('response', callback);
97
- }
98
-
99
- // Set default headers
100
- if (opts.headers) {
101
- for (const [key, value] of Object.entries(opts.headers)) {
102
- this.setHeader(key, value);
103
- }
104
- }
105
-
106
- if (opts.setHost !== false && !this._headers.has('host')) {
107
- const defaultPort = this.protocol === 'https:' ? 443 : 80;
108
- const hostHeader = this.port === defaultPort ? this.hostname : `${this.hostname}:${this.port}`;
109
- this.setHeader('Host', hostHeader);
110
- }
111
-
112
- // Basic authentication: encode user:password as Base64 Authorization header
113
- if (opts.auth && !this._headers.has('authorization')) {
114
- this.setHeader('Authorization', 'Basic ' + Buffer.from(opts.auth).toString('base64'));
115
- }
116
-
117
- // AbortSignal support
118
- if (opts.signal) {
119
- if (opts.signal.aborted) {
120
- this.abort();
121
- } else {
122
- opts.signal.addEventListener('abort', () => this.abort(), { once: true });
123
- }
124
- }
125
-
126
- // Create Soup objects
127
- const uri = GLib.Uri.parse(this._buildUrl(), GLib.UriFlags.NONE);
128
- this._session = new Soup.Session();
129
- this._message = new Soup.Message({ method: this.method, uri });
130
- this._cancellable = new Gio.Cancellable();
131
-
132
- if (this._timeout > 0) {
133
- this._session.timeout = Math.ceil(this._timeout / 1000);
134
- // Start timeout timer immediately for timeout option
135
- this._timeoutTimer = setTimeout(() => {
136
- this._timeoutTimer = null;
137
- this.emit('timeout');
138
- }, this._timeout);
139
- }
140
- }
141
-
142
- private _buildUrl(): string {
143
- const proto = this.protocol.endsWith(':') ? this.protocol : this.protocol + ':';
144
- const defaultPort = proto === 'https:' ? 443 : 80;
145
- const portStr = this.port === defaultPort ? '' : `:${this.port}`;
146
- return `${proto}//${this.hostname}${portStr}${this.path}`;
147
- }
148
-
149
- /** Get raw header names and values as a flat array. */
150
- getRawHeaderNames(): string[] {
151
- return Array.from(this._headers.keys());
152
- }
153
-
154
- /** Flush headers — marks headers as sent. */
155
- override flushHeaders(): void {
156
- if (!this.headersSent) {
157
- this._applyHeaders();
158
- }
159
- }
160
-
161
- /** Set timeout for the request. Emits 'timeout' if no response within msecs. */
162
- setTimeout(msecs: number, callback?: () => void): this {
163
- this._timeout = msecs;
164
- if (this._timeoutTimer) {
165
- clearTimeout(this._timeoutTimer);
166
- this._timeoutTimer = null;
167
- }
168
- if (callback) this.once('timeout', callback);
169
- if (msecs > 0) {
170
- this._session.timeout = Math.ceil(msecs / 1000);
171
- this._timeoutTimer = setTimeout(() => {
172
- this._timeoutTimer = null;
173
- this.emit('timeout');
174
- }, msecs);
175
- }
176
- return this;
177
- }
178
-
179
- /** Abort the request. */
180
- abort(): void {
181
- if (this.aborted) return;
182
- this.aborted = true;
183
- if (this._timeoutTimer) {
184
- clearTimeout(this._timeoutTimer);
185
- this._timeoutTimer = null;
186
- }
187
- this._cancellable.cancel();
188
- this.emit('abort');
189
- this.destroy();
190
- }
191
-
192
- /** Writable stream _write implementation — collect body chunks. */
193
- _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {
194
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding as BufferEncoding);
195
- this._chunks.push(buf);
196
- callback();
197
- }
198
-
199
- /** Called when the writable stream ends — send the request. */
200
- _final(callback: (error?: Error | null) => void): void {
201
- this._sendRequest()
202
- .then(() => callback())
203
- .catch((err) => callback(err));
204
- }
205
-
206
- private _applyHeaders(): void {
207
- if (this.headersSent) return;
208
- this.headersSent = true;
209
-
210
- const requestHeaders = this._message.get_request_headers();
211
- for (const [key, value] of this._headers) {
212
- if (Array.isArray(value)) {
213
- for (const v of value) {
214
- requestHeaders.append(key, v);
215
- }
216
- } else {
217
- requestHeaders.replace(key, value as string);
218
- }
219
- }
220
- }
221
-
222
- private async _sendRequest(): Promise<void> {
223
- this._applyHeaders();
224
-
225
- // Set request body if we have data
226
- const body = Buffer.concat(this._chunks);
227
- if (body.length > 0) {
228
- const contentType = (this._headers.get('content-type') as string) || 'application/octet-stream';
229
- this._message.set_request_body_from_bytes(contentType, new GLib.Bytes(body));
230
- }
231
-
232
- try {
233
- // Send request asynchronously
234
- const inputStream = await new Promise<Gio.InputStream>((resolve, reject) => {
235
- this._session.send_async(this._message, GLib.PRIORITY_DEFAULT, this._cancellable, (_self: any, asyncRes: Gio.AsyncResult) => {
236
- try {
237
- const stream = this._session.send_finish(asyncRes);
238
- resolve(stream);
239
- } catch (error) {
240
- reject(error);
241
- }
242
- });
243
- });
244
-
245
- // Read the entire response body before emitting 'response'.
246
- const bodyChunks: Buffer[] = [];
247
- try {
248
- let chunk: Uint8Array | null;
249
- while ((chunk = await readBytesAsync(inputStream, 4096, GLib.PRIORITY_DEFAULT, this._cancellable)) !== null) {
250
- bodyChunks.push(Buffer.from(chunk));
251
- }
252
- } catch (readErr) {
253
- // Reading may fail if the connection was reset — still emit response with what we have
254
- }
255
-
256
- // Build IncomingMessage from the response
257
- const res = new IncomingMessage();
258
- res.statusCode = this._message.status_code;
259
- res.statusMessage = this._message.get_reason_phrase();
260
- res.httpVersion = '1.1';
261
-
262
- // Parse response headers
263
- const responseHeaders = this._message.get_response_headers();
264
- responseHeaders.foreach((name: string, value: string) => {
265
- const lower = name.toLowerCase();
266
- res.rawHeaders.push(name, value);
267
- if (lower in res.headers) {
268
- const existing = res.headers[lower];
269
- if (Array.isArray(existing)) {
270
- existing.push(value);
271
- } else {
272
- res.headers[lower] = [existing as string, value];
273
- }
274
- } else {
275
- res.headers[lower] = value;
276
- }
277
- });
278
-
279
- this.finished = true;
280
-
281
- // Clear timeout — response received
282
- if (this._timeoutTimer) {
283
- clearTimeout(this._timeoutTimer);
284
- this._timeoutTimer = null;
285
- }
286
-
287
- // Emit 'response' so the user can attach 'data'/'end' listeners
288
- this.emit('response', res);
289
-
290
- // Now push the buffered body data into the Readable stream.
291
- // Defer to next tick so listeners from the 'response' handler are attached.
292
- setTimeout(() => {
293
- for (const buf of bodyChunks) {
294
- res.push(buf);
295
- }
296
- res.push(null);
297
- res.complete = true;
298
- }, 0);
299
- } catch (error: any) {
300
- if (this.aborted) {
301
- this.emit('abort');
302
- } else {
303
- this.emit('error', error instanceof Error ? error : new Error(String(error)));
304
- }
305
- }
306
- }
307
- }