Prechádzať zdrojové kódy

Add new JavaScript framework: Just-JS (#5969)

* Add new JavaScript framework: Just-JS

* fix bug in cached world test

* remove dependency on openssl for quicker build
Andrew Johnston 5 rokov pred
rodič
commit
2e215bb6c7

+ 1 - 1
.travis.yml

@@ -56,7 +56,7 @@ env:
     - 'TESTDIR="Java/play1 Java/play2-java Java/wildfly-ee"'
     - 'TESTDIR="JavaScript/0http JavaScript/express JavaScript/fastify JavaScript/hapi JavaScript/koa"'
     - 'TESTDIR="JavaScript/nodejs JavaScript/polkadot JavaScript/restana JavaScript/restify JavaScript/sailsjs"'
-    - 'TESTDIR="JavaScript/es4x JavaScript/ringojs"'
+    - 'TESTDIR="JavaScript/es4x JavaScript/ringojs JavaScript/just"'
     - "TESTLANG=Kotlin"
     - "TESTLANG=Lisp"
     - "TESTLANG=Lua"

+ 45 - 0
frameworks/JavaScript/just/README.md

@@ -0,0 +1,45 @@
+# [Just-JS](https://github.com/just-js) Benchmarking Test
+
+This test benchmarks the [Just-JS](https://github.com/just-js) framework. Just-JS is an in progress javascript framework for x86_64 linux.
+
+Author: Andrew Johnston <[email protected]>
+
+### Test Type Implementation Source Code
+
+* [JSON] techempower.js
+* [PLAINTEXT] techempower.js
+* [DB] techempower.js
+* [QUERY] techempower.js
+* [CACHED QUERY] techempower.js
+* [UPDATE] techempower.js
+* [FORTUNES] techempower.js
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/query?q=
+
+### UPDATE
+
+http://localhost:8080/update?q=
+
+### FORTUNES
+
+http://localhost:8080/fortunes
+
+### CACHED QUERY
+
+http://localhost:8080/cached-world?q=
+

+ 32 - 0
frameworks/JavaScript/just/benchmark_config.json

@@ -0,0 +1,32 @@
+{
+  "framework": "just",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/query?q=",
+        "cached_query_url": "/cached-world?q=",
+        "update_url": "/update?q=",
+        "network": "tfb",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "Postgres",
+        "framework": "None",
+        "language": "JavaScript",
+        "flavor": "just",
+        "orm": "Raw",
+        "platform": "just-js",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "just-js",
+        "notes": "",
+        "versus": "nodejs"
+      }
+    }
+  ]
+}

+ 24 - 0
frameworks/JavaScript/just/just.dockerfile

@@ -0,0 +1,24 @@
+FROM debian:stretch-slim AS builder
+RUN apt update
+RUN apt install -y g++ curl make tar gzip libfindbin-libs-perl
+RUN curl -L -o 0.0.1.tar.gz -L https://github.com/just-js/just/archive/0.0.1.tar.gz
+RUN tar -zxvf 0.0.1.tar.gz
+WORKDIR /just-0.0.1
+RUN make runtime
+RUN curl -L -o modules.tar.gz https://github.com/just-js/modules/archive/0.0.1.tar.gz
+RUN tar -zxvf modules.tar.gz
+RUN mv modules-0.0.1 modules
+RUN JUST_HOME=$(pwd) make -C modules/picohttp/ deps http.so
+RUN JUST_HOME=$(pwd) make -C modules/html/ html.so
+
+FROM debian:stretch-slim
+WORKDIR /app
+RUN mkdir -p /app/lib
+COPY lib/stringify.js lib/connection.js lib/dns.js lib/http.js lib/lookup.js lib/pg.js lib/stats.js lib/tcp.js lib/md5.js ./lib/
+COPY techempower.js spawn.js ./
+COPY --from=builder /just-0.0.1/just ./
+COPY --from=builder /just-0.0.1/modules/picohttp/http.so ./
+COPY --from=builder /just-0.0.1/modules/html/html.so ./
+ENV LD_LIBRARY_PATH=/app
+ENV PGPOOL=1
+CMD ["./just", "spawn.js", "techempower.js"]

+ 356 - 0
frameworks/JavaScript/just/lib/connection.js

@@ -0,0 +1,356 @@
+const { lookup } = require('lookup.js')
+const { createClient } = require('tcp.js')
+const { md5AuthMessage, syncMessage, startupMessage, createParser, getPGError, constants } = require('pg.js')
+const { html } = just.library('html.so', 'html')
+
+const {
+  AuthenticationOk,
+  ErrorResponse,
+  RowDescription,
+  CommandComplete,
+  ParseComplete,
+  NoData,
+  ReadyForQuery
+} = constants.messageTypes
+
+const { INT4OID } = constants.fieldTypes
+
+function getMessageName (type) {
+  const code = String.fromCharCode(type)
+  let name = ''
+  Object.keys(constants.messageTypes).some(key => {
+    if (constants.messageTypes[key] === type) {
+      name = key
+      return true
+    }
+  })
+  return { type, code, name }
+}
+
+function setupSocket (sock, config) {
+  function compile (query, onComplete) {
+    const buf = new ArrayBuffer(4096)
+    const dv = new DataView(buf)
+    let len = 0
+    const fun = {
+      dv,
+      size: 0,
+      described: false,
+      buffer: new ArrayBuffer(65536),
+      messages: {
+        prepare: { start: 0, len: 0 },
+        bind: { start: 0, len: 0 },
+        exec: { start: 0, len: 0 },
+        describe: { start: 0, len: 0 },
+        flush: { start: 0, len: 0 },
+        sync: { start: 0, len: 0 }
+      },
+      paramStart: 0
+    }
+    fun.buffer.offset = 0
+    const { name, sql, params = [], formats = [], fields = [], portal = '', maxRows = 0 } = query
+    fun.call = (onComplete, syncIt = true, flushIt = false) => {
+      let off = fun.paramStart
+      // 32 bit integers only for now
+      for (let i = 0; i < params.length; i++) {
+        off += 4
+        dv.setUint32(off, params[i])
+        off += 4
+      }
+      const { bind, exec, flush, sync } = fun.messages
+      off = bind.start
+      let len = 0
+      if (flushIt) {
+        len = flush.start + flush.len - off
+      } else if (syncIt) {
+        len = sync.start + sync.len - off
+      } else {
+        len = exec.start + exec.len - off
+      }
+      const r = sock.write(buf, len, off)
+      if (r < len) {
+        just.error('short write')
+      }
+      callbacks.push(onComplete)
+    }
+    fun.append = (onComplete, syncIt = true, flushIt = false) => {
+      let off = fun.paramStart
+      // 32 bit integers only for now
+      for (let i = 0; i < params.length; i++) {
+        off += 4
+        dv.setUint32(off, params[i])
+        off += 4
+      }
+      const { bind, exec, flush, sync } = fun.messages
+      off = bind.start
+      let len = 0
+      if (flushIt) {
+        len = flush.start + flush.len - off
+      } else if (syncIt) {
+        len = sync.start + sync.len - off
+      } else {
+        len = exec.start + exec.len - off
+      }
+      fun.buffer.offset += fun.buffer.copyFrom(buf, fun.buffer.offset, len, off)
+      callbacks.push(onComplete)
+    }
+    fun.send = () => {
+      const r = sock.write(fun.buffer, fun.buffer.offset, 0)
+      if (r < len) {
+        just.error('short write')
+      }
+      fun.buffer.offset = 0
+    }
+    fun.bind = (flushIt = true, onComplete) => {
+      const { bind, flush } = fun.messages
+      sock.write(buf, bind.len, bind.start)
+      if (flushIt) {
+        sock.write(buf, flush.len, flush.start)
+      }
+      callbacks.push(onComplete)
+    }
+    fun.exec = (flushIt = true, onComplete) => {
+      const { exec, flush } = fun.messages
+      sock.write(buf, exec.len, exec.start)
+      if (flushIt) {
+        sock.write(buf, flush.len, flush.start)
+      }
+      callbacks.push(onComplete)
+    }
+    fun.prepare = (flushIt = true, onComplete) => {
+      const { prepare, flush } = fun.messages
+      sock.write(buf, prepare.len, prepare.start)
+      if (flushIt) {
+        sock.write(buf, flush.len, flush.start)
+      }
+      callbacks.push(onComplete)
+    }
+    fun.describe = (flushIt = true, onComplete) => {
+      const { describe, flush } = fun.messages
+      sock.write(buf, describe.len, describe.start)
+      if (flushIt) {
+        sock.write(buf, flush.len, flush.start)
+      }
+      callbacks.push(onComplete)
+    }
+    let off = 0
+    // Prepare Message
+    fun.messages.prepare.start = off
+    len = 1 + 4 + sql.length + 1 + name.length + 1 + 2 + (formats.length * 4)
+    dv.setUint8(off++, 80) // 'P'
+    dv.setUint32(off, len - 1)
+    off += 4
+    off += buf.writeString(name, off)
+    dv.setUint8(off++, 0)
+    off += buf.writeString(sql, off)
+    dv.setUint8(off++, 0)
+    dv.setUint16(off, formats.length)
+    off += 2
+    for (let i = 0; i < formats.length; i++) {
+      dv.setUint32(off, formats[i].oid)
+      off += 4
+    }
+    fun.messages.prepare.len = off - fun.messages.prepare.start
+    // Describe Message
+    fun.messages.describe.start = off
+    len = 7 + name.length
+    dv.setUint8(off++, 68) // 'D'
+    dv.setUint32(off, len - 1)
+    off += 4
+    dv.setUint8(off++, 83) // 'S'
+    off += buf.writeString(name, off)
+    dv.setUint8(off++, 0)
+    fun.messages.describe.len = off - fun.messages.describe.start
+
+    // Bind Message
+    fun.messages.bind.start = off
+    dv.setUint8(off++, 66) // 'B'
+    off += 4 // length - will be filled in later
+    if (portal.length) {
+      off += buf.writeString(portal, off)
+      dv.setUint8(off++, 0)
+      off += buf.writeString(name, off)
+      dv.setUint8(off++, 0)
+    } else {
+      dv.setUint8(off++, 0)
+      off += buf.writeString(name, off)
+      dv.setUint8(off++, 0)
+    }
+    dv.setUint16(off, formats.length || 0)
+    off += 2
+    for (let i = 0; i < formats.length; i++) {
+      dv.setUint16(off, formats[i].format)
+      off += 2
+    }
+    dv.setUint16(off, params.length || 0)
+    off += 2
+    fun.paramStart = off
+    for (let i = 0; i < params.length; i++) {
+      if ((formats[i] || formats[0]).format === 1) {
+        dv.setUint32(off, 4)
+        off += 4
+        dv.setUint32(off, params[i])
+        off += 4
+      } else {
+        const paramString = params[i].toString()
+        dv.setUint32(off, paramString.length)
+        off += 4
+        off += buf.writeString(paramString, off)
+      }
+    }
+    dv.setUint16(off, fields.length)
+    off += 2
+    for (let i = 0; i < fields.length; i++) {
+      dv.setUint16(off, fields[i].format)
+      off += 2
+    }
+    fun.messages.bind.len = off - fun.messages.bind.start
+    dv.setUint32(fun.messages.bind.start + 1, fun.messages.bind.len - 1)
+    // Exec Message
+    fun.messages.exec.start = off
+    len = 6 + portal.length + 4
+    dv.setUint8(off++, 69) // 'E'
+    dv.setUint32(off, len - 1)
+    off += 4
+    if (portal.length) {
+      off += buf.writeString(portal, off)
+    }
+    dv.setUint8(off++, 0)
+    dv.setUint32(off, maxRows)
+    off += 4
+    fun.messages.exec.len = off - fun.messages.exec.start
+    // Sync Message
+    fun.messages.sync.start = off
+    dv.setUint8(off++, 83) // 'S'
+    dv.setUint32(off, 4)
+    off += 4
+    fun.messages.sync.len = off - fun.messages.sync.start
+    // Flush Message
+    fun.messages.flush.start = off
+    dv.setUint8(off++, 72) // 'H'
+    dv.setUint32(off, 4)
+    off += 4
+    fun.messages.flush.len = off - fun.messages.flush.start
+    fun.size = off
+    fun.buf = buf.slice(0, off)
+    Object.assign(query, fun)
+    let readString = just.sys.readString
+    if (query.htmlEscape) {
+      readString = html.escape
+    }
+    query.getRows = () => {
+      const { buf, dv } = parser
+      const { fields } = query
+      const { start, rows } = parser.query
+      let off = start
+      const result = []
+      let i = 0
+      let j = 0
+      let row
+      for (i = 0; i < rows; i++) {
+        off += 5
+        const cols = dv.getUint16(off)
+        off += 2
+        row = Array(cols)
+        result.push(row)
+        for (j = 0; j < cols; j++) {
+          len = dv.getUint32(off)
+          const { oid, format } = (fields[j] || fields[0])
+          off += 4
+          if (format === 0) { // Non-Binary
+            if (oid === INT4OID) {
+              row[j] = parseInt(buf.readString(len, off), 10)
+            } else {
+              row[j] = readString(buf, len, off)
+            }
+          } else {
+            if (oid === INT4OID) {
+              row[j] = dv.getInt32(off)
+            } else {
+              row[j] = buf.slice(off, off + len)
+            }
+          }
+          off += len
+        }
+      }
+      return result
+    }
+    query.getResult = () => parser.getResult()
+    if (!onComplete) return query
+    fun.prepare(true, err => {
+      if (err) return onComplete(err)
+      fun.describe(true, err => {
+        if (err) return onComplete(err)
+        onComplete()
+      })
+    })
+    return query
+  }
+
+  function start (onStart) {
+    callbacks.push(onStart)
+    sock.write(startupMessage(config))
+  }
+
+  function authenticate (onAuthenticate) {
+    callbacks.push(onAuthenticate)
+    sock.write(md5AuthMessage({ user, pass, salt: parser.salt }))
+  }
+
+  function onMessage () {
+    const { type } = parser
+    if (type === CommandComplete) {
+      callbacks.shift()()
+      return
+    }
+    if (type === ReadyForQuery) {
+      if (!sock.authenticated) {
+        sock.authenticated = true
+        callbacks.shift()()
+      }
+      return
+    }
+    if (type === ErrorResponse) {
+      callbacks.shift()(new Error(getPGError(parser.errors)))
+      return
+    }
+    if (type === AuthenticationOk || type === ParseComplete || type === RowDescription || type === NoData) callbacks.shift()()
+  }
+
+  const buf = new ArrayBuffer(64 * 1024)
+  sock.authenticated = false
+  const parser = sock.parser = createParser(buf)
+  const callbacks = []
+  const { user, pass } = config
+  parser.onMessage = onMessage
+  sock.authenticate = authenticate
+  sock.sync = () => sock.write(syncMessage())
+  sock.start = start
+  sock.compile = compile
+  sock.onData = bytes => parser.parse(bytes)
+  sock.onClose = () => {
+    just.print('pg socket closed')
+  }
+  sock.getParams = () => parser.parameters
+  sock.size = () => callbacks.length
+  sock.query = parser.query
+  sock.buffer = buf
+  return sock
+}
+
+function connect (config, onPGConnect) {
+  lookup(config.hostname, record => {
+    const { message } = record
+    const { ip } = message.answer[0]
+    config.address = `${ip[0]}.${ip[1]}.${ip[2]}.${ip[3]}`
+    const sock = createClient(config.address, config.port)
+    sock.onClose = () => {}
+    sock.onConnect = err => {
+      onPGConnect(err, setupSocket(sock, config))
+      return sock.buffer
+    }
+    sock.connect()
+  })
+}
+
+module.exports = { connect, constants, getMessageName }

+ 193 - 0
frameworks/JavaScript/just/lib/dns.js

@@ -0,0 +1,193 @@
+const opcode = {
+  QUERY: 0,
+  IQUERY: 1,
+  STATUS: 2
+}
+
+const qtype = {
+  A: 1,
+  NS: 2,
+  MD: 3,
+  MF: 4,
+  CNAME: 5,
+  SOA: 6,
+  MB: 7,
+  MG: 8,
+  MR: 9,
+  NULL: 10,
+  WKS: 11,
+  PTR: 12,
+  HINFO: 13,
+  MINFO: 14,
+  MX: 15,
+  TXT: 16,
+  // Additional
+  AXFR: 252,
+  MAILB: 253,
+  MAILA: 254,
+  ANY: 255
+}
+
+const qclass = {
+  IN: 1,
+  CS: 2,
+  CH: 3,
+  HS: 4,
+  ANY: 255
+}
+
+const rcode = {
+  NOERROR: 0,
+  FORMAT: 1,
+  SERVER: 2,
+  NAME: 3,
+  NOTIMPL: 4,
+  REFUSED: 5
+}
+
+const types = { opcode, qtype, qclass, rcode }
+
+function readName (offset, buf, view) {
+  let name = []
+  let qnameSize = view.getUint8(offset++)
+  while (qnameSize) {
+    if ((qnameSize & 192) === 192) {
+      let off = (qnameSize - 192) << 8
+      off += view.getUint8(offset++)
+      name = name.concat(readName(off, buf, view))
+      qnameSize = 0
+    } else {
+      name.push(buf.readString(qnameSize, offset))
+      offset += qnameSize
+      qnameSize = view.getUint8(offset++)
+    }
+  }
+  return name
+}
+
+const parse = (buf, len) => {
+  const bytes = new Uint8Array(buf)
+  const view = new DataView(buf)
+  const id = view.getUint16(0)
+  const flags = view.getUint16(2)
+  const QR = (flags >> 15) & 0b1
+  const opCode = (flags >> 11) & 0b1111
+  const AA = (flags >> 10) & 0b1
+  const TC = (flags >> 9) & 0b1
+  const RD = (flags >> 8) & 0b1
+  const RA = (flags >> 7) & 0b1
+  const Z = (flags >> 4) & 0b111
+  const RCODE = flags & 0b1111
+  const qcount = view.getUint16(4)
+  const ancount = view.getUint16(6)
+  const nscount = view.getUint16(8)
+  const arcount = view.getUint16(10)
+  const question = []
+  const answer = []
+  const authority = []
+  const additional = []
+  const start = 12
+  let off = start
+  let i = off
+  let counter = qcount
+  while (counter--) {
+    let size = 0
+    const sections = []
+    while (bytes[i++]) size++
+    if (size > 0) {
+      while (off - start < size) {
+        const qnameSize = view.getUint8(off++)
+        sections.push(buf.readString(qnameSize, off))
+        off += qnameSize
+      }
+    }
+    off++
+    const qtype = view.getUint16(off)
+    off += 2
+    const qclass = view.getUint16(off)
+    off += 2
+    question.push({ qtype, qclass, name: sections })
+  }
+  counter = ancount
+  while (counter--) {
+    const next = view.getUint16(off)
+    let name
+    if ((0b1100000000000000 & next) === 0b1100000000000000) {
+      name = readName(next & 0b11111111111111, buf, view)
+      off += 2
+    } else {
+      name = readName(off, buf, view)
+      off += name.length + (name.reduce((a, v) => a + v.length, 0)) + 1
+    }
+    const qtype = view.getUint16(off)
+    off += 2
+    const qclass = view.getUint16(off)
+    off += 2
+    const ttl = view.getUint32(off)
+    off += 4
+    const rdLength = view.getUint16(off)
+    off += 2
+    if (qtype === 5) {
+      const cname = readName(off, buf, view)
+      answer.push({ name, cname, qtype, qclass, ttl })
+    } else if (qtype === 1) {
+      answer.push({ name, qtype, qclass, ttl, ip: bytes.slice(off, off + rdLength) })
+    }
+    off += rdLength
+  }
+  return { bytes: bytes.slice(0, len), qcount, nscount, ancount, arcount, id, flags, QR, opCode, AA, TC, RD, RA, Z, RCODE, question, answer, authority, additional }
+}
+
+const create = (domain, buf, id, qtype = 1, qclass = 1) => {
+  const view = new DataView(buf)
+  const bytes = new Uint8Array(buf)
+  view.setUint16(0, id)
+  view.setUint16(2, 0b0000000101000000)
+  view.setUint16(4, 1)
+  view.setUint16(6, 0)
+  view.setUint16(8, 0)
+  view.setUint16(10, 0)
+  let off = 12
+  const parts = domain.split('.')
+  for (const part of parts) {
+    view.setUint8(off++, part.length)
+    buf.writeString(part, off)
+    off += part.length
+  }
+  bytes[off++] = 0
+  view.setUint16(off, qtype)
+  off += 2
+  view.setUint16(off, qclass)
+  off += 2
+  return off
+}
+
+const qtypes = {}
+Object.keys(types.qtype).forEach(k => {
+  qtypes[types.qtype[k]] = k
+})
+const qclasses = {}
+Object.keys(types.qclass).forEach(k => {
+  qclasses[types.qclass[k]] = k
+})
+const opcodes = {}
+Object.keys(types.opcode).forEach(k => {
+  opcodes[types.opcode[k]] = k
+})
+const rcodes = {}
+Object.keys(types.rcode).forEach(k => {
+  rcodes[types.rcode[k]] = k
+})
+
+function getFlags (message) {
+  const flags = []
+  if (message.QR) flags.push('qr')
+  if (message.AA) flags.push('aa')
+  if (message.TC) flags.push('tc')
+  if (message.RD) flags.push('rd')
+  if (message.RA) flags.push('ra')
+  if (message.Z) flags.push('z')
+  return flags.join(' ')
+}
+
+module.exports = { getFlags, create, parse, types, qtypes, qclasses, opcodes, rcodes }

+ 38 - 0
frameworks/JavaScript/just/lib/http.js

@@ -0,0 +1,38 @@
+const { http } = just.library('http.so', 'http')
+const { parseRequests, getRequests, getUrl } = http
+
+const free = []
+
+function createParser (buffer) {
+  if (free.length) {
+    const parser = free.shift()
+    parser.buffer.offset = 0
+    return parser
+  }
+  const answer = [0]
+  const parser = { buffer }
+  function parse (bytes, off = 0) {
+    const count = parseRequests(buffer, buffer.offset + bytes, off, answer)
+    if (count > 0) {
+      parser.onRequests(count)
+    }
+    if (answer[0] > 0) {
+      const start = buffer.offset + bytes - answer[0]
+      const len = answer[0]
+      if (start > buffer.offset) {
+        buffer.copyFrom(buffer, 0, len, start)
+      }
+      buffer.offset = len
+      return
+    }
+    buffer.offset = 0
+  }
+  buffer.offset = 0
+  parser.parse = parse
+  parser.get = count => getRequests(count)
+  parser.url = index => getUrl(index)
+  parser.free = () => free.push(parser)
+  return parser
+}
+
+module.exports = { createParser }

+ 23 - 0
frameworks/JavaScript/just/lib/lookup.js

@@ -0,0 +1,23 @@
+const { create, parse } = require('dns.js')
+const { udp, net } = just
+const { loop } = just.factory
+
+const dnsServer = just.env().DNS_SERVER || '127.0.0.11'
+
+function lookup (query = 'www.google.com', onRecord = () => {}, address = dnsServer, port = 53, buf = new ArrayBuffer(65536)) {
+  const fd = net.socket(net.AF_INET, net.SOCK_DGRAM | net.SOCK_NONBLOCK, 0)
+  net.bind(fd, address, port)
+  loop.add(fd, (fd, event) => {
+    const answer = []
+    const len = udp.recvmsg(fd, buf, answer)
+    const [address, port] = answer
+    const message = { length: len, address, port, message: parse(buf, len) }
+    loop.remove(fd)
+    net.close(fd)
+    onRecord(message)
+  })
+  const len = create(query, buf, 1)
+  udp.sendmsg(fd, buf, address, port, len)
+}
+
+module.exports = { lookup }

+ 683 - 0
frameworks/JavaScript/just/lib/md5.js

@@ -0,0 +1,683 @@
+/**
+ * [js-md5]{@link https://github.com/emn178/js-md5}
+ *
+ * @namespace md5
+ * @version 0.7.3
+ * @author Chen, Yi-Cyuan [[email protected]]
+ * @copyright Chen, Yi-Cyuan 2014-2017
+ * @license MIT
+ */
+(function () {
+  'use strict';
+
+  var ERROR = 'input is invalid type';
+  var WINDOW = typeof window === 'object';
+  var root = WINDOW ? window : {};
+  if (root.JS_MD5_NO_WINDOW) {
+    WINDOW = false;
+  }
+  var WEB_WORKER = !WINDOW && typeof self === 'object';
+  var NODE_JS = !root.JS_MD5_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
+  if (NODE_JS) {
+    root = global;
+  } else if (WEB_WORKER) {
+    root = self;
+  }
+  var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && typeof module === 'object' && module.exports;
+  var AMD = typeof define === 'function' && define.amd;
+  var ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
+  var HEX_CHARS = '0123456789abcdef'.split('');
+  var EXTRA = [128, 32768, 8388608, -2147483648];
+  var SHIFT = [0, 8, 16, 24];
+  var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64'];
+  var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+  var blocks = [], buffer8;
+  if (ARRAY_BUFFER) {
+    var buffer = new ArrayBuffer(68);
+    buffer8 = new Uint8Array(buffer);
+    blocks = new Uint32Array(buffer);
+  }
+
+  if (root.JS_MD5_NO_NODE_JS || !Array.isArray) {
+    Array.isArray = function (obj) {
+      return Object.prototype.toString.call(obj) === '[object Array]';
+    };
+  }
+
+  if (ARRAY_BUFFER && (root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) {
+    ArrayBuffer.isView = function (obj) {
+      return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
+    };
+  }
+
+  /**
+   * @method hex
+   * @memberof md5
+   * @description Output hash as hex string
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {String} Hex string
+   * @example
+   * md5.hex('The quick brown fox jumps over the lazy dog');
+   * // equal to
+   * md5('The quick brown fox jumps over the lazy dog');
+   */
+  /**
+   * @method digest
+   * @memberof md5
+   * @description Output hash as bytes array
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {Array} Bytes array
+   * @example
+   * md5.digest('The quick brown fox jumps over the lazy dog');
+   */
+  /**
+   * @method array
+   * @memberof md5
+   * @description Output hash as bytes array
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {Array} Bytes array
+   * @example
+   * md5.array('The quick brown fox jumps over the lazy dog');
+   */
+  /**
+   * @method arrayBuffer
+   * @memberof md5
+   * @description Output hash as ArrayBuffer
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {ArrayBuffer} ArrayBuffer
+   * @example
+   * md5.arrayBuffer('The quick brown fox jumps over the lazy dog');
+   */
+  /**
+   * @method buffer
+   * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.
+   * @memberof md5
+   * @description Output hash as ArrayBuffer
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {ArrayBuffer} ArrayBuffer
+   * @example
+   * md5.buffer('The quick brown fox jumps over the lazy dog');
+   */
+  /**
+   * @method base64
+   * @memberof md5
+   * @description Output hash as base64 string
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {String} base64 string
+   * @example
+   * md5.base64('The quick brown fox jumps over the lazy dog');
+   */
+  var createOutputMethod = function (outputType) {
+    return function (message) {
+      return new Md5(true).update(message)[outputType]();
+    };
+  };
+
+  /**
+   * @method create
+   * @memberof md5
+   * @description Create Md5 object
+   * @returns {Md5} Md5 object.
+   * @example
+   * var hash = md5.create();
+   */
+  /**
+   * @method update
+   * @memberof md5
+   * @description Create and update Md5 object
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {Md5} Md5 object.
+   * @example
+   * var hash = md5.update('The quick brown fox jumps over the lazy dog');
+   * // equal to
+   * var hash = md5.create();
+   * hash.update('The quick brown fox jumps over the lazy dog');
+   */
+  var createMethod = function () {
+    var method = createOutputMethod('hex');
+    if (NODE_JS) {
+      method = nodeWrap(method);
+    }
+    method.create = function () {
+      return new Md5();
+    };
+    method.update = function (message) {
+      return method.create().update(message);
+    };
+    for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
+      var type = OUTPUT_TYPES[i];
+      method[type] = createOutputMethod(type);
+    }
+    return method;
+  };
+
+  var nodeWrap = function (method) {
+    var crypto = eval("require('crypto')");
+    var Buffer = eval("require('buffer').Buffer");
+    var nodeMethod = function (message) {
+      if (typeof message === 'string') {
+        return crypto.createHash('md5').update(message, 'utf8').digest('hex');
+      } else {
+        if (message === null || message === undefined) {
+          throw ERROR;
+        } else if (message.constructor === ArrayBuffer) {
+          message = new Uint8Array(message);
+        }
+      }
+      if (Array.isArray(message) || ArrayBuffer.isView(message) ||
+        message.constructor === Buffer) {
+        return crypto.createHash('md5').update(new Buffer(message)).digest('hex');
+      } else {
+        return method(message);
+      }
+    };
+    return nodeMethod;
+  };
+
+  /**
+   * Md5 class
+   * @class Md5
+   * @description This is internal class.
+   * @see {@link md5.create}
+   */
+  function Md5(sharedMemory) {
+    if (sharedMemory) {
+      blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
+      blocks[4] = blocks[5] = blocks[6] = blocks[7] =
+      blocks[8] = blocks[9] = blocks[10] = blocks[11] =
+      blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
+      this.blocks = blocks;
+      this.buffer8 = buffer8;
+    } else {
+      if (ARRAY_BUFFER) {
+        var buffer = new ArrayBuffer(68);
+        this.buffer8 = new Uint8Array(buffer);
+        this.blocks = new Uint32Array(buffer);
+      } else {
+        this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+      }
+    }
+    this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0;
+    this.finalized = this.hashed = false;
+    this.first = true;
+  }
+
+  /**
+   * @method update
+   * @memberof Md5
+   * @instance
+   * @description Update hash
+   * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+   * @returns {Md5} Md5 object.
+   * @see {@link md5.update}
+   */
+  Md5.prototype.update = function (message) {
+    if (this.finalized) {
+      return;
+    }
+
+    var notString, type = typeof message;
+    if (type !== 'string') {
+      if (type === 'object') {
+        if (message === null) {
+          throw ERROR;
+        } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
+          message = new Uint8Array(message);
+        } else if (!Array.isArray(message)) {
+          if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) {
+            throw ERROR;
+          }
+        }
+      } else {
+        throw ERROR;
+      }
+      notString = true;
+    }
+    var code, index = 0, i, length = message.length, blocks = this.blocks;
+    var buffer8 = this.buffer8;
+
+    while (index < length) {
+      if (this.hashed) {
+        this.hashed = false;
+        blocks[0] = blocks[16];
+        blocks[16] = blocks[1] = blocks[2] = blocks[3] =
+        blocks[4] = blocks[5] = blocks[6] = blocks[7] =
+        blocks[8] = blocks[9] = blocks[10] = blocks[11] =
+        blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
+      }
+
+      if (notString) {
+        if (ARRAY_BUFFER) {
+          for (i = this.start; index < length && i < 64; ++index) {
+            buffer8[i++] = message[index];
+          }
+        } else {
+          for (i = this.start; index < length && i < 64; ++index) {
+            blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
+          }
+        }
+      } else {
+        if (ARRAY_BUFFER) {
+          for (i = this.start; index < length && i < 64; ++index) {
+            code = message.charCodeAt(index);
+            if (code < 0x80) {
+              buffer8[i++] = code;
+            } else if (code < 0x800) {
+              buffer8[i++] = 0xc0 | (code >> 6);
+              buffer8[i++] = 0x80 | (code & 0x3f);
+            } else if (code < 0xd800 || code >= 0xe000) {
+              buffer8[i++] = 0xe0 | (code >> 12);
+              buffer8[i++] = 0x80 | ((code >> 6) & 0x3f);
+              buffer8[i++] = 0x80 | (code & 0x3f);
+            } else {
+              code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
+              buffer8[i++] = 0xf0 | (code >> 18);
+              buffer8[i++] = 0x80 | ((code >> 12) & 0x3f);
+              buffer8[i++] = 0x80 | ((code >> 6) & 0x3f);
+              buffer8[i++] = 0x80 | (code & 0x3f);
+            }
+          }
+        } else {
+          for (i = this.start; index < length && i < 64; ++index) {
+            code = message.charCodeAt(index);
+            if (code < 0x80) {
+              blocks[i >> 2] |= code << SHIFT[i++ & 3];
+            } else if (code < 0x800) {
+              blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
+              blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
+            } else if (code < 0xd800 || code >= 0xe000) {
+              blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
+              blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
+              blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
+            } else {
+              code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
+              blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
+              blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
+              blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
+              blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
+            }
+          }
+        }
+      }
+      this.lastByteIndex = i;
+      this.bytes += i - this.start;
+      if (i >= 64) {
+        this.start = i - 64;
+        this.hash();
+        this.hashed = true;
+      } else {
+        this.start = i;
+      }
+    }
+    if (this.bytes > 4294967295) {
+      this.hBytes += this.bytes / 4294967296 << 0;
+      this.bytes = this.bytes % 4294967296;
+    }
+    return this;
+  };
+
+  Md5.prototype.finalize = function () {
+    if (this.finalized) {
+      return;
+    }
+    this.finalized = true;
+    var blocks = this.blocks, i = this.lastByteIndex;
+    blocks[i >> 2] |= EXTRA[i & 3];
+    if (i >= 56) {
+      if (!this.hashed) {
+        this.hash();
+      }
+      blocks[0] = blocks[16];
+      blocks[16] = blocks[1] = blocks[2] = blocks[3] =
+      blocks[4] = blocks[5] = blocks[6] = blocks[7] =
+      blocks[8] = blocks[9] = blocks[10] = blocks[11] =
+      blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
+    }
+    blocks[14] = this.bytes << 3;
+    blocks[15] = this.hBytes << 3 | this.bytes >>> 29;
+    this.hash();
+  };
+
+  Md5.prototype.hash = function () {
+    var a, b, c, d, bc, da, blocks = this.blocks;
+
+    if (this.first) {
+      a = blocks[0] - 680876937;
+      a = (a << 7 | a >>> 25) - 271733879 << 0;
+      d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708;
+      d = (d << 12 | d >>> 20) + a << 0;
+      c = (-271733879 ^ (d & (a ^ -271733879))) + blocks[2] - 1126478375;
+      c = (c << 17 | c >>> 15) + d << 0;
+      b = (a ^ (c & (d ^ a))) + blocks[3] - 1316259209;
+      b = (b << 22 | b >>> 10) + c << 0;
+    } else {
+      a = this.h0;
+      b = this.h1;
+      c = this.h2;
+      d = this.h3;
+      a += (d ^ (b & (c ^ d))) + blocks[0] - 680876936;
+      a = (a << 7 | a >>> 25) + b << 0;
+      d += (c ^ (a & (b ^ c))) + blocks[1] - 389564586;
+      d = (d << 12 | d >>> 20) + a << 0;
+      c += (b ^ (d & (a ^ b))) + blocks[2] + 606105819;
+      c = (c << 17 | c >>> 15) + d << 0;
+      b += (a ^ (c & (d ^ a))) + blocks[3] - 1044525330;
+      b = (b << 22 | b >>> 10) + c << 0;
+    }
+
+    a += (d ^ (b & (c ^ d))) + blocks[4] - 176418897;
+    a = (a << 7 | a >>> 25) + b << 0;
+    d += (c ^ (a & (b ^ c))) + blocks[5] + 1200080426;
+    d = (d << 12 | d >>> 20) + a << 0;
+    c += (b ^ (d & (a ^ b))) + blocks[6] - 1473231341;
+    c = (c << 17 | c >>> 15) + d << 0;
+    b += (a ^ (c & (d ^ a))) + blocks[7] - 45705983;
+    b = (b << 22 | b >>> 10) + c << 0;
+    a += (d ^ (b & (c ^ d))) + blocks[8] + 1770035416;
+    a = (a << 7 | a >>> 25) + b << 0;
+    d += (c ^ (a & (b ^ c))) + blocks[9] - 1958414417;
+    d = (d << 12 | d >>> 20) + a << 0;
+    c += (b ^ (d & (a ^ b))) + blocks[10] - 42063;
+    c = (c << 17 | c >>> 15) + d << 0;
+    b += (a ^ (c & (d ^ a))) + blocks[11] - 1990404162;
+    b = (b << 22 | b >>> 10) + c << 0;
+    a += (d ^ (b & (c ^ d))) + blocks[12] + 1804603682;
+    a = (a << 7 | a >>> 25) + b << 0;
+    d += (c ^ (a & (b ^ c))) + blocks[13] - 40341101;
+    d = (d << 12 | d >>> 20) + a << 0;
+    c += (b ^ (d & (a ^ b))) + blocks[14] - 1502002290;
+    c = (c << 17 | c >>> 15) + d << 0;
+    b += (a ^ (c & (d ^ a))) + blocks[15] + 1236535329;
+    b = (b << 22 | b >>> 10) + c << 0;
+    a += (c ^ (d & (b ^ c))) + blocks[1] - 165796510;
+    a = (a << 5 | a >>> 27) + b << 0;
+    d += (b ^ (c & (a ^ b))) + blocks[6] - 1069501632;
+    d = (d << 9 | d >>> 23) + a << 0;
+    c += (a ^ (b & (d ^ a))) + blocks[11] + 643717713;
+    c = (c << 14 | c >>> 18) + d << 0;
+    b += (d ^ (a & (c ^ d))) + blocks[0] - 373897302;
+    b = (b << 20 | b >>> 12) + c << 0;
+    a += (c ^ (d & (b ^ c))) + blocks[5] - 701558691;
+    a = (a << 5 | a >>> 27) + b << 0;
+    d += (b ^ (c & (a ^ b))) + blocks[10] + 38016083;
+    d = (d << 9 | d >>> 23) + a << 0;
+    c += (a ^ (b & (d ^ a))) + blocks[15] - 660478335;
+    c = (c << 14 | c >>> 18) + d << 0;
+    b += (d ^ (a & (c ^ d))) + blocks[4] - 405537848;
+    b = (b << 20 | b >>> 12) + c << 0;
+    a += (c ^ (d & (b ^ c))) + blocks[9] + 568446438;
+    a = (a << 5 | a >>> 27) + b << 0;
+    d += (b ^ (c & (a ^ b))) + blocks[14] - 1019803690;
+    d = (d << 9 | d >>> 23) + a << 0;
+    c += (a ^ (b & (d ^ a))) + blocks[3] - 187363961;
+    c = (c << 14 | c >>> 18) + d << 0;
+    b += (d ^ (a & (c ^ d))) + blocks[8] + 1163531501;
+    b = (b << 20 | b >>> 12) + c << 0;
+    a += (c ^ (d & (b ^ c))) + blocks[13] - 1444681467;
+    a = (a << 5 | a >>> 27) + b << 0;
+    d += (b ^ (c & (a ^ b))) + blocks[2] - 51403784;
+    d = (d << 9 | d >>> 23) + a << 0;
+    c += (a ^ (b & (d ^ a))) + blocks[7] + 1735328473;
+    c = (c << 14 | c >>> 18) + d << 0;
+    b += (d ^ (a & (c ^ d))) + blocks[12] - 1926607734;
+    b = (b << 20 | b >>> 12) + c << 0;
+    bc = b ^ c;
+    a += (bc ^ d) + blocks[5] - 378558;
+    a = (a << 4 | a >>> 28) + b << 0;
+    d += (bc ^ a) + blocks[8] - 2022574463;
+    d = (d << 11 | d >>> 21) + a << 0;
+    da = d ^ a;
+    c += (da ^ b) + blocks[11] + 1839030562;
+    c = (c << 16 | c >>> 16) + d << 0;
+    b += (da ^ c) + blocks[14] - 35309556;
+    b = (b << 23 | b >>> 9) + c << 0;
+    bc = b ^ c;
+    a += (bc ^ d) + blocks[1] - 1530992060;
+    a = (a << 4 | a >>> 28) + b << 0;
+    d += (bc ^ a) + blocks[4] + 1272893353;
+    d = (d << 11 | d >>> 21) + a << 0;
+    da = d ^ a;
+    c += (da ^ b) + blocks[7] - 155497632;
+    c = (c << 16 | c >>> 16) + d << 0;
+    b += (da ^ c) + blocks[10] - 1094730640;
+    b = (b << 23 | b >>> 9) + c << 0;
+    bc = b ^ c;
+    a += (bc ^ d) + blocks[13] + 681279174;
+    a = (a << 4 | a >>> 28) + b << 0;
+    d += (bc ^ a) + blocks[0] - 358537222;
+    d = (d << 11 | d >>> 21) + a << 0;
+    da = d ^ a;
+    c += (da ^ b) + blocks[3] - 722521979;
+    c = (c << 16 | c >>> 16) + d << 0;
+    b += (da ^ c) + blocks[6] + 76029189;
+    b = (b << 23 | b >>> 9) + c << 0;
+    bc = b ^ c;
+    a += (bc ^ d) + blocks[9] - 640364487;
+    a = (a << 4 | a >>> 28) + b << 0;
+    d += (bc ^ a) + blocks[12] - 421815835;
+    d = (d << 11 | d >>> 21) + a << 0;
+    da = d ^ a;
+    c += (da ^ b) + blocks[15] + 530742520;
+    c = (c << 16 | c >>> 16) + d << 0;
+    b += (da ^ c) + blocks[2] - 995338651;
+    b = (b << 23 | b >>> 9) + c << 0;
+    a += (c ^ (b | ~d)) + blocks[0] - 198630844;
+    a = (a << 6 | a >>> 26) + b << 0;
+    d += (b ^ (a | ~c)) + blocks[7] + 1126891415;
+    d = (d << 10 | d >>> 22) + a << 0;
+    c += (a ^ (d | ~b)) + blocks[14] - 1416354905;
+    c = (c << 15 | c >>> 17) + d << 0;
+    b += (d ^ (c | ~a)) + blocks[5] - 57434055;
+    b = (b << 21 | b >>> 11) + c << 0;
+    a += (c ^ (b | ~d)) + blocks[12] + 1700485571;
+    a = (a << 6 | a >>> 26) + b << 0;
+    d += (b ^ (a | ~c)) + blocks[3] - 1894986606;
+    d = (d << 10 | d >>> 22) + a << 0;
+    c += (a ^ (d | ~b)) + blocks[10] - 1051523;
+    c = (c << 15 | c >>> 17) + d << 0;
+    b += (d ^ (c | ~a)) + blocks[1] - 2054922799;
+    b = (b << 21 | b >>> 11) + c << 0;
+    a += (c ^ (b | ~d)) + blocks[8] + 1873313359;
+    a = (a << 6 | a >>> 26) + b << 0;
+    d += (b ^ (a | ~c)) + blocks[15] - 30611744;
+    d = (d << 10 | d >>> 22) + a << 0;
+    c += (a ^ (d | ~b)) + blocks[6] - 1560198380;
+    c = (c << 15 | c >>> 17) + d << 0;
+    b += (d ^ (c | ~a)) + blocks[13] + 1309151649;
+    b = (b << 21 | b >>> 11) + c << 0;
+    a += (c ^ (b | ~d)) + blocks[4] - 145523070;
+    a = (a << 6 | a >>> 26) + b << 0;
+    d += (b ^ (a | ~c)) + blocks[11] - 1120210379;
+    d = (d << 10 | d >>> 22) + a << 0;
+    c += (a ^ (d | ~b)) + blocks[2] + 718787259;
+    c = (c << 15 | c >>> 17) + d << 0;
+    b += (d ^ (c | ~a)) + blocks[9] - 343485551;
+    b = (b << 21 | b >>> 11) + c << 0;
+
+    if (this.first) {
+      this.h0 = a + 1732584193 << 0;
+      this.h1 = b - 271733879 << 0;
+      this.h2 = c - 1732584194 << 0;
+      this.h3 = d + 271733878 << 0;
+      this.first = false;
+    } else {
+      this.h0 = this.h0 + a << 0;
+      this.h1 = this.h1 + b << 0;
+      this.h2 = this.h2 + c << 0;
+      this.h3 = this.h3 + d << 0;
+    }
+  };
+
+  /**
+   * @method hex
+   * @memberof Md5
+   * @instance
+   * @description Output hash as hex string
+   * @returns {String} Hex string
+   * @see {@link md5.hex}
+   * @example
+   * hash.hex();
+   */
+  Md5.prototype.hex = function () {
+    this.finalize();
+
+    var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3;
+
+    return HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
+      HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +
+      HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +
+      HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +
+      HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
+      HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +
+      HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +
+      HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +
+      HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
+      HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +
+      HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +
+      HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +
+      HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
+      HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +
+      HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +
+      HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F];
+  };
+
+  /**
+   * @method toString
+   * @memberof Md5
+   * @instance
+   * @description Output hash as hex string
+   * @returns {String} Hex string
+   * @see {@link md5.hex}
+   * @example
+   * hash.toString();
+   */
+  Md5.prototype.toString = Md5.prototype.hex;
+
+  /**
+   * @method digest
+   * @memberof Md5
+   * @instance
+   * @description Output hash as bytes array
+   * @returns {Array} Bytes array
+   * @see {@link md5.digest}
+   * @example
+   * hash.digest();
+   */
+  Md5.prototype.digest = function () {
+    this.finalize();
+
+    var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3;
+    return [
+      h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 24) & 0xFF,
+      h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 24) & 0xFF,
+      h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 24) & 0xFF,
+      h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 24) & 0xFF
+    ];
+  };
+
+  /**
+   * @method array
+   * @memberof Md5
+   * @instance
+   * @description Output hash as bytes array
+   * @returns {Array} Bytes array
+   * @see {@link md5.array}
+   * @example
+   * hash.array();
+   */
+  Md5.prototype.array = Md5.prototype.digest;
+
+  /**
+   * @method arrayBuffer
+   * @memberof Md5
+   * @instance
+   * @description Output hash as ArrayBuffer
+   * @returns {ArrayBuffer} ArrayBuffer
+   * @see {@link md5.arrayBuffer}
+   * @example
+   * hash.arrayBuffer();
+   */
+  Md5.prototype.arrayBuffer = function () {
+    this.finalize();
+
+    var buffer = new ArrayBuffer(16);
+    var blocks = new Uint32Array(buffer);
+    blocks[0] = this.h0;
+    blocks[1] = this.h1;
+    blocks[2] = this.h2;
+    blocks[3] = this.h3;
+    return buffer;
+  };
+
+  /**
+   * @method buffer
+   * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.
+   * @memberof Md5
+   * @instance
+   * @description Output hash as ArrayBuffer
+   * @returns {ArrayBuffer} ArrayBuffer
+   * @see {@link md5.buffer}
+   * @example
+   * hash.buffer();
+   */
+  Md5.prototype.buffer = Md5.prototype.arrayBuffer;
+
+  /**
+   * @method base64
+   * @memberof Md5
+   * @instance
+   * @description Output hash as base64 string
+   * @returns {String} base64 string
+   * @see {@link md5.base64}
+   * @example
+   * hash.base64();
+   */
+  Md5.prototype.base64 = function () {
+    var v1, v2, v3, base64Str = '', bytes = this.array();
+    for (var i = 0; i < 15;) {
+      v1 = bytes[i++];
+      v2 = bytes[i++];
+      v3 = bytes[i++];
+      base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] +
+        BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] +
+        BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] +
+        BASE64_ENCODE_CHAR[v3 & 63];
+    }
+    v1 = bytes[i];
+    base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] +
+      BASE64_ENCODE_CHAR[(v1 << 4) & 63] +
+      '==';
+    return base64Str;
+  };
+
+  var exports = createMethod();
+
+  if (COMMON_JS) {
+    module.exports = exports;
+  } else {
+    /**
+     * @method md5
+     * @description Md5 hash function, export to global in browsers.
+     * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
+     * @returns {String} md5 hashes
+     * @example
+     * md5(''); // d41d8cd98f00b204e9800998ecf8427e
+     * md5('The quick brown fox jumps over the lazy dog'); // 9e107d9d372bb6826bd81d3542a419d6
+     * md5('The quick brown fox jumps over the lazy dog.'); // e4d909c290d0fb1ca068ffaddf22cbd0
+     *
+     * // It also supports UTF-8 encoding
+     * md5('中文'); // a7bac2239fcdcb3a067903d8077c4a07
+     *
+     * // It also supports byte `Array`, `Uint8Array`, `ArrayBuffer`
+     * md5([]); // d41d8cd98f00b204e9800998ecf8427e
+     * md5(new Uint8Array([])); // d41d8cd98f00b204e9800998ecf8427e
+     */
+    root.md5 = exports;
+    if (AMD) {
+      define(function () {
+        return exports;
+      });
+    }
+  }
+})();

+ 350 - 0
frameworks/JavaScript/just/lib/pg.js

@@ -0,0 +1,350 @@
+const md5 = require('md5.js')
+
+function syncMessage () {
+  const len = 5
+  const buf = new ArrayBuffer(len)
+  const dv = new DataView(buf)
+  dv.setUint8(0, 83)
+  dv.setUint32(1, 4)
+  return buf
+}
+
+function startupMessage ({ user, database, parameters = [] }) {
+  let len = 8 + 4 + 1 + user.length + 1 + 8 + 1 + database.length + 2
+  for (let i = 0; i < parameters.length; i++) {
+    const { name, value } = parameters[i]
+    len += (name.length + 1 + value.length + 1)
+  }
+  const buf = new ArrayBuffer(len)
+  const dv = new DataView(buf)
+  let off = 0
+  dv.setInt32(0, 0)
+  off += 4
+  // 0x00030000 = 3.0
+  dv.setInt32(4, 196608)
+  off += 4
+
+  off += buf.writeString('user', off)
+  dv.setUint8(off++, 0)
+  off += buf.writeString(user, off)
+  dv.setUint8(off++, 0)
+
+  off += buf.writeString('database', off)
+  dv.setUint8(off++, 0)
+  off += buf.writeString(database, off)
+  dv.setUint8(off++, 0)
+
+  for (let i = 0; i < parameters.length; i++) {
+    const { name, value } = parameters[i]
+    off += buf.writeString(name, off)
+    dv.setUint8(off++, 0)
+    off += buf.writeString(value, off)
+    dv.setUint8(off++, 0)
+  }
+  dv.setUint8(off++, 0)
+  dv.setInt32(0, off)
+  return buf
+}
+
+function md5AuthMessage ({ user, pass, salt }) {
+  const token = `${pass}${user}`
+  let hash = md5(token)
+  const plain = new ArrayBuffer(36)
+  plain.writeString(`md5${hash}`, 0)
+  const plain2 = new ArrayBuffer(36)
+  plain2.copyFrom(plain, 0, 32, 3)
+  plain2.copyFrom(salt, 32, 4)
+  hash = `md5${md5(plain2)}`
+  const len = hash.length + 5
+  let off = 0
+  const buf = new ArrayBuffer(len + 1)
+  const dv = new DataView(buf)
+  dv.setUint8(off++, 112)
+  dv.setUint32(off, len)
+  off += 4
+  off += buf.writeString(hash, off)
+  dv.setUint8(off++, 0)
+  return buf
+}
+
+function createParser (buf) {
+  let nextRow = 0
+  let parseNext = 0
+  let parameters = {}
+  const query = { start: 0, end: 0, rows: 0, running: false }
+
+  if (freeList.length) return freeList.shift()
+
+  function onDataRow (len, off) {
+    // D = DataRow
+    nextRow++
+    return off + len - 4
+  }
+
+  function onCommandComplete (len, off) {
+    // C = CommandComplete
+    query.end = off
+    query.rows = nextRow
+    query.running = false
+    off += len - 4
+    nextRow = 0
+    parser.onMessage()
+    return off
+  }
+
+  function onRowDescripton (len, off) {
+    // T = RowDescription
+    const fieldCount = dv.getInt16(off)
+    off += 2
+    fields.length = 0
+    for (let i = 0; i < fieldCount; i++) {
+      const name = readCString(buf, u8, off)
+      off += name.length + 1
+      const tid = dv.getInt32(off)
+      off += 4
+      const attrib = dv.getInt16(off)
+      off += 2
+      const oid = dv.getInt32(off)
+      off += 4
+      const size = dv.getInt16(off)
+      off += 2
+      const mod = dv.getInt32(off)
+      off += 4
+      const format = dv.getInt16(off)
+      off += 2
+      fields.push({ name, tid, attrib, oid, size, mod, format })
+    }
+    parser.onMessage()
+    return off
+  }
+
+  function onAuthenticationOk (len, off) {
+    // R = AuthenticationOk
+    const method = dv.getInt32(off)
+    off += 4
+    if (method === constants.AuthenticationMD5Password) {
+      parser.salt = buf.slice(off, off + 4)
+      off += 4
+      parser.onMessage()
+    }
+    return off
+  }
+
+  function onErrorResponse (len, off) {
+    // E = ErrorResponse
+    errors.length = 0
+    let fieldType = u8[off++]
+    while (fieldType !== 0) {
+      const val = readCString(buf, u8, off)
+      errors.push({ type: fieldType, val })
+      off += (val.length + 1)
+      fieldType = u8[off++]
+    }
+    parser.onMessage()
+    return off
+  }
+
+  function onParameterStatus (len, off) {
+    // S = ParameterStatus
+    const key = readCString(buf, u8, off)
+    off += (key.length + 1)
+    const val = readCString(buf, u8, off)
+    off += val.length + 1
+    parameters[key] = val
+    return off
+  }
+
+  function onParameterDescription (len, off) {
+    // t = ParameterDescription
+    const nparams = dv.getInt16(off)
+    parser.params = []
+    off += 2
+    for (let i = 0; i < nparams; i++) {
+      parser.params.push(dv.getUint32(off))
+      off += 4
+    }
+    return off
+  }
+
+  function onParseComplete (len, off) {
+    // 1 = ParseComplete
+    off += len - 4
+    parser.onMessage()
+    return off
+  }
+
+  function onBindComplete (len, off) {
+    // 2 = BindComplete
+    off += len - 4
+    parser.onMessage()
+    query.rows = 0
+    query.start = query.end = off
+    query.running = true
+    return off
+  }
+
+  function onReadyForQuery (len, off) {
+    // Z = ReadyForQuery
+    parser.status = u8[off]
+    parser.onMessage()
+    off += len - 4
+    return off
+  }
+
+  function onBackendKeyData (len, off) {
+    // K = BackendKeyData
+    parser.pid = dv.getUint32(off)
+    off += 4
+    parser.key = dv.getUint32(off)
+    off += 4
+    parser.onMessage()
+    return off
+  }
+
+  function parse (bytesRead) {
+    let type
+    let len
+    let off = parseNext
+    const end = buf.offset + bytesRead
+    while (off < end) {
+      const remaining = end - off
+      let want = 5
+      if (remaining < want) {
+        if (byteLength - off < 1024) {
+          if (query.running) {
+            const queryLen = off - query.start + remaining
+            buf.copyFrom(buf, 0, queryLen, query.start)
+            buf.offset = queryLen
+            parseNext = off - query.start
+            query.start = 0
+            return
+          }
+          buf.copyFrom(buf, 0, remaining, off)
+          buf.offset = remaining
+          parseNext = 0
+          return
+        }
+        buf.offset = off + remaining
+        parseNext = off
+        return
+      }
+      type = parser.type = dv.getUint8(off)
+      len = parser.len = dv.getUint32(off + 1)
+      want = len + 1
+      if (remaining < want) {
+        if (byteLength - off < 1024) {
+          if (query.running) {
+            const queryLen = off - query.start + remaining
+            buf.copyFrom(buf, 0, queryLen, query.start)
+            buf.offset = queryLen
+            parseNext = off - query.start
+            query.start = 0
+            return
+          }
+          buf.copyFrom(buf, 0, remaining, off)
+          buf.offset = remaining
+          parseNext = 0
+          return
+        }
+        buf.offset = off + remaining
+        parseNext = off
+        return
+      }
+      off += 5
+      off = (V[type] || V[0])(len, off)
+    }
+    parseNext = buf.offset = 0
+  }
+
+  function getResult () {
+    return readCString(buf, u8, parseNext)
+  }
+
+  function onDefault (len, off) {
+    off += len - 4
+    parser.onMessage()
+    return off
+  }
+
+  function free () {
+    parser.fields.length = 0
+    parser.errors.length = 0
+    parameters = parser.parameters = {}
+    nextRow = 0
+    parseNext = 0
+    query.start = query.end = query.rows = 0
+    query.running = false
+    freeList.push(parser)
+  }
+
+  const { messageTypes } = constants
+  const dv = new DataView(buf)
+  const u8 = new Uint8Array(buf)
+  const byteLength = buf.byteLength
+  const fields = []
+  const errors = []
+  const V = {
+    [messageTypes.AuthenticationOk]: onAuthenticationOk,
+    [messageTypes.ErrorResponse]: onErrorResponse,
+    [messageTypes.RowDescription]: onRowDescripton,
+    [messageTypes.CommandComplete]: onCommandComplete,
+    [messageTypes.ParseComplete]: onParseComplete,
+    [messageTypes.BindComplete]: onBindComplete,
+    [messageTypes.ReadyForQuery]: onReadyForQuery,
+    [messageTypes.BackendKeyData]: onBackendKeyData,
+    [messageTypes.ParameterStatus]: onParameterStatus,
+    [messageTypes.ParameterDescription]: onParameterDescription,
+    [messageTypes.DataRow]: onDataRow,
+    0: onDefault
+  }
+  const parser = {
+    buf,
+    dv,
+    fields,
+    parameters,
+    type: 0,
+    len: 0,
+    errors,
+    getResult,
+    parse,
+    free,
+    query
+  }
+  return parser
+}
+
+function readCString (buf, u8, off) {
+  const start = off
+  while (u8[off] !== 0) off++
+  return buf.readString(off - start, start)
+}
+
+function getPGError (errors) {
+  return errors.filter(v => v.type === 77)[0].val
+}
+
+const constants = {
+  AuthenticationMD5Password: 5,
+  fieldTypes: {
+    INT4OID: 23,
+    VARCHAROID: 1043
+  },
+  messageTypes: {
+    AuthenticationOk: 82,
+    ErrorResponse: 69,
+    RowDescription: 84,
+    CommandComplete: 67,
+    ParseComplete: 49,
+    BindComplete: 50,
+    ReadyForQuery: 90,
+    BackendKeyData: 75,
+    ParameterStatus: 83,
+    ParameterDescription: 116,
+    DataRow: 68,
+    NoData: 110
+  }
+}
+
+const freeList = []
+
+module.exports = { createParser, syncMessage, startupMessage, md5AuthMessage, getPGError, constants }

+ 28 - 0
frameworks/JavaScript/just/lib/stats.js

@@ -0,0 +1,28 @@
+function start () {
+  function onTime () {
+    stats.time = (new Date()).toUTCString()
+  }
+  const stats = { conn: 0, qps: 0, rps: 0, time: 0 }
+  just.setInterval(() => {
+    if (just.buffer) {
+      u32 = new Uint32Array(just.buffer)
+    }
+    if (u32) {
+      Atomics.exchange(u32, 0, stats.rps)
+    } else {
+      const { conn, qps, rps, clients } = stats
+      const { user, system } = just.cpuUsage()
+      const { rss } = just.memoryUsage()
+      const rpspc = ((rps / (user + system)) || 0)
+      just.error(`conn ${conn} qps ${qps} rps ${rps} clients ${clients} mem ${rss} cpu (${user.toFixed(2)}/${system.toFixed(2)}) ${(user + system).toFixed(2)} rps/core ${rpspc.toFixed(2)}`)
+    }
+    stats.qps = stats.rps = 0
+  }, 1000)
+  just.setInterval(onTime, 100)
+  onTime()
+  return stats
+}
+
+let u32
+
+module.exports = { start }

+ 103 - 0
frameworks/JavaScript/just/lib/stringify.js

@@ -0,0 +1,103 @@
+// lifted from here: https://github.com/lucagez/slow-json-stringify
+
+var _prepare = function(e) {
+  var r = JSON.stringify(e, function(e, r) {
+      return r.isSJS ? r.type + "__sjs" : r
+  });
+  return {
+      preparedString: r,
+      preparedSchema: JSON.parse(r)
+  }
+},
+_find = function(path) {
+  for (var length = path.length, str = "obj", i = 0; i < length; i++) str = str.replace(/^/, "("), str += " || {})." + path[i];
+  return just.vm.runScript("((obj) => " + str + ")")
+},
+_makeArraySerializer = function(e) {
+  return e instanceof Function ? function(r) {
+      for (var n = "", t = r.length, a = 0; a < t - 1; a++) n += e(r[a]) + ",";
+      return "[" + (n += e(r[t - 1])) + "]"
+  } : function(e) {
+      return JSON.stringify(e)
+  }
+},
+TYPES = ["number", "string", "boolean", "array", "null"],
+attr = function(e, r) {
+  if (!TYPES.includes(e)) throw new Error('Expected one of: "number", "string", "boolean", "null". received "' + e + '" instead');
+  var n = r || function(e) {
+      return e
+  };
+  return {
+      isSJS: !0,
+      type: e,
+      serializer: "array" === e ? _makeArraySerializer(r) : n
+  }
+},
+defaultRegex = new RegExp('\\n|\\r|\\t|\\"|\\\\', "gm"),
+escape = function(e) {
+  return void 0 === e && (e = defaultRegex),
+      function(r) {
+          return r.replace(e, function(e) {
+              return "\\" + e
+          })
+      }
+},
+_makeQueue = function(e, r) {
+  var n = [];
+  return function e(t, a) {
+      if (void 0 === a && (a = []), !/__sjs/.test(t)) return Object.keys(t).map(function(r) {
+          return e(t[r], a.concat([r]))
+      });
+      var i = Array.from(a),
+          u = _find(i),
+          s = u(r);
+      n.push({
+          serializer: s.serializer,
+          find: u,
+          name: a[a.length - 1]
+      })
+  }(e), n
+},
+_makeChunks = function(e, r) {
+  return e.replace(/"\w+__sjs"/gm, function(e) {
+      return /string/.test(e) ? '"__par__"' : "__par__"
+  }).split("__par__").map(function(e, n, t) {
+      var a = '("' + (r[n] || {}).name + '":("?))$',
+          i = "(,?)" + a,
+          u = /^("}|})/.test(t[n + 1] || ""),
+          s = new RegExp(u ? i : a),
+          f = /^(\"\,|\,|\")/;
+      return {
+          flag: !1,
+          pure: e,
+          prevUndef: e.replace(f, ""),
+          isUndef: e.replace(s, ""),
+          bothUndef: e.replace(f, "").replace(s, "")
+      }
+  })
+},
+_select = function(e) {
+  return function(r, n) {
+      var t = e[n];
+      return void 0 !== r ? t.flag ? t.prevUndef + r : t.pure + r : (e[n + 1].flag = !0, t.flag ? t.bothUndef : t.isUndef)
+  }
+},
+sjs = function(e) {
+  var r = _prepare(e),
+      n = r.preparedString,
+      t = _makeQueue(r.preparedSchema, e),
+      a = _makeChunks(n, t),
+      i = _select(a),
+      u = t.length;
+  return function(e) {
+      for (var r = "", n = 0; n !== u;) {
+          var s = t[n],
+              f = s.serializer,
+              p = (0, s.find)(e);
+          r += i(f(p), n), n += 1
+      }
+      var o = a[a.length - 1];
+      return r + (o.flag ? o.prevUndef : o.pure)
+  }
+};
+exports.sjs = sjs, exports.attr = attr, exports.escape = escape;

+ 175 - 0
frameworks/JavaScript/just/lib/tcp.js

@@ -0,0 +1,175 @@
+
+const { sys, net } = just
+const { EPOLLIN, EPOLLERR, EPOLLHUP, EPOLLOUT } = just.loop
+const { IPPROTO_TCP, O_NONBLOCK, TCP_NODELAY, SO_KEEPALIVE, SOMAXCONN, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SO_REUSEPORT, SOCK_NONBLOCK, SO_ERROR } = net
+
+const { loop } = just.factory
+
+const readableMask = EPOLLIN | EPOLLERR | EPOLLHUP
+const readableWritableMask = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLOUT
+
+function createServer (host = '127.0.0.1', port = 3000) {
+  const server = { host, port }
+  const sockets = {}
+
+  function closeSocket (sock) {
+    const { fd } = sock
+    sock.onClose && sock.onClose(sock)
+    delete sockets[fd]
+    loop.remove(fd)
+    net.close(fd)
+  }
+
+  function onConnect (fd, event) {
+    if (event & EPOLLERR || event & EPOLLHUP) {
+      return closeSocket({ fd })
+    }
+    const clientfd = net.accept(fd)
+    const socket = sockets[clientfd] = { fd: clientfd }
+    net.setsockopt(clientfd, IPPROTO_TCP, TCP_NODELAY, 0)
+    net.setsockopt(clientfd, SOL_SOCKET, SO_KEEPALIVE, 0)
+    loop.add(clientfd, (fd, event) => {
+      if (event & EPOLLERR || event & EPOLLHUP) {
+        return closeSocket(socket)
+      }
+      const bytes = net.recv(fd, buffer, buffer.offset, buffer.byteLength - buffer.offset)
+      if (bytes > 0) {
+        socket.onData(bytes)
+        return
+      }
+      if (bytes < 0) {
+        const errno = sys.errno()
+        if (errno === net.EAGAIN) return
+        just.error(`recv error: ${sys.strerror(errno)} (${errno})`)
+      }
+      closeSocket(socket)
+    })
+    let flags = sys.fcntl(clientfd, sys.F_GETFL, 0)
+    flags |= O_NONBLOCK
+    sys.fcntl(clientfd, sys.F_SETFL, flags)
+    loop.update(clientfd, readableMask)
+    socket.write = (buf, len = buf.byteLength, off = 0) => {
+      const written = net.send(clientfd, buf, len, off)
+      if (written > 0) {
+        return written
+      }
+      if (written < 0) {
+        const errno = sys.errno()
+        if (errno === net.EAGAIN) return written
+        just.error(`write error (${clientfd}): ${sys.strerror(errno)} (${errno})`)
+      }
+      if (written === 0) {
+        just.error(`zero write ${clientfd}`)
+      }
+      return written
+    }
+    socket.writeString = str => net.sendString(clientfd, str)
+    socket.close = () => closeSocket(socket)
+    const buffer = server.onConnect(socket)
+    buffer.offset = 0
+  }
+
+  function listen (maxconn = SOMAXCONN) {
+    const r = net.listen(sockfd, maxconn)
+    if (r === 0) loop.add(sockfd, onConnect)
+    return r
+  }
+  server.listen = listen
+
+  const sockfd = net.socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)
+  net.setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 1)
+  net.setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, 1)
+  net.bind(sockfd, host, port)
+
+  return server
+}
+
+function createClient (address = '127.0.0.1', port = 3000) {
+  const sock = { address, port, connected: false }
+  let fd
+
+  function closeSocket () {
+    sock.onClose && sock.onClose(sock)
+    loop.remove(fd)
+    net.close(fd)
+  }
+
+  function handleRead (fd, event) {
+    const bytes = net.recv(fd, buffer, buffer.offset, buffer.byteLength - buffer.offset)
+    if (bytes > 0) {
+      sock.onData(bytes)
+      return
+    }
+    if (bytes < 0) {
+      const errno = sys.errno()
+      if (errno === net.EAGAIN) return
+      just.print(`recv error: ${sys.strerror(errno)} (${errno})`)
+    }
+    closeSocket(sock)
+  }
+
+  function handleError (fd, event) {
+    const errno = net.getsockopt(fd, SOL_SOCKET, SO_ERROR)
+    if (!sock.connected) {
+      sock.onConnect(new Error(`${errno} : ${just.sys.strerror(errno)}`))
+    }
+  }
+
+  function handleWrite (fd, event) {
+    if (!sock.connected) {
+      net.setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, 0)
+      net.setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, 0)
+      loop.update(fd, readableMask)
+      buffer = sock.onConnect(null, sock)
+      buffer.offset = 0
+      sock.connected = true
+    }
+  }
+
+  function onSocketEvent (fd, event) {
+    if (event & EPOLLERR || event & EPOLLHUP) {
+      handleError(fd, event)
+      closeSocket()
+      return
+    }
+    if (event & EPOLLIN) {
+      handleRead(fd, event)
+    }
+    if (event & EPOLLOUT) {
+      handleWrite(fd, event)
+    }
+  }
+
+  sock.write = (buf, len = buf.byteLength, off = 0) => {
+    const written = net.send(fd, buf, len, off)
+    if (written > 0) {
+      return written
+    }
+    if (written < 0) {
+      const errno = sys.errno()
+      if (errno === net.EAGAIN) return written
+      just.error(`write error (${fd}): ${sys.strerror(errno)} (${errno})`)
+    }
+    if (written === 0) {
+      just.error(`zero write ${fd}`)
+    }
+    return written
+  }
+  sock.writeString = str => net.sendString(fd, str)
+
+  sock.close = () => closeSocket(sock)
+
+  function connect () {
+    fd = net.socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)
+    loop.add(fd, onSocketEvent, readableWritableMask)
+    net.connect(fd, address, port)
+    sock.fd = fd
+    return sock
+  }
+
+  let buffer
+  sock.connect = connect
+  return sock
+}
+
+module.exports = { createServer, createClient }

+ 26 - 0
frameworks/JavaScript/just/spawn.js

@@ -0,0 +1,26 @@
+const { readFile } = require('fs')
+
+function spawn (source) {
+  const shared = new SharedArrayBuffer(4)
+  const u32 = new Uint32Array(shared)
+  const tid = just.thread.spawn(source, just.args.slice(1), shared)
+  const thread = { tid, u32 }
+  threads.push(thread)
+  return thread
+}
+
+function onTimer () {
+  const { user, system } = just.cpuUsage()
+  const { rss } = just.memoryUsage()
+  let total = 0
+  for (const thread of threads) {
+    total += Atomics.load(thread.u32, 0)
+  }
+  just.print(`threads ${threads.length} total ${total} mem ${rss} cpu (${user.toFixed(2)}/${system.toFixed(2)}) ${(user + system).toFixed(2)} qps/core ${(total / (user + system)).toFixed(2)}`)
+}
+
+const source = readFile(just.args[2] || 'test.js')
+const cpus = parseInt(just.env().CPUS || just.sys.cpus, 10)
+const threads = []
+for (let i = 0; i < cpus; i++) spawn(source)
+just.setInterval(onTimer, 1000)

+ 445 - 0
frameworks/JavaScript/just/techempower.js

@@ -0,0 +1,445 @@
+const { connect, constants } = require('lib/connection.js')
+const { createServer } = require('lib/tcp.js')
+const { createParser } = require('lib/http.js')
+const { start } = require('lib/stats.js')
+const { sjs, attr } = require('lib/stringify.js')
+
+function compile (sock, query) {
+  return new Promise((resolve, reject) => {
+    const result = sock.compile(query, err => {
+      if (err) return reject(err)
+      resolve(result)
+    })
+  })
+}
+
+async function onPGAuth (sock) {
+  sock.getWorldById = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: 'select id, randomNumber from World where id = $1',
+    fields: [{ format: 1, oid: INT4OID }],
+    name: 's1',
+    portal: '',
+    maxRows: 0,
+    params: [1]
+  })
+  sock.allFortunes = await compile(sock, {
+    formats: [],
+    sql: 'select * from Fortune',
+    fields: [{ format: 1, oid: INT4OID }, { format: 0, oid: VARCHAROID }],
+    name: 's2',
+    portal: '',
+    maxRows: 0,
+    htmlEscape: true,
+    params: []
+  })
+  sock.updateWorldById = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: 'update World set randomNumber = $2 where id = $1',
+    fields: [],
+    name: 's3',
+    portal: '',
+    maxRows: 0,
+    params: [1, 1]
+  })
+  // TODO: we could actually build these on the fly for any number of updates
+  sock.updateWorldById20 = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: `update world set randomnumber = CASE id 
+when $1 then $2 
+when $3 then $4 
+when $5 then $6 
+when $7 then $8 
+when $9 then $10 
+when $11 then $12 
+when $13 then $14 
+when $15 then $16 
+when $17 then $18 
+when $19 then $20 
+when $21 then $22 
+when $23 then $24 
+when $25 then $26 
+when $27 then $28 
+when $29 then $30 
+when $31 then $32 
+when $33 then $34 
+when $35 then $36 
+when $37 then $38 
+when $39 then $40
+else randomnumber 
+end where id in ($1,$3,$5,$7,$9,$11,$13,$15,$17,$19,$21,$23,$25,$27,$29,$31,$33,$35,$37,$39)
+`,
+    fields: [],
+    name: 's4',
+    portal: '',
+    maxRows: 0,
+    params: Array(40).fill(0)
+  })
+  sock.updateWorldById15 = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: `update world set randomnumber = CASE id 
+when $1 then $2 
+when $3 then $4 
+when $5 then $6 
+when $7 then $8 
+when $9 then $10 
+when $11 then $12 
+when $13 then $14 
+when $15 then $16 
+when $17 then $18 
+when $19 then $20 
+when $21 then $22 
+when $23 then $24 
+when $25 then $26 
+when $27 then $28 
+when $29 then $30 
+else randomnumber 
+end where id in ($1,$3,$5,$7,$9,$11,$13,$15,$17,$19,$21,$23,$25,$27,$29)
+`,
+    fields: [],
+    name: 's5',
+    portal: '',
+    maxRows: 0,
+    params: Array(30).fill(0)
+  })
+  sock.updateWorldById10 = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: `update world set randomnumber = CASE id 
+when $1 then $2 
+when $3 then $4 
+when $5 then $6 
+when $7 then $8 
+when $9 then $10 
+when $11 then $12 
+when $13 then $14 
+when $15 then $16 
+when $17 then $18 
+when $19 then $20 
+else randomnumber 
+end where id in ($1,$3,$5,$7,$9,$11,$13,$15,$17,$19)
+`,
+    fields: [],
+    name: 's6',
+    portal: '',
+    maxRows: 0,
+    params: Array(20).fill(0)
+  })
+  sock.updateWorldById5 = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: `update world set randomnumber = CASE id 
+when $1 then $2 
+when $3 then $4 
+when $5 then $6 
+when $7 then $8 
+when $9 then $10 
+else randomnumber 
+end where id in ($1,$3,$5,$7,$9)
+`,
+    fields: [],
+    name: 's7',
+    portal: '',
+    maxRows: 0,
+    params: Array(10).fill(0)
+  })
+  sock.getCachedWorldById = await compile(sock, {
+    formats: [{ format: 1, oid: INT4OID }],
+    sql: 'select id, randomNumber from World where id = $1',
+    fields: [{ format: 1, oid: INT4OID }],
+    name: 's8',
+    portal: '',
+    maxRows: 0,
+    params: [1]
+  })
+  clients.push(sock)
+  stats.clients = clients.length
+  if (clients.length === poolSize) onPGReady()
+}
+
+function onPGConnect (err, sock) {
+  if (err) {
+    just.error(err.stack)
+    just.setTimeout(() => connect(tfb, onPGConnect), 1000)
+    return
+  }
+  sock.onClose = () => just.print('pg.close')
+  sock.start(err => {
+    if (err) return just.error(err.stack)
+    sock.authenticate(err => {
+      if (err) return just.error(err.stack)
+      onPGAuth(sock).catch(err => just.error(err.stack))
+    })
+  })
+}
+
+const HEADER = '<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>'
+const FOOTER = '</table></body></html>'
+const S1 = '<tr><td>'
+const S2 = '</td><td>'
+const S3 = '</td></tr>'
+function getHTML (rows) {
+  let html = HEADER
+  for (const row of rows) {
+    html += (S1 + row[0] + S2 + row[1] + S3)
+  }
+  return html + FOOTER
+}
+
+function sortByMessage (a, b) {
+  if (a[1] > b[1]) return 1
+  if (a[1] < b[1]) return -1
+  return 0
+}
+
+const cache = {}
+
+function onHTTPConnect (sock) {
+  stats.conn++
+  const client = clients[sock.fd % clients.length]
+  const rbuf = new ArrayBuffer(4096)
+  const parser = createParser(rbuf)
+  const { getWorldById, updateWorldById, allFortunes, updateWorldById20, updateWorldById15, updateWorldById10, updateWorldById5, getCachedWorldById } = client
+  const message = { message: 'Hello, World!' }
+  const text = 'Hello, World!'
+  const extra = [0, 'Additional fortune added at request time.']
+  const updateQueries = {
+    5: updateWorldById5,
+    10: updateWorldById10,
+    15: updateWorldById15,
+    20: updateWorldById20
+  }
+  const results = []
+  let queries = 0
+  let updates = 0
+  function onUpdateMulti () {
+    stats.qps++
+    const json = JSON.stringify(results)
+    sock.writeString(`${rJSON}${json.length}${END}${json}`)
+    stats.rps++
+  }
+  function onUpdateSingle () {
+    stats.qps++
+    updates++
+    if (results.length === updates) {
+      const json = JSON.stringify(results)
+      sock.writeString(`${rJSON}${json.length}${END}${json}`)
+      stats.rps++
+    }
+  }
+  function onUpdates () {
+    stats.qps++
+    const [id, randomNumber] = getWorldById.getRows()[0]
+    results.push({ id, randomNumber })
+    if (results.length === queries) {
+      const query = updateQueries[queries]
+      if (query) {
+        let i = 0
+        for (const row of results) {
+          row.randomNumber = Math.ceil(Math.random() * 10000)
+          query.params[i++] = row.id
+          query.params[i++] = row.randomNumber
+        }
+        query.call(onUpdateMulti)
+        return
+      }
+      updates = 0
+      for (const row of results) {
+        row.randomNumber = Math.ceil(Math.random() * 10000)
+        updateWorldById.params[0] = row.id
+        updateWorldById.params[1] = row.randomNumber
+        updateWorldById.append(onUpdateSingle)
+      }
+      updateWorldById.send()
+    }
+  }
+  function handleUpdates (qs) {
+    const [, val] = qs.split('=')
+    queries = Math.min(parseInt(val || 1, 10), 500) || 1
+    results.length = 0
+    for (let i = 1; i < queries; i++) {
+      getWorldById.params[0] = Math.ceil(Math.random() * 10000)
+      getWorldById.append(onUpdates, (i % 20 === 0))
+    }
+    getWorldById.params[0] = Math.ceil(Math.random() * 10000)
+    getWorldById.append(onUpdates)
+    getWorldById.send()
+  }
+  function onMulti () {
+    stats.qps++
+    const [id, randomNumber] = getWorldById.getRows()[0]
+    results.push({ id, randomNumber })
+    if (results.length === queries) {
+      const json = JSON.stringify(results)
+      sock.writeString(`${rJSON}${json.length}${END}${json}`)
+      stats.rps++
+      queries = 0
+    }
+  }
+  function handleMulti (qs) {
+    const [, val] = qs.split('=')
+    queries = Math.min(parseInt(val || 1, 10), 500) || 1
+    results.length = 0
+    for (let i = 1; i < queries; i++) {
+      getWorldById.params[0] = Math.ceil(Math.random() * 10000)
+      getWorldById.append(onMulti, (i % 20 === 0))
+    }
+    getWorldById.params[0] = Math.ceil(Math.random() * 10000)
+    getWorldById.append(onMulti)
+    getWorldById.send()
+  }
+  function onCached () {
+    stats.qps++
+    const row = getCachedWorldById.getRows()[0]
+    const [id, randomNumber] = row
+    const world = { id, randomNumber }
+    cache[id] = world
+    results.push(world)
+    if (results.length === queries) {
+      const json = JSON.stringify(results)
+      sock.writeString(`${rJSON}${json.length}${END}${json}`)
+      stats.rps++
+      queries = 0
+      results.length = 0
+    }
+  }
+  function handleCached (qs) {
+    const [, val] = qs.split('=')
+    queries = Math.min(parseInt(val || 1, 10), 500) || 1
+    for (let i = 1; i < queries; i++) {
+      const id = Math.ceil(Math.random() * 10000)
+      const row = cache[id]
+      if (row) {
+        results.push(row)
+      } else {
+        getCachedWorldById.params[0] = id
+        getCachedWorldById.append(onCached, (i % 20 === 0))
+      }
+    }
+    const id = Math.ceil(Math.random() * 10000)
+    const row = cache[id]
+    if (row) {
+      results.push(row)
+    } else {
+      getCachedWorldById.params[0] = id
+      getCachedWorldById.append(onCached)
+    }
+    if (results.length === queries) {
+      const json = JSON.stringify(results)
+      sock.writeString(`${rJSON}${json.length}${END}${json}`)
+      stats.rps++
+      queries = 0
+      results.length = 0
+      return
+    }
+    getCachedWorldById.send()
+  }
+  function onFortunes () {
+    stats.qps++
+    const html = getHTML([extra, ...allFortunes.getRows()].sort(sortByMessage))
+    sock.writeString(`${rHTML}${utf8Length(html)}${END}${html}`)
+    stats.rps++
+  }
+  function onSingle () {
+    stats.qps++
+    const [id, randomNumber] = getWorldById.getRows()[0]
+    const json = sDB({ id, randomNumber })
+    sock.writeString(`${rJSON}${json.length}${END}${json}`)
+    stats.rps++
+  }
+  const queryPath = '/query'
+  const updatePath = '/update'
+  const cachePath = '/cached-world'
+  const pathSep = '?'
+  const END = '\r\n\r\n'
+  const handlers = {
+    '/json': () => {
+      const json = sJSON(message)
+      sock.writeString(`${rJSON}${json.length}${END}${json}`)
+      stats.rps++
+    },
+    '/fortunes': () => {
+      allFortunes.call(onFortunes)
+    },
+    '/db': () => {
+      getWorldById.params[0] = Math.ceil(Math.random() * 10000)
+      getWorldById.call(onSingle)
+    },
+    '/plaintext': () => {
+      sock.writeString(`${rTEXT}${text.length}${END}${text}`)
+      stats.rps++
+    },
+    default: url => {
+      const [path, qs] = url.split(pathSep)
+      if (path === queryPath) {
+        handleMulti(qs)
+        return
+      }
+      if (path === updatePath) {
+        handleUpdates(qs)
+        return
+      }
+      if (path === cachePath) {
+        handleCached(qs)
+        return
+      }
+      sock.writeString(r404)
+      stats.rps++
+    }
+  }
+  parser.onRequests = count => {
+    if (count > 1) {
+      sock.writeString(`${rTEXT}${text.length}${END}${text}`.repeat(count))
+      stats.rps += count
+      return
+    }
+    const url = parser.url(0)
+    const handler = (handlers[url] || handlers.default)
+    handler(url)
+  }
+  sock.onData = bytes => parser.parse(bytes)
+  sock.onClose = () => {
+    stats.conn--
+    parser.free()
+  }
+  return parser.buffer
+}
+
+function onPGReady () {
+  microtasks = false
+  server.listen()
+}
+
+const { utf8Length } = just.sys
+const poolSize = parseInt(just.env().PGPOOL || just.sys.cpus, 10)
+const server = createServer('0.0.0.0', 8080)
+server.onConnect = onHTTPConnect
+const { INT4OID, VARCHAROID } = constants.fieldTypes
+const clients = []
+const tfb = {
+  hostname: 'tfb-database',
+  port: 5432,
+  user: 'benchmarkdbuser',
+  pass: 'benchmarkdbpass',
+  database: 'hello_world'
+}
+const stats = start()
+let i = poolSize
+const sJSON = sjs({ message: attr('string') })
+const sDB = sjs({ id: attr('number'), randomNumber: attr('number') })
+const sQUERY = sjs(attr('array', sjs({ id: attr('number'), randomNumber: attr('number') })))
+while (i--) connect(tfb, onPGConnect)
+const { loop } = just.factory
+let microtasks = true
+let rHTML = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: `
+let rTEXT = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: `
+let rJSON = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: application/json\r\nContent-Length: `
+let r404 = `HTTP/1.1 404 Not Found\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r\n`
+just.setInterval(() => {
+  rHTML = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: `
+  rTEXT = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: `
+  rJSON = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: application/json\r\nContent-Length: `
+  r404 = `HTTP/1.1 404 Not Found\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r\n`
+}, 100)
+while (1) {
+  loop.poll(10)
+  if (microtasks) just.sys.runMicroTasks()
+}