diff --git a/src/puter-js/src/index.js b/src/puter-js/src/index.js index 6a740d35b..5e77f0749 100644 --- a/src/puter-js/src/index.js +++ b/src/puter-js/src/index.js @@ -21,8 +21,6 @@ import { NoPuterYetService } from './services/NoPuterYet.js'; import { Debug } from './modules/Debug.js'; import { PSocket } from './modules/networking/PSocket.js'; import { PTLSSocket } from "./modules/networking/PTLS.js" -import { PWispHandler } from './modules/networking/PWispHandler.js'; -import { make_http_api } from './lib/http.js'; import Exec from './modules/Exec.js'; import Convert from './modules/Convert.js'; import Threads from './modules/Threads.js'; @@ -330,10 +328,6 @@ export default window.puter = (function() { await this.services.wait_for_init(['api-access']); this.p_can_request_rao_.resolve(); })(); - - // TODO: This should be separated into modules called "Net" and "Http". - // Modules need to be refactored first because right now they - // are too tightly-coupled with authentication state. this.net = { @@ -354,12 +348,6 @@ export default window.puter = (function() { }, fetch: pFetch } - - // === puter.http === - this.http = make_http_api( - { Socket: this.net.Socket, DEFAULT_PORT: 80 }); - this.https = make_http_api( - { Socket: this.net.tls.TLSSocket, DEFAULT_PORT: 443 }); } diff --git a/src/puter-js/src/lib/http.js b/src/puter-js/src/lib/http.js deleted file mode 100644 index ec3ac2bb0..000000000 --- a/src/puter-js/src/lib/http.js +++ /dev/null @@ -1,236 +0,0 @@ -import putility from "@heyputer/putility"; -import EventListener from "./EventListener.js"; - -// TODO: this inheritance is an anti-pattern; we should use -// a trait or mixin for event emitters. -export class HTTPRequest extends EventListener { - constructor ({ options, callback }) { - super(['data','end','error']); - this.options = options; - this.callback = callback; - - this.buffer = []; - this.onData_ = null; - } - set onData (callback) { - this.onData_ = callback; - if ( this.buffer.length ) { - this.buffer.forEach(chunk => this.onData_(chunk)); - this.buffer = []; - } - } - write (chunk) { - // NOTE: Should be `.on('data', ...)` instead of this onData thing - // but how do we buffer in that case? EventListener doesn't - // currently support buffering events and #eventListeners is - // private. - if ( this.onData_ ) { - this.onData_(chunk); - } else { - this.buffer.push(chunk); - } - } - end () { - this.emit('end'); - } -} - -export const make_http_api = ({ Socket, DEFAULT_PORT }) => { - // Helper to create an EventEmitter-like object - - const api = {}; - - api.request = (options, callback) => { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - - let sock; - - // Request object - const req = new HTTPRequest([ - 'data', - 'end', - 'error', - ]); - - // Response object - const res = new EventListener([ - 'data', - 'end', - 'error', - ]); - res.headers = {}; - res.statusCode = null; - res.statusMessage = ''; - - let buffer = ''; - - let amount = 0; - const TRANSFER_CONTENT_LENGTH = { - data: data => { - const contentLength = parseInt(res.headers['content-length'], 10); - if ( buffer ) { - const bin = encoder.encode(buffer); - data = new Uint8Array([...bin, ...data]); - buffer = ''; - } - amount += data.length; - res.emit('data', decoder.decode(data)); - if (amount >= contentLength) { - sock.close(); - } - } - }; - const TRANSFER_CHUNKED = { - data: data => { - // TODO - throw new Error('Chunked transfer encoding not implemented'); - } - }; - const TRANSFER_NO_KEEPALIVE = { - data: data => { - if ( buffer ) { - const bin = encoder.encode(buffer); - data = new Uint8Array([...bin, ...data]); - buffer = ''; - } - res.emit('data', decoder.decode(data)); - } - }; - let transfer = null; - - let keepalive = false; - const STATE_HEADERS = { - data: data => { - data = decoder.decode(data); - - buffer += data; - const headerEndIndex = buffer.indexOf('\r\n\r\n'); - if ( headerEndIndex === -1 ) return; - - // Parse headers - const headersString = buffer.substring(0, headerEndIndex); - const headerLines = headersString.split('\r\n'); - - // Remove headers from buffer - buffer = buffer.substring(headerEndIndex + 4); - - // Parse status line - const [httpVersion, statusCode, ...statusMessageParts] = headerLines[0].split(' '); - res.statusCode = parseInt(statusCode, 10); - res.statusMessage = statusMessageParts.join(' '); - - // Parse headers - for (let i = 1; i < headerLines.length; i++) { - const [key, ...valueParts] = headerLines[i].split(':'); - if (key) { - res.headers[key.toLowerCase().trim()] = valueParts.join(':').trim(); - } - } - - - if ( ! keepalive ) { - transfer = TRANSFER_NO_KEEPALIVE; - } else if ( res.headers['transfer-encoding'] === 'chunked' ) { - transfer = TRANSFER_CHUNKED; - } else if ( res.headers['transfer-encoding'] ) { - throw new Error('Unsupported transfer encoding'); - } else if ( res.headers['content-length'] ) { - transfer = TRANSFER_CONTENT_LENGTH; - } else { - throw new Error('No content length or transfer encoding'); - } - state = STATE_BODY; - - callback(res); - } - }; - const STATE_BODY = { - data: data => { - transfer.data(data); - } - }; - let state = STATE_HEADERS; - - // Construct and send HTTP request - const method = options.method || 'GET'; - const path = options.path || '/'; - const headers = options.headers || {}; - headers['Host'] = options.hostname; - if ( ! headers['Connection'] ) { - headers['Connection'] = 'close'; - } else { - if ( headers['Connection'] !== 'close' ) { - keepalive = true; - } - } - - let headerString = `${method} ${path} HTTP/1.1\r\n`; - for (const [key, value] of Object.entries(headers)) { - headerString += `${key}: ${value}\r\n`; - } - - let bodyChunks = []; - - if (options.data) { - bodyChunks.push(options.data); - } - - sock = new Socket(options.hostname, options.port ?? DEFAULT_PORT); - - const p_socketOpen = new putility.libs.promise.TeePromise(); - const p_reqEnd = new putility.libs.promise.TeePromise(); - - (async () => { - await p_socketOpen; - req.onData = (chunk) => { - if ( typeof chunk === 'string' ) { - chunk = encoder.encode(chunk); - } - bodyChunks.push(chunk); - } - await p_reqEnd; - if ( bodyChunks.length ) { - headerString += `Content-Length: ${bodyChunks.reduce((acc, chunk) => acc + chunk.length, 0)}\r\n`; - } - sock.write(encoder.encode(headerString)); - sock.write(encoder.encode('\r\n')); - bodyChunks.forEach(chunk => sock.write(chunk)); - })() - - req.on('end', () => { - p_reqEnd.resolve(); - }) - - sock.on('data', (data) => { - console.log('data event', data); - state.data(data); - }); - sock.on('open', () => { - p_socketOpen.resolve(); - }); - sock.on('error', (err) => { - req.emit('error', err); - }); - let closed = false; - sock.on('close', () => { - if ( closed ) { - console.error('close event after closed'); - return; - } - closed = true; - if ( buffer ) { - console.log('close with buffer', buffer); - const bin = encoder.encode(buffer); - buffer = ''; - state.data(bin); - } - res.emit('end'); - }); - - - return req; - }; - - return api; -}; \ No newline at end of file