OverviewFilesCommitsIssuesDocsReleasesPeers

ollama_semantic_function_calling / ollamatools-new / keet / node_modules / bare-http1 / lib / server-connection.js

272 lines · javascript ref:
const tcp = require('bare-tcp')
const { isEnded, isFinished, getStreamError } = require('bare-stream')
const HTTPIncomingMessage = require('./incoming-message')
const HTTPServerResponse = require('./server-response')
const constants = require('./constants')

const empty = Buffer.alloc(0)

module.exports = class HTTPServerConnection {
  static _connections = new WeakMap()

  static for(socket) {
    return this._connections.get(socket) || null
  }

  constructor(server, socket, opts = {}) {
    const {
      IncomingMessage = HTTPIncomingMessage,
      ServerResponse = HTTPServerResponse
    } = opts

    this.server = server
    this.socket = socket

    this.req = null
    this.res = null

    this._IncomingMessage = IncomingMessage
    this._ServerResponse = ServerResponse

    this._state = constants.state.BEFORE_HEAD
    this._length = -1
    this._read = 0
    this._buffer = null
    this._idle = true

    this._onclose = this._onclose.bind(this)
    this._ondata = this._ondata.bind(this)
    this._ondrain = this._ondrain.bind(this)
    this._ontimeout = this._ontimeout.bind(this)

    socket
      .on('error', noop)
      .on('close', this._onclose)
      .on('data', this._ondata)
      .on('drain', this._ondrain)
      .on('timeout', this._ontimeout)

    HTTPServerConnection._connections.set(socket, this)

    if (this.server.timeout) socket.setTimeout(this.server.timeout)
  }

  get idle() {
    return this._idle
  }

  _onclose() {
    if (this.req && !isEnded(this.req)) this.req.destroy()
    if (this.res && !isFinished(this.res)) this.res.destroy()
    const err = getStreamError(this.socket)
    if (err) this.socket.destroy(err)
  }

  _ondata(data) {
    this._idle = false

    if (this._state === constants.state.IN_BODY) return this._onbody(data)

    if (this._buffer !== null) {
      this._buffer = Buffer.concat([this._buffer, data])
    } else {
      this._buffer = data
    }

    let hits = 0

    for (let i = 0; i < this._buffer.byteLength; i++) {
      const b = this._buffer[i]

      if (hits === 0 && b === 13) {
        hits++
      } else if (hits === 1 && b === 10) {
        hits++

        if (this._state === constants.state.BEFORE_CHUNK) {
          const head = this._buffer.subarray(0, i - 1)
          this._buffer =
            i + 1 === this._buffer.byteLength
              ? null
              : this._buffer.subarray(i + 1)
          i = 0
          hits = 0
          this._onchunklength(head)

          if (this._buffer === null) break
        } else if (this._state === constants.state.IN_CHUNK) {
          const chunk = this._buffer.subarray(0, i - 1)

          if (chunk.byteLength !== this._length) {
            hits = 0
            continue
          }

          this._buffer =
            i + 1 === this._buffer.byteLength
              ? null
              : this._buffer.subarray(i + 1)
          i = 0
          hits = 0
          this._onchunk(chunk)

          if (this._buffer === null) break
        }
      } else if (hits === 2 && b === 13) {
        hits++
      } else if (hits === 3 && b === 10) {
        if (this._state === constants.state.BEFORE_HEAD) {
          const head = this._buffer.subarray(0, i - 3)
          this._buffer =
            i + 1 === this._buffer.byteLength
              ? null
              : this._buffer.subarray(i + 1)
          i = 0
          hits = 0
          this._onhead(head)

          if (this._buffer === null) break
        }
      } else {
        hits = 0
      }
    }
  }

  _onhead(data) {
    this._state = constants.state.IN_HEAD

    const r = data.toString().split('\r\n')
    if (r.length === 0) return this.socket.destroy()

    const [method, url] = r[0].split(' ')
    if (!method || !url) return this.socket.destroy()

    const headers = {}

    for (let i = 1; i < r.length; i++) {
      const [name, value] = r[i].split(': ')
      headers[name.toLowerCase()] = value
    }

    this.req = new this._IncomingMessage(this.socket, headers, { method, url })

    this.req.on('close', () => {
      this.req = null
      this._onreset()
    })

    this.req.resume()
    this.req.pause()

    if (headers.connection && headers.connection.toLowerCase() === 'upgrade') {
      const head = this._buffer
      this._buffer = null
      return this._onupgrade(head)
    }

    this.res = new this._ServerResponse(
      this.socket,
      this.req,
      headers.connection === 'close'
    )

    this.res.on('close', () => {
      this.res = null
    })

    this.server.emit('request', this.req, this.res)

    if (headers['transfer-encoding'] === 'chunked') {
      this._state = constants.state.BEFORE_CHUNK
    } else {
      this._length = parseInt(headers['content-length'], 10) || 0

      if (this._length === 0) return this._onfinished()

      this._state = constants.state.IN_BODY

      if (this._buffer) {
        const body = this._buffer
        this._buffer = null
        this._onbody(body)
      }
    }
  }

  _onchunklength(data) {
    this._length = parseInt(data.toString(), 16)

    if (this._length === 0) this._onfinished()
    else this._state = constants.state.IN_CHUNK
  }

  _onchunk(data) {
    this._read += data.byteLength

    this.req.push(data)

    this._state = constants.state.BEFORE_CHUNK
  }

  _onbody(data) {
    this._read += data.byteLength

    this.req.push(data)

    if (this._read === this._length) this._onfinished()
  }

  _onupgrade(head) {
    this._ondetach()

    const req = this.req

    req.upgrade = true
    req.destroy()

    this.server.emit('upgrade', req, this.socket, head || empty)
  }

  _ontimeout() {
    const reqTimeout = this.req && this.req.emit('timeout')
    const resTimeout = this.res && this.res.emit('timeout')
    const serverTimeout = this.server.emit('timeout', this.socket)

    if (!reqTimeout && !resTimeout && !serverTimeout) this.socket.destroy()
  }

  _onfinished() {
    if (this.req) this.req.push(null)
  }

  _onreset() {
    this._state = constants.state.BEFORE_HEAD
    this._length = -1
    this._read = 0
    this._buffer = null
    this._idle = true

    if (this.server._state & tcp.constants.state.CLOSING) {
      this.socket.destroy()
    }
  }

  _ondrain() {
    if (this.res) this.res._continueWrite()
  }

  _ondetach() {
    this.socket
      .off('error', noop)
      .off('close', this._onclose)
      .off('data', this._ondata)
      .off('drain', this._ondrain)
      .off('timeout', this._ontimeout)

    HTTPServerConnection._connections.delete(this.socket)
  }
}

function noop() {}