Skip to main content

NODE_HTTP_JS

Constant NODE_HTTP_JS 

Source
pub const NODE_HTTP_JS: &str = r#"
import EventEmitter from "node:events";

// ─── STATUS_CODES ────────────────────────────────────────────────────────────

const STATUS_CODES = {
  200: 'OK', 201: 'Created', 204: 'No Content',
  301: 'Moved Permanently', 302: 'Found', 304: 'Not Modified',
  400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden',
  404: 'Not Found', 405: 'Method Not Allowed', 408: 'Request Timeout',
  500: 'Internal Server Error', 502: 'Bad Gateway', 503: 'Service Unavailable',
};

const METHODS = [
  'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT',
  'OPTIONS', 'TRACE', 'PATCH',
];

function __pi_http_is_binary_chunk(chunk) {
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(chunk)) {
    return true;
  }
  if (chunk instanceof Uint8Array || chunk instanceof ArrayBuffer) {
    return true;
  }
  return !!(ArrayBuffer.isView && ArrayBuffer.isView(chunk));
}

function __pi_http_to_uint8(chunk) {
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(chunk)) {
    return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
  }
  if (chunk instanceof Uint8Array) {
    return chunk;
  }
  if (chunk instanceof ArrayBuffer) {
    return new Uint8Array(chunk);
  }
  if (ArrayBuffer.isView && ArrayBuffer.isView(chunk)) {
    return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
  }
  return new TextEncoder().encode(String(chunk ?? ''));
}

function __pi_http_clone_body_chunk(chunk) {
  const view = __pi_http_to_uint8(chunk);
  if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
    return Buffer.from(view);
  }
  return new Uint8Array(view);
}

function __pi_http_chunks_to_base64(chunks) {
  const parts = chunks.map((chunk) => __pi_http_to_uint8(chunk));
  const total = parts.reduce((sum, part) => sum + part.byteLength, 0);
  const merged =
    typeof Buffer !== 'undefined' && typeof Buffer.alloc === 'function'
      ? Buffer.alloc(total)
      : new Uint8Array(total);

  let offset = 0;
  for (const part of parts) {
    merged.set(part, offset);
    offset += part.byteLength;
  }

  if (typeof globalThis.__pi_base64_encode_bytes_native === 'function') {
    return __pi_base64_encode_bytes_native(merged);
  }

  // Fallback for older runtime bounds
  let binary = '';
  let chunk = [];
  for (let i = 0; i < merged.length; i++) {
    chunk.push(merged[i]);
    if (chunk.length >= 4096) {
      binary += String.fromCharCode.apply(null, chunk);
      chunk.length = 0;
    }
  }
  if (chunk.length > 0) {
    binary += String.fromCharCode.apply(null, chunk);
  }
  return __pi_base64_encode_native(binary);
}

function __pi_http_decode_body_bytes(bodyBytes) {
  const encoded = String(bodyBytes ?? '');
  const binary = globalThis.__pi_base64_decode_native(encoded);
  const out =
    typeof Buffer !== 'undefined' && typeof Buffer.alloc === 'function'
      ? Buffer.alloc(binary.length)
      : new Uint8Array(binary.length);

  for (let i = 0; i < binary.length; i++) {
    out[i] = binary.charCodeAt(i) & 0xff;
  }
  return out;
}

function __pi_http_decode_chunk(chunk, encoding) {
  if (!encoding || typeof chunk === 'string') {
    return chunk;
  }

  const bytes = __pi_http_to_uint8(chunk);
  if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
    return Buffer.from(bytes).toString(encoding);
  }
  return new TextDecoder(encoding).decode(bytes);
}

// ─── IncomingMessage ─────────────────────────────────────────────────────────

class IncomingMessage extends EventEmitter {
  constructor(statusCode, headers, body) {
    super();
    this.statusCode = statusCode;
    this.statusMessage = STATUS_CODES[statusCode] || 'Unknown';
    this.headers = headers || {};
    this._body = body || '';
    this._destroyed = false;
    this.complete = false;
    this.httpVersion = '1.1';
    this.method = null;
    this.url = '';
  }

  _deliver() {
    if (this._destroyed) {
      return;
    }

    const chunk = __pi_http_decode_chunk(this._body, this._encoding);
    if (chunk && chunk.length > 0) {
      this.emit('data', chunk);
    }

    if (this._destroyed) {
      return;
    }

    this.complete = true;
    this.emit('end');
  }

  setEncoding(encoding) {
    this._encoding = encoding ? String(encoding) : 'utf8';
    return this;
  }
  resume() { return this; }
  pause() { return this; }
  destroy() {
    if (this._destroyed) {
      return this;
    }
    this._destroyed = true;
    this.emit('close');
    return this;
  }
}

// ─── ClientRequest ───────────────────────────────────────────────────────────

class ClientRequest extends EventEmitter {
  constructor(options, callback) {
    super();
    this._options = options;
    this._body = [];
    this._ended = false;
    this._aborted = false;
    this._headers = {};
    this.socket = { remoteAddress: '127.0.0.1', remotePort: 0 };
    this.method = options.method || 'GET';
    this.path = options.path || '/';

    if (options.headers) {
      for (const [k, v] of Object.entries(options.headers)) {
        this._headers[String(k).toLowerCase()] = String(v);
      }
    }

    if (typeof callback === 'function') {
      this.once('response', callback);
    }
  }

  write(chunk) {
    if (!this._ended && !this._aborted) {
      this._body.push(
        __pi_http_is_binary_chunk(chunk)
          ? __pi_http_clone_body_chunk(chunk)
          : String(chunk)
      );
    }
    return true;
  }

  end(chunk, _encoding, callback) {
    if (typeof chunk === 'function') { callback = chunk; chunk = undefined; }
    if (typeof _encoding === 'function') { callback = _encoding; }
    if (chunk) this.write(chunk);
    if (typeof callback === 'function') this.once('finish', callback);

    this._ended = true;
    this._send();
    return this;
  }

  abort() {
    this._aborted = true;
    this.emit('abort');
    this.destroy();
  }

  destroy(error) {
    this._aborted = true;
    if (error) this.emit('error', error);
    this.emit('close');
    return this;
  }

  setTimeout(ms, callback) {
    if (typeof callback === 'function') this.once('timeout', callback);
    this._timeoutMs = ms;
    return this;
  }

  setNoDelay() { return this; }
  setSocketKeepAlive() { return this; }
  flushHeaders() {}
  getHeader(name) { return this._headers[String(name).toLowerCase()]; }
  setHeader(name, value) {
    if (!this._ended && !this._aborted) {
      this._headers[String(name).toLowerCase()] = String(value);
    }
    return this;
  }
  removeHeader(name) {
    if (!this._ended && !this._aborted) {
      delete this._headers[String(name).toLowerCase()];
    }
    return this;
  }

  _send() {
    if (this._aborted) {
      return;
    }

    const opts = this._options;
    const protocol = opts.protocol || 'http:';
    const hostname = opts.hostname || opts.host || 'localhost';
    const port = opts.port ? `:${opts.port}` : '';
    const path = opts.path || '/';
    const url = `${protocol}//${hostname}${port}${path}`;

    const headers = { ...this._headers };

    const method = (opts.method || 'GET').toUpperCase();
    const request = { url, method, headers };
    if (this._body.length > 0) {
      const hasBinaryChunk = this._body.some((chunk) => __pi_http_is_binary_chunk(chunk));
      if (hasBinaryChunk) {
        request.body_bytes = __pi_http_chunks_to_base64(this._body);
      } else {
        request.body = this._body.join('');
      }
    }
    if (this._timeoutMs) request.timeout = this._timeoutMs;

    // Use pi.http() hostcall if available
    if (typeof globalThis.pi === 'object' && typeof globalThis.pi.http === 'function') {
      try {
        const promise = globalThis.pi.http(request);
        if (promise && typeof promise.then === 'function') {
          promise.then(
            (result) => {
              if (!this._aborted) {
                this._handleResponse(result);
              }
            },
            (err) => {
              if (!this._aborted) {
                this.emit('error', typeof err === 'string' ? new Error(err) : err);
              }
            }
          );
        } else {
          this._handleResponse(promise);
        }
      } catch (err) {
        this.emit('error', err);
      }
    } else {
      // No pi.http available — emit error
      this.emit('error', new Error('HTTP requests require pi.http() hostcall'));
    }

    this.emit('finish');
  }

  _handleResponse(result) {
    if (this._aborted) {
      return;
    }

    if (!result || typeof result !== 'object') {
      this.emit('error', new Error('Invalid HTTP response from hostcall'));
      return;
    }

    const statusCode = result.status || result.statusCode || 200;
    const headers = result.headers || {};
    const body =
      result.body_bytes !== undefined && result.body_bytes !== null
        ? __pi_http_decode_body_bytes(result.body_bytes)
        : (result.body || result.data || '');

    const res = new IncomingMessage(statusCode, headers, body);
    this.emit('response', res);
    // Deliver body asynchronously (in next microtask)
    Promise.resolve().then(() => {
      if (!this._aborted) {
        res._deliver();
      }
    });
  }
}

// ─── Module API ──────────────────────────────────────────────────────────────

function _parseOptions(input, options) {
  if (typeof input === 'string') {
    try {
      const url = new URL(input);
      return {
        protocol: url.protocol,
        hostname: url.hostname,
        port: url.port || undefined,
        path: url.pathname + url.search,
        ...(options || {}),
      };
    } catch (_e) {
      return { path: input, ...(options || {}) };
    }
  }
  if (input && typeof input === 'object' && !(input instanceof URL)) {
    return { ...input };
  }
  if (input instanceof URL) {
    return {
      protocol: input.protocol,
      hostname: input.hostname,
      port: input.port || undefined,
      path: input.pathname + input.search,
      ...(options || {}),
    };
  }
  return options || {};
}

export function request(input, optionsOrCallback, callback) {
  let options;
  if (typeof optionsOrCallback === 'function') {
    callback = optionsOrCallback;
    options = _parseOptions(input);
  } else {
    options = _parseOptions(input, optionsOrCallback);
  }
  if (!options.protocol) options.protocol = 'http:';
  return new ClientRequest(options, callback);
}

export function get(input, optionsOrCallback, callback) {
  const req = request(input, optionsOrCallback, callback);
  req.end();
  return req;
}

export function createServer() {
  throw new Error('node:http.createServer is not available in PiJS');
}

export { STATUS_CODES, METHODS, IncomingMessage, ClientRequest };
export default { request, get, createServer, STATUS_CODES, METHODS, IncomingMessage, ClientRequest };
"#;
Expand description

The JS source for the node:http virtual module.