A simple web server that can be developed online using typescript/javascript.
-
Clone the repository.
-
Build from the source code.
make build
Or build without CDN and compress with UPX:
make build CDN=0 UPX=1
-
Start the server.
./cube -n 256
Or you can start directly from the source code:
make run
For more startup parameters, please refer to:
./cube --help
-
Open
http://127.0.0.1:8090/
in browser.
-
Ensure that
ca.key
,ca.crt
,server.key
andserver.crt
have been created:make crt
-
Start the server:
./cube \ -n 256 \ # using 256 virtual machines -p 8443 \ # server with port 8443 -s \ # enable SSL/TLS -v # enable client cert verification
-
If you are using a self-signed certificate, you can install the
ca.crt
to the local root certificate library.rem Ensure that ca.crt is installed into the Trusted Root Certification Authorities certificate store certutil -addstore root ca.crt
-
Open
https://127.0.0.1:8443/
in browser. -
You can run your service with client certificate using curl:
# Create client.key and client.crt make ccrt # Run the service with client.crt and ca.crt curl --cacert ./ca.crt --cert ./client.crt --key ./client.key https://127.0.0.1:8443/service/foo
Or you can access it in chrome:
rem Parse client.crt and client.key into client.p12 openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -passout pass:123456 rem Install client.p12 into My certificate store certutil -importPFX -f -p 123456 My client.p12 rem Open https://127.0.0.1:8443/ and select your client certificate chrome https://127.0.0.1:8443/
-
Ensure that
ca.key
,ca.crt
,server.key
andserver.crt
have been created:make crt
-
Start the server:
./cube \ -n 256 \ # using 256 virtual machines -p 8443 \ # server with port 8443 -s \ # enable SSL/TLS -3 # enable HTTP/3
-
You can test your service using curl:
curl --http3 -I https://127.0.0.1:8443/service/foo
Or you can access it in chrome with quic enabled:
rem Ensure that all running chrome processes are terminated taskkill /f /t /im chrome.exe rem Restart chrome with quic enabled and open https://127.0.0.1:8443/ chrome --enable-quic --origin-to-force-quic-on=127.0.0.1:8443 https://127.0.0.1:8443/
You can create a controller as a http/https service.
-
A simple controller
export default function (ctx: ServiceContext): ServiceResponse | Uint8Array | any { return "hello, world" }
-
Get request parameters
- Create a controller with name
greeting
, typecontroller
and url/service/{name}/greeting/{words}
.export default function (ctx: ServiceContext) { // get http request body String.fromCharCode(...ctx.getBody()) // get variables in path ctx.getPathVariables() // {"name":"zhangsan","words":"hello"} // get request form ctx.getForm() // {"a":["1","3"],"b":["2"],"c":[""],"d":["4","6"],"e":["5"],"f":[""]} // get request url path and params ctx.getURL() // {"params":{"a":["1","3"],"b":["2"],"c":[""]},"path":"/service/foo"} }
- You can test it using curl:
curl -XPOST -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8090/service/zhangsan/greeting/hello?a=1&b=2&c&a=3" -d "d=4&e=5&f&d=6"
- Create a controller with name
-
Return a custom response
export default function (ctx: ServiceContext): ServiceResponse { // return new Uint8Array([104, 101, 108, 108, 111]) // response with body "hello" return new ServiceResponse(500, { "Content-Type": "text/plain", }, new Uint8Array([104, 101, 108, 108, 111])) }
-
Websocket server
export default function (ctx: ServiceContext) { const ws = ctx.upgradeToWebSocket() // upgrade http and get a websocket console.info(ws.read()) // read a message ws.send("hello, world") // write a message ws.close() // close the connection }
-
Http chunk
- Create a controller with name
foo
, typecontroller
and url/service/foo
.export default function (ctx: ServiceContext) { ctx.write("hello, chunk 0") ctx.flush() ctx.write("hello, chunk 1") ctx.flush() ctx.write("hello, chunk 2") ctx.flush() }
- You can test it using telnet:
{ echo "GET /service/foo HTTP/1.1"; echo "Host: 127.0.0.1"; echo ""; sleep 1; echo exit; } | telnet 127.0.0.1 8090
- Create a controller with name
-
Read byte(s) from request body or read chunks from a chunked request
export default function (ctx: ServiceContext) { const reader = ctx.getReader() // String.fromCharCode(...reader.read(10)) // Read 10 bytes from request body as a Uint8Array. Return null if got EOF. const arr = [] let byte = reader.readByte() while (byte != -1) { // Return -1 if got EOF arr.push(byte) byte = reader.readByte() } console.debug(String.fromCharCode(...arr)) }
A module can be imported in the controller.
-
A custom module
export const user = { name: "zhangsan" }
import { user } from "./user" export default function (ctx: ServiceContext) { return `hello, ${user?.name ?? "world"}` }
The daemon is a backend running service with no timeout limit.
- Create a daemon
export default function () { const b = $native("pipe")("default") while (true) { console.info(b.drain(100, 5000)) } }
Here are some built-in methods and modules.
-
Buffer
const buf = Buffer.from("hello", "utf8") buf // [104, 101, 108, 108, 111] buf.toString("base64") // aGVsbG8= String.fromCharCode(...buf) // hello
-
Console
// ... console.error("this is a error message")
-
Date
Date.toDate("2006-01-02 15:04:05.012", "yyyy-MM-dd HH:mm:ss.SSS") .toString("yyyyMMddHHmmssSSS") // "20060102150405012"
-
Decimal
const d1 = new Decimal("0.1"), d2 = new Decimal("0.2") d2.add(d1) // 0.3 d2.sub(d1) // 0.1 d2.mul(d1) // 0.02 d2.div(d1) // 2
-
Error
// ... throw new Error("error message") // ... throw { code: "error code", message: "error message" }
-
Bqueue & Pipe
const b = $native("pipe")("mypipe") // const b = $native("bqueue")(99) b.put(1) b.put(2) b.drain(4, 2000) // [1, 2]
-
Db
$native("db").query("select name from script") // [{"name":"foo"}, {"name":"user"}]
-
Email
const emailc = $native("email")("smtp.163.com", 465, username, password) emailc.send(["[email protected]"], "greeting", "hello, world") emailc.send(["[email protected]"], "greeting", "hello, world", [{ name: "hello.txt", contentType: "text/plain", base64: "aGVsbG8=", }])
-
Crypto
const cryptoc = $native("crypto") // hash cryptoc.createHash("md5").sum("hello, world").map(c => c.toString(16).padStart(2, "0")).join("") // "e4d7f1b4ed2e42d15898f4b27b019da4" // hmac cryptoc.createHmac("sha1").sum("hello, world", "123456").toString("hex") // "9a231f1dd39a4ff6ea778a5640d1498794f8a9f8" // rsa // privateKey and publicKey mentioned is PKCS#1 format const rsa = cryptoc.createRsa(), { privateKey, publicKey } = rsa.generateKey(); rsa.decrypt( rsa.encrypt("hello, world", publicKey), privateKey, ).toString() // "hello, world" rsa.verify( "hello, world", rsa.sign("hello, world", privateKey, "sha256", "pss"), publicKey, "sha256", "pss", ) // true
-
File
const filec = $native("file") filec.write("greeting.txt", "hello, world") String.fromCharCode(...filec.read("greeting.txt")) // "hello, world"
-
Http
const httpc = $native("http")({ // caCert: "", // ca certificates for http client // cert: "", key: "", // private key and certificate/public key for http client auth // insecureSkipVerify: true, // disable verify server certificate // proxy: "http://127.0.0.1:5566", // proxy server }) const { status, header, data } = httpc.request("GET", "https://www.baidu.com") status // 200 header // { "Content-Length": "227", "Content-Type": "text/html", ... } data.toString() // "<html>..."
-
Image
const imagec = $native("image"), filec = $native("file") const img = imagec.parse(filec.read("input.jpg")), text = "hello, world", textHeight = 28, textWidth = text.length * textHeight * 0.46, rotation = -30 img.setDrawFontFace(textHeight) img.setDrawColor([255, 255, 255, 80]) img.setDrawRotate(rotation) for (let i = 0, di = textWidth / Math.tan(Math.PI / 180 * rotation * -1), ic = img.width() / di; i < ic; i++) { for (let j = 0, dj = textWidth, jc = img.height() / dj; j < jc; j++) { img.drawString(text, i * di + 20, j * dj) } } img.drawString(text, img.width(), img.height() - textHeight, 1, 1) // write text in the bottom right corner of the image filec.write("output.jpg", img.resize(1280).toJPG())
-
Template
const content = $native("template")("greeting", { // read template greeting.tpl and render with input name: "this is name", })
-
Xml
// see https://github.com/antchfx/xpath for syntax const doc = $native("xml")(` <Users> <User> <ID>1</ID> <Name>zhangsan</Name> </User> <User> <ID>2</ID> <Name>lisi</Name> </User> </Users> `) doc.find("//user[id=2]/name").pop().innerText() // lisi doc.findOne("//user[1]/name").innerText() // zhangsan doc.findOne("//user[1]").findOne("name").innerText() // zhangsan
- Upload file
- Create a resource with lang
html
and url/resource/foo.html
.<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="//unpkg.com/element-ui/lib/theme-chalk/index.css"> </head> <body> <div id="app" v-cloak> <el-upload action="/service/foo" accept="image/jpeg" :auto-upload="true"> <el-button icon="el-icon-upload2">Upload</el-button> </el-upload> </div> </body> <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.7.14/vue.js"></script> <script src="//unpkg.com/element-ui"></script> <script> new Vue({ el: "#app" }) </script> </html>
- Create a controller with url
/service/foo
.export default function (ctx: ServiceContext) { const file = ctx.getFile("file"), hash = $native("crypto").md5(file.data).toString("hex") console.info(hash) }
- You can preview at
http://127.0.0.1:8090/resource/foo.html
. You can also run it using curl:# Upload a file curl -F "file=@./abc.txt; filename=abc.txt;" http://127.0.0.1:8090/service/foo
- Create a resource with lang