net/http
HTTP servers and clients: handlers, text and JSON responses, cookies and sending requests.
import net.http
- A JSON API in one file
- HTML forms, query strings and redirects
- Route groups and a login check
- Static files and server settings
- Running a real server
- Calling an API as a client
- Raw requests: your own headers, bytes and time limits
- POST JSON with your own headers
- Status codes and failures
- A small API client
- Chunked answers and time limits
- Binary downloads
- HTTPS
- Timeouts, limits and redirects
- Which function for which job
- Types
- Constants and variables
- Functions
This package has both halves of HTTP/1.1: a server for web pages and JSON APIs, and a client for calling other services. The server hands each request to a net/router router, which picks the handler by method and path. Every handler takes one i64, the context id of the request; you read the request and write the response with the context functions below, or turn the id into a Context struct with Ctx and call the same names on it.
The packages around it:
- net/http/middleware for access logs, CORS and cache headers, net/http/session for logins with a cookie, net/http/validate for checking input, and net/http/swagger for API documentation;
- net/http2 for HTTP/2, and net/websocket for connections that stay open.
Things people get wrong:
- Register every route before
Use. The server keeps a copy of the router. Post,PutandPatchlabel a body as a form (application/x-www-form-urlencoded) unless you pass aContent-Type; send JSON withPostWithand that header.- The client does not follow redirects unless you ask. A 302 comes back as a 302 with its
Locationheader; setmax_redirectsinRequestOptions(or callSetMaxRedirectson a client) to follow them. It never hangs or reads without limit: a read or write that is idle for 30 seconds fails, a response over 256 MB fails, andtimeout_mscaps the whole request. Okmeans a 2xx status. When the server could not be reached at all,Statusis 0 andErrorsays why.- A value read from a request is freed when the handler returns; see the end of the page.
The examples run the server with RunBackground on 127.0.0.1 and send requests to it from the same program.
A JSON API in one file
The most common job is a small JSON API: list some records, show one by its id, and create new ones. Register each route on the router with its handler, hand the router to the server with Use, and start it. Here the records are Salam structs: json.Marshal turns them into the response body, and json.UnmarshalLenient reads the posted body back into a struct, ignoring fields it does not know and leaving the missing ones at their defaults.
import net.http
import net.router
import encoding.json
import encoding.encerr
import conv
mut books := Vector {} as Vector<Book>
struct Book:
@json "id"
pub id: int = 0
@json "title"
pub title: str = ""
end
func list(ctx: i64):
http.Json(ctx, json.Marshal(books))
end
func show(ctx: i64):
id := conv.Atoi(http.Param(ctx, "id"))
each b in books:
if b.id == id:
http.Json(ctx, json.Marshal(b))
ret
end
end
http.SetStatus(ctx, 404)
http.Json(ctx, `{"error":"no such book"}`)
end
func create(ctx: i64):
mut b := Book {}
mut e := encerr.NoError()
if not json.UnmarshalLenient(http.Body(ctx), b, e):
http.SetStatus(ctx, 400)
http.Json(ctx, "{" + json.Member("error", e.message) + "}")
ret
end
b.id = books.len() + 1
books.push(b)
http.SetStatus(ctx, 201)
http.SetHeader(ctx, "Location", "/books/" + b.id)
http.Json(ctx, json.Marshal(b))
end
func main:
mut r := router.NewRouter()
router.Get(r, "/books", list)
router.Get(r, "/books/:id", show)
router.Post(r, "/books", create)
mut s := http.NewServer(19761)
http.Use(s, r)
_ := http.RunBackground(s)
base := "http://127.0.0.1:19761"
a := http.Post(base + "/books", `{"title": "The Little Prince"}`)
println http.Status(a), http.GetHeader(a, "location"), http.Body(a)
_ := http.Post(base + "/books", `{"title": "Rumi"}`)
println http.Body(http.Get(base + "/books"))
println http.Body(http.Get(base + "/books/2"))
c := http.Get(base + "/books/9")
println http.Status(c), http.Body(c)
d := http.Post(base + "/books", `{"title": 7}`)
println http.Status(d), http.Body(d)
endNothing is sent until the handler returns: the status, the headers and the body are collected and go out together, and a second body call replaces the first. That is why a handler returns right after it writes an error. A 201 with a Location header is the usual answer to a create, and a 404 with a short JSON error is easier for a client to handle than an empty page. Path parameters such as :id always arrive as text, so convert them with conv.Atoi before comparing.
HTML forms, query strings and redirects
A browser form arrives as application/x-www-form-urlencoded. Form reads one field and decodes it, so %40 becomes @ and + becomes a space. Query does the same for the query string. After a successful POST, answer with Redirect so that pressing reload in the browser does not send the form again.
import net.http
import net.router
import template
mut messages := Vector {} as Vector<str>
func board(ctx: i64):
mut html := "<ul>"
each m in messages:
html = html + "<li>" + template.EscapeHTML(m) + "</li>"
end
http.Html(ctx, html + "</ul>")
end
func post(ctx: i64):
name := http.Form(ctx, "name")
text := http.Form(ctx, "text")
if name == "" or text == "":
http.SetStatus(ctx, 400)
http.Text(ctx, "name and text are required")
ret
end
messages.push(name + ": " + text)
http.Redirect(ctx, "/board")
end
func search(ctx: i64):
word := http.Query(ctx, "q")
mut found := 0
each m in messages:
if m.find(word) >= 0:
found += 1
end
end
http.Text(ctx, "'" + word + "' found in " + found + " message(s)")
end
func main:
mut r := router.NewRouter()
router.Get(r, "/board", board)
router.Post(r, "/board", post)
router.Get(r, "/search", search)
mut s := http.NewServer(19762)
http.Use(s, r)
_ := http.RunBackground(s)
base := "http://127.0.0.1:19762"
mut form := http.NewHeaders()
form.insert("name", "Sara")
form.insert("text", "see you at 5 & bring tea")
mut headers := http.NewHeaders()
headers.insert("Content-Type", "application/x-www-form-urlencoded")
a := http.PostWith(base + "/board", http.EncodeForm(form), headers)
println http.Status(a), http.GetHeader(a, "location")
b := http.PostWith(base + "/board", "name=Reza", headers)
println http.Status(b), http.Body(b)
println http.Body(http.Get(base + "/board"))
mut q := http.NewHeaders()
q.insert("q", "bring tea")
println http.Body(http.Get(http.WithQuery(base + "/search", q)))
endAnything a user typed must be escaped before it goes into HTML; otherwise a message containing <script> runs in every visitor's browser. template.EscapeHTML does that, as the & in the output shows. On the client side, EncodeForm builds a form body from a map and WithQuery adds a query string to a URL, both with correct escaping.
Route groups and a login check
router.Group puts a route under a common prefix, which is how an API is versioned under /api/v1. There is no separate middleware chain: a check that several handlers share is a plain function that returns true when the request may go on, or writes the error response itself and returns false. Each handler calls it on its first line and returns when it fails.
import net.http
import net.router
func signed_in(ctx: i64): bool:
if http.Header(ctx, "Authorization") == "Bearer s3cret":
ret true
end
http.SetStatus(ctx, 401)
http.SetHeader(ctx, "WWW-Authenticate", "Bearer")
http.Json(ctx, `{"error":"sign in first"}`)
ret false
end
func health(ctx: i64):
http.Text(ctx, "ok")
end
func profile(ctx: i64):
if not signed_in(ctx): ret end
http.Json(ctx, `{"user":"maryam"}`)
end
func remove(ctx: i64):
if not signed_in(ctx): ret end
http.SetStatus(ctx, 204)
http.Text(ctx, "")
end
func main:
mut r := router.NewRouter()
router.Get(r, "/health", health)
router.Group(r, "/api/v1", "GET", "/profile", profile)
router.Group(r, "/api/v1", "DELETE", "/posts/:id", remove)
mut s := http.NewServer(19763)
http.Use(s, r)
_ := http.RunBackground(s)
base := "http://127.0.0.1:19763"
println http.Body(http.Get(base + "/health"))
a := http.Get(base + "/api/v1/profile")
println http.Status(a), http.GetHeader(a, "www-authenticate"), http.Body(a)
mut auth := http.NewHeaders()
auth.insert("Authorization", "Bearer s3cret")
println http.Body(http.GetWith(base + "/api/v1/profile", auth))
println http.Status(http.DeleteWith(base + "/api/v1/posts/3", auth))
println http.Status(http.Get(base + "/profile"))
endA route that is not registered answers 404 on its own, so /profile without the prefix is not found. For a cookie based login instead of a token, see net/http/session.
Static files and server settings
router.Static serves a whole directory under a prefix, with the content type taken from the file extension. A path that tries to climb out of the directory with .. is refused, and a missing file is a 404. HideTokens drops the version number from the Server header, so the answer says only Salam.
import net.http
import net.router
import os
func main:
_ := os.MkdirAll("public/css")
_ := os.WriteFile("public/index.html", "<h1>Welcome</h1>")
_ := os.WriteFile("public/css/site.css", "body { margin: 0 }")
mut r := router.NewRouter()
router.Static(r, "/site", "public")
mut s := http.NewServer(19764)
http.HideTokens(s)
http.Use(s, r)
_ := http.RunBackground(s)
base := "http://127.0.0.1:19764"
a := http.Get(base + "/site/index.html")
println http.Status(a), http.GetHeader(a, "content-type"), http.Body(a)
b := http.Get(base + "/site/css/site.css")
println http.GetHeader(b, "content-type"), http.Body(b)
println http.Status(http.Get(base + "/site/missing.png"))
println http.Status(http.Get(base + "/site/../secret.txt"))
println http.GetHeader(a, "server")
endRunning a real server
The examples above use RunBackground so that one program can be both the server and its own client. A real service calls Run, which never returns. Read the port from the environment, listen on 0.0.0.0 when the server sits in a container or behind a proxy, and turn on the access log while you are getting it running.
import net.http
import net.router
import os
import conv
func home(ctx: i64):
http.Html(ctx, "<h1>Salam</h1>")
end
func health(ctx: i64):
http.Json(ctx, `{"status":"ok"}`)
end
func main:
mut port := 8080
if os.Env("PORT") != "":
port = conv.Atoi(os.Env("PORT"))
end
http.SetMaxBodyBytes(1048576)
http.SetKeepAliveIdle(5000)
mut r := router.NewRouter()
router.Get(r, "/", home)
router.Get(r, "/health", health)
router.Static(r, "/assets", "public")
mut s := http.NewServer(port)
http.SetHost(s, "0.0.0.0")
http.HideTokens(s)
http.EnableAccessLog(s)
http.Use(s, r)
println "listening on port " + port
http.Run(s)
endRun it and try it from a second terminal:
PORT=8080 salam run server.salam
curl http://127.0.0.1:8080/health
The limits are for the whole process: SetMaxBodyBytes and SetMaxHeaderBytes reject requests that are too large, SetKeepAliveIdle closes idle connections, and SetKeepAliveMaxRequests caps how many requests one connection may send. For HTTPS, call RunTLS with a certificate and key file instead of Run, or put the server behind a proxy that handles TLS.
Calling an API as a client
Get, Post and the other one-line calls send a request and return a Response; the ...With forms take a map of extra headers. The program below shows the four answers a client has to handle. A Post without a content type reaches the server as a form, so a JSON API refuses it with 415; the same body through PostWith with Content-Type: application/json creates the ticket. A redirect is not followed, so Get returns the 302 and you read Location yourself. A server that is not running gives status 0 and the reason in Error.
import net.http
import net.router
import encoding.json
func create_ticket(ctx: i64):
kind := http.Header(ctx, "Content-Type")
if kind != "application/json":
http.SetStatus(ctx, 415)
http.Json(ctx, "{\"error\":\"expected JSON, got " + kind + "\"}")
ret
end
title := json.Get(http.Body(ctx), "title")
http.SetStatus(ctx, 201)
http.Json(ctx, "{\"id\":31,\"title\":" + json.Str(title) + "}")
end
func old_tickets(ctx: i64): http.Redirect(ctx, "/v2/tickets") end
func main:
mut r := router.NewRouter()
router.Post(r, "/tickets", create_ticket)
router.Get(r, "/tickets", old_tickets)
mut s := http.NewServer(18631)
http.SetHost(s, "127.0.0.1")
http.Use(s, r)
if not http.RunBackground(s):
println "port 18631 is busy"
ret 1
end
base := "http://127.0.0.1:18631"
body := `{"title": "Printer is out of paper"}`
plain := http.Post(base + "/tickets", body)
println "Post:", http.Status(plain), http.Body(plain)
mut headers := http.NewHeaders()
headers.insert("Content-Type", "application/json")
made := http.PostWith(base + "/tickets", body, headers)
println "PostWith:", http.Status(made), http.Ok(made), "id", json.GetInt(http.Body(made), "id")
moved := http.Get(base + "/tickets")
println "Get:", http.Status(moved), "Location:", http.GetHeader(moved, "location")
down := http.Get("http://127.0.0.1:1/tickets")
println "dead server:", http.Status(down), http.Ok(down), http.Error(down)
ret 0
endGzip and chunked answers are decoded for you, and every request is sent with Connection: close. For a series of calls that share a login, NewClient keeps the cookies from each answer and sends them with the next request, plus any headers set with SetHeader. A cookie is sent back only to the host that set it; a cookie you add with SetCookie goes to the host of the first request, never to a host a redirect leads to.
Any number of threads can use the one-line calls at once, because a call keeps no state outside itself. A Client from NewClient can be shared by threads too: it carries its own lock, so SetHeader, the cookie jar and the settings stay consistent. Call Free when you are done with a shared client. A Client {} written as a literal has no lock and belongs to one thread.
Raw requests: your own headers, bytes and time limits
The one-line calls above build the request for you. When you need full control of the request line and headers, for example to sign a request, to send a binary body or to talk to an API with unusual headers, write the request head yourself and send it with SendRaw. The aws, webpush, db/meilisearch, db/elasticsearch and db/typesense packages are built on these calls. The examples run against a small server in a thread of the same program, so they need no network.
Every raw call takes an Endpoint from ParseEndpoint (host, port and TLS on or off; any path in the URL is dropped, because the path goes in your request line) and a request head: the request line, the headers and an empty line, each line ending in \r\n. The body is a pointer and a length, so it can hold any bytes. The answer is a RawResponse with the body as bytes, chunked bodies already decoded, freed with FreeRaw. SendRaw waits as long as the server does; SendRawTimeout adds a time limit in milliseconds; SendRawTLS adds your own tls.Config. A response larger than RawLimit (256 MB by default) is reported as an error.
Things that catch people out:
- Send
Connection: closeunless the answer is framed. The raw calls stop as soon as an answer with aContent-Lengthor a chunked body is complete. An answer with neither is read until the server closes the connection, so on a kept-alive connection the call waits until then (or until the time limit). okmeans a 2xx status, not "an answer arrived". Checkstatusto tell a 404 from a dead server.- A
strstops at the first zero byte, so binary bodies stay indata;RawTextcuts at the first zero.
POST JSON with your own headers
ParseEndpoint turns a URL into the host, port and whether to use TLS. The request head is plain text that you write: the request line, a Host header, and here an Authorization token, the content type and the body length, closed by an empty line. SendRawTimeout sends the head and the body and returns the answer; RawText turns the reply body into a str. HeaderValue finds a header in the response head, ignoring case.
import net.http
import net.tcp
import encoding.json
mut server := tcp.Server {}
func read_request(conn: tcp.Conn): str:
mut got := tcp.Read(conn, 4096)
until got.find("\r\n\r\n") < 0:
got += tcp.Read(conn, 4096)
end
head_end := got.find("\r\n\r\n") + 4
want := http.HeaderValue(got, "content-length").to_int()
until got.len() < head_end + want:
got += tcp.Read(conn, 4096)
end
ret got
end
func api():
conn := tcp.Accept(server)
request := read_request(conn)
at := request.find("\r\n\r\n") + 4
body := request.substr(at, request.len() - at)
name := json.Get(body, "name")
auth := http.HeaderValue(request, "authorization")
reply := "{\"id\": 7, \"name\": \"" + name + "\", \"auth\": \"" + auth + "\"}"
head := "HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nLocation: /users/7\r\nContent-Length: " + reply.len() + "\r\nConnection: close\r\n\r\n"
_ := tcp.Write(conn, head + reply)
tcp.Close(conn)
end
func main:
server = tcp.Bind(0)
t := spawn(api)
ep := http.ParseEndpoint("http://127.0.0.1:" + tcp.Port(server))
body := `{"name": "Sara", "role": "editor"}`
mut head := "POST /users HTTP/1.1\r\n"
head += "Host: 127.0.0.1\r\n"
head += "Authorization: Bearer abc123\r\n"
head += "Content-Type: application/json\r\n"
head += "Content-Length: " + body.len() + "\r\n"
head += "Connection: close\r\n\r\n"
mut r := http.SendRawTimeout(ep, head, body as void*, body.len(), 5000)
reply := http.RawText(r)
http.FreeRaw(r)
join(t)
tcp.CloseServer(server)
println "status:", r.status, r.ok
println "location:", http.HeaderValue(r.head, "Location")
println "id:", json.GetInt(reply, "id")
println "body:", reply
endContent-Length must be the length in bytes, which is what str.len() returns, so it stays right for text in any language. A wrong length makes servers wait for more data or cut the body.
Status codes and failures
A RawResponse tells three cases apart. ok is true only for a 2xx status. A 4xx or 5xx answer still has its status and a body, which usually explain the problem. When no answer came at all, because the connection was refused, the host was unknown or the URL could not be parsed, status is 0 and error says why.
import net.http
import net.tcp
mut server := tcp.Server {}
func api():
repeat 2:
conn := tcp.Accept(server)
request := tcp.Read(conn, 4096)
if request.find("GET /missing ") == 0:
tcp.WriteHTTP(conn, "404 Not Found", "application/json", `{"error": "no such page"}`)
else:
tcp.WriteHTTP(conn, "503 Service Unavailable", "text/plain", "try again later")
end
tcp.Close(conn)
end
end
func get(ep: http.Endpoint, path: str):
head := "GET " + path + " HTTP/1.1\r\nHost: " + ep.host + "\r\nConnection: close\r\n\r\n"
mut r := http.SendRawTimeout(ep, head, null, 0, 5000)
body := http.RawText(r)
if r.ok:
println path, "worked:", body
else r.status > 0:
println path, "answered", r.status, "with", body
else:
println path, "failed:", r.error
end
http.FreeRaw(r)
end
func main:
server = tcp.Bind(0)
t := spawn(api)
ep := http.ParseEndpoint("http://127.0.0.1:" + tcp.Port(server))
get(ep, "/missing")
get(ep, "/busy")
join(t)
tcp.CloseServer(server)
get(http.ParseEndpoint("http://127.0.0.1:1"), "/nobody-listens")
bad := http.ParseEndpoint("ftp://files.example.com")
println "ftp endpoint ok:", bad.ok
get(bad, "/")
endA 503 or 429 often means "try again later"; read Retry-After with HeaderValue before you retry, and do not retry a 4xx such as 404, which will not change.
A small API client
A struct that holds the endpoint and the token, with one method per kind of request, keeps the header writing in one place. The JSON in the replies is read with encoding/json: json.Get for a field and json.Items to walk an array.
import net.http
import net.tcp
import encoding.json
mut server := tcp.Server {}
struct Api:
pub ep: http.Endpoint = http.Endpoint {}
pub token: str = ""
pub func get(path: str): str:
mut head := "GET " + path + " HTTP/1.1\r\n"
head += "Host: " + this.ep.host + "\r\n"
head += "Authorization: Bearer " + this.token + "\r\n"
head += "Accept: application/json\r\n"
head += "Connection: close\r\n\r\n"
mut r := http.SendRawTimeout(this.ep, head, null, 0, 5000)
defer http.FreeRaw(r)
ret http.RawText(r)
end
end
func backend():
repeat 2:
conn := tcp.Accept(server)
request := tcp.Read(conn, 4096)
if request.find("GET /users/7 ") == 0:
tcp.WriteHTTP(conn, "200 OK", "application/json", `{"id": 7, "name": "Sara", "city": "Shiraz"}`)
else:
tcp.WriteHTTP(conn, "200 OK", "application/json", `[{"item": "pen", "qty": 3}, {"item": "notebook", "qty": 1}]`)
end
tcp.Close(conn)
end
end
func main:
server = tcp.Bind(0)
t := spawn(backend)
api := Api { ep = http.ParseEndpoint("http://127.0.0.1:" + tcp.Port(server)), token = "abc123" }
user := api.get("/users/7")
println json.Get(user, "name"), "from", json.Get(user, "city")
orders := api.get("/users/7/orders")
each order in json.Items(orders):
println " ", json.GetInt(order, "qty"), "x", json.Get(order, "item")
end
join(t)
tcp.CloseServer(server)
endChunked answers and time limits
A server that does not know the length of its answer in advance sends it in chunks, each one prefixed by its size in hex. SendRawTimeout removes that framing for you. The last parameter of SendRawTimeout is a time limit in milliseconds for each read and write, and on Linux also for the connect, so a server that accepts the connection and then says nothing cannot hang the program. The second request below hits such a server with a 300 ms limit.
import net.http
import net.tcp
mut server := tcp.Server {}
func feed():
repeat 2 in i:
conn := tcp.Accept(server)
_ := tcp.Read(conn, 4096)
if i < 1:
_ := tcp.Write(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nnews: \r\n9\r\nall quiet\r\n0\r\n\r\n")
else:
_ := tcp.Read(conn, 4096)
end
tcp.Close(conn)
end
end
func main:
server = tcp.Bind(0)
t := spawn(feed)
ep := http.ParseEndpoint("http://127.0.0.1:" + tcp.Port(server))
head := "GET /feed HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"
mut raw := http.SendRawTimeout(ep, head, null, 0, 2000)
println "body:", http.RawText(raw)
http.FreeRaw(raw)
mut slow := http.SendRawTimeout(ep, head, null, 0, 300)
println "silent server:", slow.ok, slow.status, slow.error
http.FreeRaw(slow)
join(t)
tcp.CloseServer(server)
endA timeout is reported as the request timed out and an answer cut short by a closing connection as the response body was cut short; both are worth a retry later. SendRaw has no time limit at all; give SendRawTimeout a limit for any server you do not control. SendRawTLS takes the same limit plus a tls.Config for a private CA.
Binary downloads
A str ends at the first zero byte, so an image or a zip file turned into one is cut short. Read it from the RawResponse instead: data points at the body bytes and len is their count. The program downloads twelve bytes of a PNG header and shows that RawText, which turns the body into a str, keeps only the eight bytes before the first zero.
import net.http
import net.tcp
import mem
import conv
mut server := tcp.Server {}
func files():
conn := tcp.Accept(server)
_ := tcp.Read(conn, 4096)
image := [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13]
buf := mem.Allocate(12 as u64) as u8*
repeat 12 in i:
buf[i] = image[i] as u8
end
_ := tcp.Write(conn, "HTTP/1.1 200 OK\r\nContent-Type: image/png\r\nContent-Length: 12\r\nConnection: close\r\n\r\n")
_ := tcp.WriteBytes(conn, buf as void*, 12)
mem.Free(buf as void*)
tcp.Close(conn)
end
func main:
server = tcp.Bind(0)
t := spawn(files)
ep := http.ParseEndpoint("http://127.0.0.1:" + tcp.Port(server))
head := "GET /logo.png HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"
mut r := http.SendRawTimeout(ep, head, null, 0, 5000)
join(t)
tcp.CloseServer(server)
println r.ok, r.status, http.HeaderValue(r.head, "content-type")
println "bytes received:", r.len
bytes := r.data as u8*
mut hex := ""
repeat r.len in i:
if i > 0: hex += " " end
if bytes[i] < 16: hex += "0" end
hex += conv.FormatHex(bytes[i] as u64)
end
println hex
println "is a PNG:", r.len >= 4 and bytes[1] as int == 80 and bytes[2] as int == 78 and bytes[3] as int == 71
println "RawText keeps", http.RawText(r).len(), "bytes"
http.FreeRaw(r)
endA RawResponse body is limited to RawLimit bytes, 256 MB unless you change it with SetRawLimit; a larger answer is reported as an error instead of filling memory. The data buffer belongs to you until FreeRaw.
HTTPS
An https:// URL gives an endpoint with secure set, and the same calls then speak TLS through tls, checking the server certificate. Nothing else changes. This example asks the GitHub API about the Salam repository, so it needs the internet; GitHub requires a User-Agent header.
import net.http
import encoding.json
func main:
ep := http.ParseEndpoint("https://api.github.com")
println ep.host, ep.port, ep.secure
mut head := "GET /repos/SalamLang/Salam HTTP/1.1\r\n"
head += "Host: api.github.com\r\n"
head += "User-Agent: salam-example\r\n"
head += "Accept: application/vnd.github+json\r\n"
head += "Connection: close\r\n\r\n"
mut r := http.SendRawTimeout(ep, head, null, 0, 10000)
defer http.FreeRaw(r)
body := http.RawText(r)
if r.status == 0:
println "request failed:", r.error
ret 1
end
println "status:", r.status
println "rate limit left:", http.HeaderValue(r.head, "x-ratelimit-remaining")
if r.ok:
println json.Get(body, "full_name"), "is written in", json.Get(body, "language")
end
ret 0
endWhen this page was written it printed:
api.github.com 443 true
status: 200
rate limit left: 58
SalamLang/Salam is written in TeX
The rate limit number counts down with each call from your address, and the language is what GitHub detects for the repository at the time.
Timeouts, limits and redirects
Client requests run under the limits below. RequestOptions holds the limits and Do sends a request with them; a Client keeps its own copy, changed with SetTimeout, SetIdleTimeout, SetMaxRedirects and SetMaxBody. idle_ms (30 seconds by default) is how long one read or write may stall, timeout_ms (off by default) is the time for the whole request including redirects, and max_body (256 MB by default) bounds the body, also after gzip is unpacked, so a small compressed answer cannot fill memory. Looking up a host name is done by the system resolver, and on systems other than Linux the connect itself waits the system default; neither is covered by these limits. A request that runs out of time reports the request timed out, and an answer cut short by a closing connection is an error rather than a smaller body.
Redirects are followed only when max_redirects is above zero. A 301 or 302 after a POST and every 303 turn into a GET without the body; 307 and 308 repeat the request as it was. When a redirect leads to another host, the Authorization and Cookie headers are dropped, and a redirect from https to http is refused. The final address is in the url field of the response.
The request is checked before anything is sent: a method, header name or address with a space or a control character, and a header value with a line break, are refused with a message in err, so a value taken from outside cannot add headers to the request. A Content-Length, Transfer-Encoding, Connection or Accept-Encoding header of yours is replaced by the one the client computes. The example sends a request to a server that answers late and one that never answers, follows a redirect and refuses an answer that is too large.
import net.http
import net.tcp
import sync
mut server := tcp.Server {}
func backend():
repeat 6:
conn := tcp.Accept(server)
request := tcp.Read(conn, 4096)
if request.find("GET /old ") == 0:
_ := tcp.Write(conn, "HTTP/1.1 302 Found\r\nLocation: /new\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
else request.find("GET /silent ") == 0:
sync.SleepMs(700)
else request.find("GET /big ") == 0:
tcp.WriteHTTP(conn, "200 OK", "text/plain", "0123456789")
else:
tcp.WriteHTTP(conn, "200 OK", "text/plain", "landed")
end
tcp.Close(conn)
end
end
func main:
server = tcp.Bind(0)
t := spawn(backend)
base := "http://127.0.0.1:" + tcp.Port(server)
mut c := http.NewClient()
c.SetMaxRedirects(3)
c.SetTimeout(5000)
c.SetIdleTimeout(2000)
c.SetMaxBody(1000)
r := c.Get(base + "/old")
println r.status, http.Body(r), r.url.find("/new") > 0
mut tight := http.RequestOptions {}
tight.max_body = 5
big := http.Do("GET", base + "/big", http.NewHeaders(), "", tight)
println big.status, big.err
mut quick := http.RequestOptions {}
quick.timeout_ms = 200
silent := http.Do("GET", base + "/silent", http.NewHeaders(), "", quick)
println silent.status, silent.err
raw := c.RequestBytes("GET", base + "/big", "", "")
println raw.status, raw.body.len
println c.RequestRaw("GET", base + "/new", "X-Id: 1\r\n", "").status
c.Free()
join(t)
tcp.CloseServer(server)
endWhich function for which job
| Job | Function |
|---|---|
| Read a path parameter, query value or form field | Param, Query, Form |
| Read the raw body or a request header | Body, Header |
| Answer with text, JSON or HTML | Text, Json, Html |
| Choose the status or add a header | SetStatus, SetHeader |
| Send the browser somewhere else | Redirect |
| Hold the request in a struct instead of the bare id | Ctx, then the same Param, Json and the rest |
| A request with a time limit, a size limit or redirects | Do with RequestOptions, or SetTimeout, SetMaxRedirects on a client |
| One request from a program | Get, Post, GetWith, PostWith, Request |
| Several requests that share cookies | NewClient |
| A request with headers you write yourself, or a binary body | ParseEndpoint, SendRaw, SendRawTimeout, SendRawTLS |
| Read the bytes of a raw answer | RawResponse data and len, RawText, then FreeRaw |
| Check the answer | Ok for a 2xx status, Status for the code (0 when nothing answered), Error for the reason |
A value you read from the request, such as Param or Form, belongs to that request and is freed when the handler returns. To keep it longer, in a global list or a session, store a copy made with str.Clone or a new string built with +.
Types
Endpoint
struct Endpoint| Name | Type | Description |
|---|---|---|
host | str = "" | The host. |
port | int = 443 | The port: the one in the URL, else 80 for http and 443 for https. |
secure | bool = true | Connect with TLS. |
ok | bool = false | The address was read correctly; false for anything that does not start with http:// or https://. |
import tls
import net.http
import net.tcp
mut server := tcp.Server {}
func responder():
repeat 3:
conn := tcp.Accept(server)
_ := tcp.Read(conn, 4096)
tcp.WriteHTTP(conn, "200 OK", "text/plain", "hello from the server")
tcp.Close(conn)
end
end
func main:
remote := http.ParseEndpoint("https://api.example.com:8443")
println remote.ok, remote.host, remote.port, remote.secure
server = tcp.Bind(0)
t := spawn(responder)
local := http.ParseEndpoint("http://127.0.0.1:" + tcp.Port(server))
head := "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"
mut r := http.SendRaw(local, head, null, 0)
println r.ok, r.status, http.RawText(r), "[" + r.error + "]"
println http.HeaderValue(r.head, "content-type")
http.FreeRaw(r)
mut raw := http.SendRawTimeout(local, head, null, 0, 2000)
println raw.ok, raw.status, raw.len, http.RawText(raw)
http.FreeRaw(raw)
mut cfg := tls.NewConfig("127.0.0.1")
cfg.ca_file = "/etc/ssl/certs/ca-certificates.crt"
mut secured := http.SendRawTLS(local, head, null, 0, 2000, cfg)
println secured.status, secured.len
http.FreeRaw(secured)
join(t)
tcp.CloseServer(server)
println http.RawLimit() > 0
http.SetRawLimit(1024 * 1024)
println http.RawLimit()
endContext
struct Context| Name | Type | Description |
|---|---|---|
h | i64 | The context id. |
import net.http
import net.router
func hello(h: i64):
ctx := http.Ctx(h)
name := http.Query(ctx, "name")
if http.HasCookie(ctx, "seen"):
http.Text(ctx, "hello again " + name + " (" + http.CookieValue(ctx, "seen") + ")")
else:
http.SetCookie(ctx, http.ExpiringCookie("seen", "1", 3600))
http.Text(ctx, "hello " + name + " (" + http.RawQuery(ctx) + ")")
end
end
func book(h: i64):
ctx := http.Ctx(h)
http.SetHeader(ctx, "X-Method", http.Method(ctx))
http.SetStatus(ctx, 202)
http.Json(ctx, `{"id": "` + http.Param(ctx, "id") + `", "path": "` + http.Path(ctx) + `"}`)
end
func form(h: i64):
ctx := http.Ctx(h)
http.Text(ctx, http.Form(ctx, "a") + "|" + http.Body(ctx) + "|" + http.Header(ctx, "Content-Type"))
end
func logout(h: i64):
ctx := http.Ctx(h)
http.ClearCookie(ctx, "seen")
http.Redirect(ctx, "/hello")
end
func page(h: i64):
ctx := http.Ctx(h)
cookies := http.Cookies(ctx)
http.Html(ctx, "<p>" + cookies.size() + "</p>")
end
func main:
mut r := router.NewRouter()
router.Get(r, "/hello", hello)
router.Get(r, "/books/:id", book)
router.Post(r, "/form", form)
router.Get(r, "/logout", logout)
router.Get(r, "/page", page)
mut s := http.NewServer(19622)
http.Use(s, r)
_ := http.RunBackground(s)
base := "http://127.0.0.1:19622"
mut c := http.NewClient()
println http.Body(c.Get(base + "/hello?name=Maryam"))
println http.Body(c.Get(base + "/hello?name=Maryam"))
println http.Body(c.Get(base + "/page"))
a := c.Get(base + "/books/7")
println http.Status(a), http.Body(a), http.GetHeader(a, "x-method")
c.SetHeader("Content-Type", "application/x-www-form-urlencoded")
println http.Body(c.Post(base + "/form", "a=1&b=2"))
b := c.Get(base + "/logout")
println http.Status(b), http.SetCookieLines(b).len() > 0
endServer
struct Server| Name | Type | Description |
|---|---|---|
port | int = 0 | The port. |
host | str = "0.0.0.0" | The listen address; 0.0.0.0 by default, meaning every address. |
router | router.Router | The router for the requests. |
hide_tokens | bool = false | Hide the server version in the responses. |
access_log | bool = false | Log every request to the output. |
import net.http
import net.router
import net.tcp
func hello(h: i64):
ctx := http.Ctx(h)
conn := http.ConnOf(ctx)
http.Text(ctx, "hello " + tcp.ConnOk(conn))
end
func main:
http.SetKeepAliveIdle(5000)
http.SetKeepAliveMaxRequests(100)
http.SetMaxHeaderBytes(16384)
http.SetMaxBodyBytes(1048576)
mut r := router.NewRouter()
router.Get(r, "/", hello)
mut s := http.NewServer(8080)
http.SetHost(s, "0.0.0.0")
http.HideTokens(s)
http.EnableAccessLog(s)
http.Use(s, r)
println http.SERVER_VERSION, http.IsEdge()
if not http.IsEdge():
http.RunTLS(s, "cert.pem", "key.pem")
end
http.Run(s)
endRawResponse
struct RawResponse| Name | Type | Description |
|---|---|---|
status | int = 0 | The status code. |
head | str = "" | The headers. |
data | void* | The address of the body. |
len | int = 0 | The length of the body in bytes, after chunked decoding. |
ok | bool = false | The status is 2xx. |
error | str = "" | The error message, such as the request timed out or the response body was cut short. |
The example for this item is shown with the ones above: net-http-SendRaw.salam
Cookie
struct Cookie| Name | Type | Description |
|---|---|---|
name | str = "" | The name. |
value | str = "" | The value. |
path | str = "/" | The path the cookie is sent for; / by default. |
domain | str = "" | The domain; empty means this host. |
max_age | int = 0 | The lifetime in seconds. |
has_max_age | bool = false | A lifetime was set; without one the cookie goes when the browser closes. |
secure | bool = false | Send it over HTTPS only. |
http_only | bool = true | Keep it out of reach of the page JavaScript; true by default. |
same_site | str = "Lax" | The SameSite policy; Lax by default. |
import net.http
func main:
c := http.NewCookie("theme", "dark")
println c.name, c.value, c.path, c.http_only, c.same_site, c.has_max_age
mut s := http.ExpiringCookie("sid", "x1", 3600)
s.secure = true
s.domain = "salamlang.ir"
println s.max_age, s.has_max_age, s.secure, s.domain
gone := http.ExpiredCookie("sid", "/", "salamlang.ir")
println gone.max_age, gone.has_max_age
println http.UrlDecode("a%20b+c")
endResponse
struct Response| Name | Type | Description |
|---|---|---|
status | int = 0 | The status code. |
body | str = "" | The body. |
head | str = "" | The raw headers. |
err | str = "" | The error message, when the request did not get through or was refused for a limit; the status is 0 when nothing came back. |
url | str = "" | The address of the last request, after redirects. |
import net.http
import net.router
func echo(ctx: i64):
http.SetHeader(ctx, "X-Tag", "a")
http.SetCookie(ctx, http.NewCookie("sid", "abc"))
http.Text(ctx, http.Method(ctx) + " " + http.Header(ctx, "X-Token") + " " + http.Body(ctx))
end
func cookies(ctx: i64):
all := http.Cookies(ctx)
http.Text(ctx, "" + all.size() + " " + http.CookieValue(ctx, "lang") + " " + http.HasCookie(ctx, "sid"))
http.ClearCookie(ctx, "old")
http.ClearCookieAt(ctx, "scoped", "/admin", "")
end
func main:
mut r := router.NewRouter()
each method in ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]:
router.Method(r, method, "/echo", echo)
end
router.Get(r, "/cookies", cookies)
mut s := http.NewServer(19623)
http.Use(s, r)
_ := http.RunBackground(s)
url := "http://127.0.0.1:19623/echo"
mut headers := http.NewHeaders()
headers.insert("X-Token", "t1")
a := http.GetWith(url, headers)
println http.Ok(a), http.Status(a), http.Body(a), "[" + http.Error(a) + "]"
println http.Headers(a).has("X-Tag"), http.HeaderValues(a, "set-cookie")
println http.CookieMap(a).get("sid"), http.RawHeaders(a).len() > 0, http.Raw(a).len() > 0
println http.Body(http.Post(url, "a")), http.Body(http.PutWith(url, "b", headers))
println http.Body(http.Delete(url)), http.Body(http.DeleteWith(url, headers))
println http.Body(http.Patch(url, "c")), http.Body(http.PatchWith(url, "d", headers))
println http.Body(http.Options(url)), http.Body(http.OptionsWith(url, headers))
println http.Status(http.Head(url)), http.Status(http.HeadWith(url, headers))
println http.Body(http.Put(url, "e")), http.Body(http.Get(url))
println http.Body(http.RequestRaw("POST", url, "X-Token: raw\r\n", "f"))
mut params := http.NewHeaders()
params.insert("q", "hello world")
println http.EncodeForm(params), http.WithQuery("http://x.ir/s", params)
mut jar := http.NewHeaders()
jar.insert("lang", "en")
jar.insert("sid", "abc")
headers.insert("Cookie", http.CookieHeader(jar))
b := http.GetWith("http://127.0.0.1:19623/cookies", headers)
println http.Body(b), http.SetCookieLines(b).len()
mut c := http.NewClient()
c.SetCookie("lang", "en")
println http.Body(c.Get("http://127.0.0.1:19623/cookies"))
c.ClearCookies()
mut extra := http.NewHeaders()
extra.insert("X-Token", "c1")
println http.Body(c.RequestWith("PUT", url, "g", extra)), http.Body(c.Request("PATCH", url, "h"))
println http.Body(c.Put(url, "i")), http.Body(c.Delete(url)), http.Body(c.Patch(url, "j")), http.Body(c.Options(url)), http.Status(c.Head(url))
bad := http.Get("http://127.0.0.1:1/")
println http.Ok(bad), http.Error(bad) != ""
endBytesResponse
struct BytesResponsestr would cut at the first zero byte.| Name | Type | Description |
|---|---|---|
status | int = 0 | The status code. |
head | str = "" | The raw headers. |
body | compress.ByteBuf | The body as a compress.ByteBuf; free it with compress.BufFree. |
err | str = "" | The error message, when the request did not get through; the status is then 0. |
url | str = "" | The address of the last request, after redirects. |
The example for this item is shown with the ones above: net-http-Client.salam
RequestOptions
struct RequestOptionsidle_ms for one stalled read or write (30000), timeout_ms for the whole request (0, none), max_body in bytes (256 MB) and max_redirects (0).| Name | Type | Description |
|---|---|---|
idle_ms | int = 30000 | How long one read or write may stall, in milliseconds. |
timeout_ms | int = 0 | The time for the whole request, redirects included; 0 means none. |
max_body | int = 268435456 | The largest body in bytes, measured after gzip is unpacked. |
max_redirects | int = 0 | How many redirects to follow; 0 follows none. |
import net.http
import net.tcp
import sync
mut server := tcp.Server {}
func backend():
repeat 6:
conn := tcp.Accept(server)
request := tcp.Read(conn, 4096)
if request.find("GET /old ") == 0:
_ := tcp.Write(conn, "HTTP/1.1 302 Found\r\nLocation: /new\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
else request.find("GET /silent ") == 0:
sync.SleepMs(700)
else request.find("GET /big ") == 0:
tcp.WriteHTTP(conn, "200 OK", "text/plain", "0123456789")
else:
tcp.WriteHTTP(conn, "200 OK", "text/plain", "landed")
end
tcp.Close(conn)
end
end
func main:
server = tcp.Bind(0)
t := spawn(backend)
base := "http://127.0.0.1:" + tcp.Port(server)
mut c := http.NewClient()
c.SetMaxRedirects(3)
c.SetTimeout(5000)
c.SetIdleTimeout(2000)
c.SetMaxBody(1000)
r := c.Get(base + "/old")
println r.status, http.Body(r), r.url.find("/new") > 0
mut tight := http.RequestOptions {}
tight.max_body = 5
big := http.Do("GET", base + "/big", http.NewHeaders(), "", tight)
println big.status, big.err
mut quick := http.RequestOptions {}
quick.timeout_ms = 200
silent := http.Do("GET", base + "/silent", http.NewHeaders(), "", quick)
println silent.status, silent.err
raw := c.RequestBytes("GET", base + "/big", "", "")
println raw.status, raw.body.len
println c.RequestRaw("GET", base + "/new", "X-Id: 1\r\n", "").status
c.Free()
join(t)
tcp.CloseServer(server)
endClient
struct Client| Name | Type | Description |
|---|---|---|
cookies | HashMap | The cookies it took from the responses; each is sent back only to the host that set it. |
headers | HashMap | The headers sent with every request. |
cookie_hosts | HashMap | Which host set each cookie in cookies. |
cookie_secure | HashMap | The cookies that came with the Secure attribute; they are sent back over HTTPS only, never over plain HTTP. |
options | RequestOptions | The limits of this client: a RequestOptions. |
mu | sync.Mutex | The lock that lets threads share a client made by NewClient. |
The example for this item is shown with the ones above: net-http-Context.salam
SetHeader
func SetHeader(name: str, value: str)The example for this item is shown with the ones above: net-http-Context.salam
SetCookie
func SetCookie(name: str, value: str)The example for this item is shown with the ones above: net-http-Client.salam
ClearCookies
func ClearCookies()The example for this item is shown with the ones above: net-http-Client.salam
SetTimeout
func SetTimeout(total_ms: int)The example for this item is shown with the ones above: net-http-Do.salam
SetIdleTimeout
func SetIdleTimeout(idle_ms: int)The example for this item is shown with the ones above: net-http-Do.salam
SetMaxRedirects
func SetMaxRedirects(n: int)The example for this item is shown with the ones above: net-http-Do.salam
SetMaxBody
func SetMaxBody(bytes: int)The example for this item is shown with the ones above: net-http-Do.salam
Free
func Free()The example for this item is shown with the ones above: net-http-Do.salam
Request
func Request(method: str, url: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
RequestRaw
func RequestRaw(method: str, url: str, header_block: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Do.salam
RequestBytes
func RequestBytes(method: str, url: str, header_block: str, body: str): BytesResponseRequestRaw, but keeps the body as bytes (a BytesResponse).The example for this item is shown with the ones above: net-http-Do.salam
RequestWith
func RequestWith(method: str, url: str, body: str, extra: HashMap<str, str>): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
Get
func Get(url: str): ResponseThe example for this item is shown with the ones above: net-http-Context.salam
Post
func Post(url: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Context.salam
Put
func Put(url: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
Delete
func Delete(url: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
Patch
func Patch(url: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
Options
func Options(url: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
Head
func Head(url: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
Constants and variables
SERVER_VERSION
const SERVER_VERSION: str = "0.6.0"Server header of the responses.The example for this item is shown with the ones above: net-http-Server.salam
Functions
UrlDecode
func UrlDecode(s: str): str+ into a space.The example for this item is shown with the ones above: net-http-Cookie.salam
ParseEndpoint
func ParseEndpoint(endpoint: str): Endpointhttps://api.example.com:8443; the default port is 80 for http and 443 for https. A path after the host is ignored.The example for this item is shown with the ones above: net-http-SendRaw.salam
HeaderValue
func HeaderValue(head: str, name: str): strThe example for this item is shown with the ones above: net-http-SendRaw.salam
Host
func Host(e: Endpoint): strEndpoint: api.example.com for https://api.example.com:8443/v1.import net.http
func main:
e := http.ParseEndpoint("https://api.example.com:8443/v1/users")
println http.EndpointOk(e), http.Host(e), http.Port(e), http.Secure(e)
plain := http.ParseEndpoint("http://example.com")
println http.EndpointOk(plain), http.Host(plain), http.Port(plain), http.Secure(plain)
println http.EndpointOk(http.ParseEndpoint("ftp://example.com"))
endPort
func Port(e: Endpoint): intEndpoint: the one in the address, else 80 for http and 443 for https.The example for this item is shown with the ones above: net-http-EndpointInfo.salam
Secure
func Secure(e: Endpoint): boolEndpoint uses TLS: true for https://, false for http://.The example for this item is shown with the ones above: net-http-EndpointInfo.salam
EndpointOk
func EndpointOk(e: Endpoint): boolParseEndpoint read the address; false for anything that does not start with http:// or https://.The example for this item is shown with the ones above: net-http-EndpointInfo.salam
SetKeepAliveIdle
func SetKeepAliveIdle(millis: int)The example for this item is shown with the ones above: net-http-Server.salam
SetMaxHeaderBytes
func SetMaxHeaderBytes(n: int)The example for this item is shown with the ones above: net-http-Server.salam
SetMaxBodyBytes
func SetMaxBodyBytes(n: int)The example for this item is shown with the ones above: net-http-Server.salam
SetKeepAliveMaxRequests
func SetKeepAliveMaxRequests(n: int)The example for this item is shown with the ones above: net-http-Server.salam
File
func File(h: i64, path: str, content_type: str): boolimport io
import os
import net.http
import net.router
func page(ctx: i64):
path := os.TempDir() + "/salam_filebody.txt"
if http.File(ctx, path, "text/plain"):
http.SetHeader(ctx, "X-Port", http.Own(ctx, "" + http.LocalPort(ctx)))
http.SetHeader(ctx, "X-Status", http.Own(ctx, "" + http.Status(ctx)))
else:
http.SetStatus(ctx, 404)
http.Text(ctx, "no file")
end
end
func part(ctx: i64):
path := os.TempDir() + "/salam_filebody.txt"
if http.FileRange(ctx, path, "text/plain", 6, 4):
http.SetStatus(ctx, 206)
else:
http.SetStatus(ctx, 416)
http.Text(ctx, "bad range")
end
end
func main:
path := os.TempDir() + "/salam_filebody.txt"
io.WriteFile(path, "hello from a file")
mut size := 0 as i64
mut mtime := 0 as i64
println http.FileStat(path, size, mtime), size
mut r := router.NewRouter()
router.Get(r, "/file", page)
router.Get(r, "/part", part)
mut s := http.NewServer(19622)
http.Use(s, r)
println http.RunBackground(s)
a := http.Get("http://127.0.0.1:19622/file")
println http.Status(a), http.Body(a)
println http.GetHeader(a, "x-port"), http.GetHeader(a, "x-status"), http.GetHeader(a, "content-type")
b := http.Get("http://127.0.0.1:19622/part")
println http.Status(b), http.Body(b)
os.Remove(path)
endLocalPort
func LocalPort(h: i64): intThe example for this item is shown with the ones above: net-http-FileBody.salam
Status
func Status(h: i64): intimport net.http
import net.router
func home(ctx: i64):
http.Text(ctx, "hello from the server")
end
func book(ctx: i64):
id := http.Param(ctx, "id")
format := http.Query(ctx, "format")
http.SetHeader(ctx, "X-Book", id)
http.Json(ctx, `{"id": "` + id + `", "format": "` + format + `", "raw": "` + http.RawQuery(ctx) + `"}`)
end
func create(ctx: i64):
name := http.Form(ctx, "name")
http.SetStatus(ctx, 201)
http.Text(ctx, http.Method(ctx) + " " + http.Path(ctx) + " " + name + " " + http.Header(ctx, "X-Client"))
end
func raw(ctx: i64):
answer := http.Own(ctx, "body: " + http.Body(ctx))
http.Text(ctx, answer)
end
func page(ctx: i64):
http.Html(ctx, "<h1>hello</h1>")
end
func old(ctx: i64):
http.Redirect(ctx, "/")
end
func main:
mut r := router.NewRouter()
router.Get(r, "/", home)
router.Get(r, "/books/:id", book)
router.Post(r, "/books", create)
router.Put(r, "/raw", raw)
router.Get(r, "/page", page)
router.Get(r, "/old", old)
mut s := http.NewServer(19621)
http.Use(s, r)
println http.RunBackground(s)
base := "http://127.0.0.1:19621"
a := http.Get(base + "/")
println http.Status(a), http.Body(a)
b := http.Get(base + "/books/42?format=pdf")
println http.Body(b), http.GetHeader(b, "x-book")
mut headers := http.NewHeaders()
headers.insert("Content-Type", "application/x-www-form-urlencoded")
headers.insert("X-Client", "salam")
c := http.PostWith(base + "/books", "name=Maryam", headers)
println http.Status(c), http.Body(c)
println http.Body(http.Put(base + "/raw", "data"))
d := http.Get(base + "/page")
println http.GetHeader(d, "content-type"), http.Body(d)
e := http.Request("GET", base + "/old", http.NewHeaders(), "")
println http.Status(e), http.GetHeader(e, "location")
println http.Status(http.Get(base + "/nope"))
endFileRange
func FileRange(h: i64, path: str, content_type: str, offset: int, length: int): boolFile but sends only length bytes starting at offset, for answers to a Range request; the caller sets the 206 status and the Content-Range header. It returns false when the part does not lie inside the file.The example for this item is shown with the ones above: net-http-FileBody.salam
FileStat
func FileStat(path: str, size &: i64, mtime &: i64): boolThe example for this item is shown with the ones above: net-http-FileBody.salam
Ctx
func Ctx(h: i64): ContextContext. Every function on this page takes either one, so this is a matter of taste: a struct to pass around and read fields from, or the bare id.The example for this item is shown with the ones above: net-http-Context.salam
Method
func Method(ctx: Context): strThe example for this item is shown with the ones above: net-http-Handle.salam
Path
func Path(ctx: Context): strThe example for this item is shown with the ones above: net-http-Handle.salam
Body
func Body(ctx: Context): strThe example for this item is shown with the ones above: net-http-Handle.salam
BodyLen
func BodyLen(ctx: Context): intBodyBytes.import mem
import str
import compress
import net.http
import net.mime
import net.router
func echo(ctx: i64):
n := http.BodyLen(ctx)
p := http.BodyBytes(ctx) as u8*
mut zeros := 0
repeat n in i:
if p[i] == 0 as u8: zeros += 1 end
end
http.SetHeader(ctx, "X-Zeros", http.Own(ctx, str.FromInt(zeros as i64)))
http.Bytes(ctx, "application/octet-stream", http.BodyBytes(ctx), n)
end
func upload(ctx: i64):
mut parts := http.Multipart(ctx)
defer mime.FreeMultipart(parts)
if parts.items.len() == 0:
http.SetStatus(ctx, 400)
http.Text(ctx, "not multipart")
ret
end
mut answer := ""
each part in parts.items:
answer = answer + part.name + ":" + part.filename + ":" + str.FromInt(part.body.len as i64) + " "
end
answer = answer + "note=" + mime.PartValue(parts, "note")
http.Text(ctx, http.Own(ctx, answer))
end
func main:
mut r := router.NewRouter()
router.Post(r, "/echo", echo)
router.Post(r, "/upload", upload)
mut s := http.NewServer(19645)
http.Use(s, r)
println http.RunBackground(s)
ep := http.ParseEndpoint("http://127.0.0.1:19645")
data := mem.Allocate(4 as u64)
bytes := data as u8*
bytes[0] = 83 as u8
bytes[1] = 0 as u8
bytes[2] = 255 as u8
bytes[3] = 0 as u8
head := "POST /echo HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 4\r\nConnection: close\r\n\r\n"
mut a := http.SendRawTimeout(ep, head, data, 4, 2000)
back := a.data as u8*
println a.status, a.len, http.HeaderValue(a.head, "x-zeros"), http.HeaderValue(a.head, "content-type")
println back[0] as int, back[1] as int, back[2] as int, back[3] as int
http.FreeRaw(a)
mut w := mime.NewMultipartWriter()
mime.WriteField(w, "note", "holiday")
mime.WriteFileBytes(w, "photo", "pic.bin", "image/png", data, 4)
mut body := mime.CloseWriter(w)
up := "POST /upload HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: " + mime.ContentTypeHeader(w) + "\r\nContent-Length: " + str.FromInt(body.len as i64) + "\r\nConnection: close\r\n\r\n"
mut b := http.SendRawTimeout(ep, up, body.data, body.len, 2000)
println b.status, http.RawText(b)
http.FreeRaw(b)
compress.BufFree(body)
mem.Free(data)
plain := http.Post("http://127.0.0.1:19645/upload", "note=x")
println http.Status(plain), http.Body(plain)
endBodyBytes
func BodyBytes(ctx: Context): void*Body would cut at the first zero byte. It belongs to the request and is freed when the handler returns; copy what you keep, and do not free it.The example for this item is shown with the ones above: net-http-Upload.salam
Multipart
func Multipart(ctx: Context): mime.MultipartPartsmultipart/form-data body into its parts, each with its name, file name, content type and bytes; no parts when the request is not multipart. The caller owns the result and frees it with mime.FreeMultipart.The example for this item is shown with the ones above: net-http-Upload.salam
Bytes
func Bytes(ctx: Context, content_type: str, p: void*, n: int)n bytes from a pointer as the body, with the given content type; binary safe, for files, images and archives. The bytes are copied, so the call takes nothing over: free the pointer afterwards only if you allocated it, and never free one the request owns, such as the one BodyBytes returns.The example for this item is shown with the ones above: net-http-Upload.salam
RawQuery
func RawQuery(ctx: Context): str?, exactly as it came and undecoded; for signing a request or passing it on to another service.The example for this item is shown with the ones above: net-http-Handle.salam
Query
func Query(ctx: Context, key: str): strThe example for this item is shown with the ones above: net-http-Handle.salam
Header
func Header(ctx: Context, name: str): strThe example for this item is shown with the ones above: net-http-Handle.salam
Param
func Param(ctx: Context, name: str): strid in /books/:id.The example for this item is shown with the ones above: net-http-Handle.salam
Form
func Form(ctx: Context, field: str): strapplication/x-www-form-urlencoded body.The example for this item is shown with the ones above: net-http-Handle.salam
SetStatus
func SetStatus(ctx: Context, code: int)The example for this item is shown with the ones above: net-http-Handle.salam
SetHeader
func SetHeader(ctx: Context, k: str, v: str)cache-control adds a second header.The example for this item is shown with the ones above: net-http-Handle.salam
AddHeader
func AddHeader(ctx: Context, k: str, v: str)Set-Cookie headers.import net.http
import net.router
import time
func show(ctx: i64):
http.AddHeader(ctx, "X-Test", "one")
http.AddHeader(ctx, "X-Test", "two")
http.AddHeader(ctx, "Set-Cookie", "a=1; Path=/")
http.AddHeader(ctx, "Set-Cookie", "b=2; Path=/")
http.Text(ctx, "ok")
end
func main:
mut r := router.NewRouter()
router.Get(r, "/", show)
mut s := http.NewServer(19521)
http.SetHost(s, "127.0.0.1")
http.Use(s, r)
if not http.RunBackground(s):
println "cannot listen"
ret 1
end
time.Sleep(300)
resp := http.Get("http://127.0.0.1:19521/")
println resp.status
each v in http.HeaderValues(resp, "X-Test"):
println v
end
each c in http.HeaderValues(resp, "Set-Cookie"):
println c
end
ret 0
endHtml
func Html(ctx: Context, html: str)The example for this item is shown with the ones above: net-http-Handle.salam
Json
func Json(ctx: Context, json: str)The example for this item is shown with the ones above: net-http-Handle.salam
Text
func Text(ctx: Context, text: str)The example for this item is shown with the ones above: net-http-Handle.salam
Redirect
func Redirect(ctx: Context, url: str)The example for this item is shown with the ones above: net-http-Handle.salam
Cookies
func Cookies(ctx: Context): HashMap<str, str>The example for this item is shown with the ones above: net-http-Context.salam
CookieValue
func CookieValue(ctx: Context, name: str): strThe example for this item is shown with the ones above: net-http-Context.salam
HasCookie
func HasCookie(ctx: Context, name: str): boolThe example for this item is shown with the ones above: net-http-Context.salam
SetCookie
func SetCookie(ctx: Context, c: Cookie)The example for this item is shown with the ones above: net-http-Context.salam
ClearCookie
func ClearCookie(ctx: Context, name: str)The example for this item is shown with the ones above: net-http-Context.salam
ConnOf
func ConnOf(ctx: Context): tcp.ConnThe example for this item is shown with the ones above: net-http-Server.salam
SendRaw
func SendRaw(ep: Endpoint, head: str, body: void*, n: int): RawResponsen bytes of body from a pointer, reads until the server closes, keeps the body binary and decodes a chunked body. No time limit.The example for this item is shown with the ones above: net-http-SendRaw.salam
SendRawTimeout
func SendRawTimeout(ep: Endpoint, head: str, body: void*, n: int, timeout_ms: int): RawResponseThe example for this item is shown with the ones above: net-http-SendRaw.salam
SendRawTLS
func SendRawTLS(ep: Endpoint, head: str, body: void*, n: int, timeout_ms: int, cfg: tls.Config): RawResponseSendRawTimeout, with your own tls.Config: a private CA in ca_file, or insecure_skip_verify for local testing. A plain http endpoint ignores it.The example for this item is shown with the ones above: net-http-SendRaw.salam
FreeRaw
func FreeRaw(r &: RawResponse)The example for this item is shown with the ones above: net-http-SendRaw.salam
RawText
func RawText(r: RawResponse): strThe example for this item is shown with the ones above: net-http-SendRaw.salam
RawLimit
func RawLimit(): intThe example for this item is shown with the ones above: net-http-SendRaw.salam
SetRawLimit
func SetRawLimit(bytes: int)The example for this item is shown with the ones above: net-http-SendRaw.salam
NewServer
func NewServer(port: int): ServerThe example for this item is shown with the ones above: net-http-Handle.salam
Use
func Use(s &: Server, r: router.Router)The example for this item is shown with the ones above: net-http-Handle.salam
HideTokens
func HideTokens(s &: Server)The example for this item is shown with the ones above: net-http-Server.salam
SetHost
func SetHost(s &: Server, host: str)127.0.0.1, for instance, for access from this machine only.The example for this item is shown with the ones above: net-http-Server.salam
EnableAccessLog
func EnableAccessLog(s &: Server)The example for this item is shown with the ones above: net-http-Server.salam
Run
func Run(s: Server)The example for this item is shown with the ones above: net-http-Server.salam
RunBackground
func RunBackground(s: Server): boolThe example for this item is shown with the ones above: net-http-Handle.salam
RunTLS
func RunTLS(s: Server, cert_file: str, key_file: str)The example for this item is shown with the ones above: net-http-Server.salam
Method
func Method(h: i64): strThe example for this item is shown with the ones above: net-http-Handle.salam
Path
func Path(h: i64): strThe example for this item is shown with the ones above: net-http-Handle.salam
Body
func Body(h: i64): strThe example for this item is shown with the ones above: net-http-Handle.salam
BodyLen
func BodyLen(h: i64): intBodyBytes.The example for this item is shown with the ones above: net-http-Upload.salam
BodyBytes
func BodyBytes(h: i64): void*Body would cut at the first zero byte. It belongs to the request and is freed when the handler returns; copy what you keep, and do not free it.The example for this item is shown with the ones above: net-http-Upload.salam
Multipart
func Multipart(h: i64): mime.MultipartPartsmultipart/form-data body into its parts, each with its name, file name, content type and bytes; no parts when the request is not multipart. The caller owns the result and frees it with mime.FreeMultipart.The example for this item is shown with the ones above: net-http-Upload.salam
Query
func Query(h: i64, key: str): strThe example for this item is shown with the ones above: net-http-Handle.salam
RawQuery
func RawQuery(h: i64): str?, exactly as it came and undecoded; for signing a request or passing it on to another service.The example for this item is shown with the ones above: net-http-Handle.salam
Header
func Header(h: i64, name: str): strThe example for this item is shown with the ones above: net-http-Handle.salam
Param
func Param(h: i64, name: str): strid in /books/:id.The example for this item is shown with the ones above: net-http-Handle.salam
Form
func Form(h: i64, field: str): strapplication/x-www-form-urlencoded body.The example for this item is shown with the ones above: net-http-Handle.salam
SetStatus
func SetStatus(h: i64, code: int)The example for this item is shown with the ones above: net-http-Handle.salam
SetHeader
func SetHeader(h: i64, k: str, v: str)cache-control adds a second header.The example for this item is shown with the ones above: net-http-Handle.salam
AddHeader
func AddHeader(h: i64, k: str, v: str)Set-Cookie headers.The example for this item is shown with the ones above: net-http-AddHeader.salam
Html
func Html(h: i64, html: str)The example for this item is shown with the ones above: net-http-Handle.salam
Json
func Json(h: i64, json: str)The example for this item is shown with the ones above: net-http-Handle.salam
Text
func Text(h: i64, text: str)The example for this item is shown with the ones above: net-http-Handle.salam
Bytes
func Bytes(h: i64, content_type: str, p: void*, n: int)n bytes from a pointer as the body, with the given content type; binary safe, for files, images and archives. The bytes are copied, so the call takes nothing over: free the pointer afterwards only if you allocated it, and never free one the request owns, such as the one BodyBytes returns.The example for this item is shown with the ones above: net-http-Upload.salam
Own
func Own(h: i64, s: str): strThe example for this item is shown with the ones above: net-http-Handle.salam
Redirect
func Redirect(h: i64, loc: str)The example for this item is shown with the ones above: net-http-Handle.salam
IsEdge
func IsEdge(): boolThe example for this item is shown with the ones above: net-http-Server.salam
NewCookie
func NewCookie(name: str, value: str): CookieThe example for this item is shown with the ones above: net-http-Client.salam
ExpiringCookie
func ExpiringCookie(name: str, value: str, max_age_seconds: int): CookieThe example for this item is shown with the ones above: net-http-Cookie.salam
ExpiredCookie
func ExpiredCookie(name: str, path: str, domain: str): CookieThe example for this item is shown with the ones above: net-http-Cookie.salam
Cookies
func Cookies(h: i64): HashMap<str, str>The example for this item is shown with the ones above: net-http-Context.salam
CookieValue
func CookieValue(h: i64, name: str): strThe example for this item is shown with the ones above: net-http-Context.salam
HasCookie
func HasCookie(h: i64, name: str): boolThe example for this item is shown with the ones above: net-http-Context.salam
SetCookie
func SetCookie(h: i64, c: Cookie)The example for this item is shown with the ones above: net-http-Context.salam
ClearCookie
func ClearCookie(h: i64, name: str)The example for this item is shown with the ones above: net-http-Context.salam
ClearCookieAt
func ClearCookieAt(h: i64, name: str, path: str, domain: str)The example for this item is shown with the ones above: net-http-Client.salam
NewHeaders
func NewHeaders(): HashMap<str, str>The example for this item is shown with the ones above: net-http-Handle.salam
Get
func Get(url: str): ResponseThe example for this item is shown with the ones above: net-http-Handle.salam
Post
func Post(url: str, body: str): Responseapplication/x-www-form-urlencoded; use PostWith to give another content type.The example for this item is shown with the ones above: net-http-Client.salam
GetWith
func GetWith(url: str, headers: HashMap<str, str>): ResponseGet, with extra headers.The example for this item is shown with the ones above: net-http-Client.salam
PostWith
func PostWith(url: str, body: str, headers: HashMap<str, str>): ResponsePost, with extra headers.The example for this item is shown with the ones above: net-http-Handle.salam
Request
func Request(method: str, url: str, headers: HashMap<str, str>, body: str): ResponseThe example for this item is shown with the ones above: net-http-Handle.salam
RequestRaw
func RequestRaw(method: str, url: str, header_block: str, body: str): Response\r\n.The example for this item is shown with the ones above: net-http-Client.salam
GetBytesWith
func GetBytesWith(url: str, headers: HashMap<str, str>): BytesResponseGetWith, but keeps the body as bytes (a BytesResponse).The example for this item is shown with the ones above: net-http-Client.salam
Do
func Do(method: str, url: str, headers: HashMap<str, str>, body: str, opts: RequestOptions): ResponseRequestOptions.The example for this item is shown with the ones above: net-http-Do.salam
Ok
func Ok(r: Response): boolOk is false for it too; tell the two apart with Status.The example for this item is shown with the ones above: net-http-Client.salam
Status
func Status(r: Response): intThe example for this item is shown with the ones above: net-http-Handle.salam
Body
func Body(r: Response): strThe example for this item is shown with the ones above: net-http-Handle.salam
Error
func Error(r: Response): strThe example for this item is shown with the ones above: net-http-Client.salam
Headers
func Headers(r: Response): HashMap<str, str>The example for this item is shown with the ones above: net-http-Client.salam
GetHeader
func GetHeader(r: Response, name: str): strThe example for this item is shown with the ones above: net-http-Handle.salam
Raw
func Raw(r: Response): strThe example for this item is shown with the ones above: net-http-Client.salam
RawHeaders
func RawHeaders(r: Response): strThe example for this item is shown with the ones above: net-http-Client.salam
HeaderValues
func HeaderValues(r: Response, name: str): Vector<str>Set-Cookie.The example for this item is shown with the ones above: net-http-Client.salam
Put
func Put(url: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Handle.salam
PutWith
func PutWith(url: str, body: str, headers: HashMap<str, str>): ResponsePut, with extra headers.The example for this item is shown with the ones above: net-http-Client.salam
Delete
func Delete(url: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
DeleteWith
func DeleteWith(url: str, headers: HashMap<str, str>): ResponseDelete, with extra headers.The example for this item is shown with the ones above: net-http-Client.salam
Patch
func Patch(url: str, body: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
PatchWith
func PatchWith(url: str, body: str, headers: HashMap<str, str>): ResponsePatch, with extra headers.The example for this item is shown with the ones above: net-http-Client.salam
Options
func Options(url: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
OptionsWith
func OptionsWith(url: str, headers: HashMap<str, str>): ResponseOptions, with extra headers.The example for this item is shown with the ones above: net-http-Client.salam
Head
func Head(url: str): ResponseThe example for this item is shown with the ones above: net-http-Client.salam
HeadWith
func HeadWith(url: str, headers: HashMap<str, str>): ResponseHead, with extra headers.The example for this item is shown with the ones above: net-http-Client.salam
EncodeForm
func EncodeForm(params: HashMap<str, str>): stra=1&b=2, with percent encoding.The example for this item is shown with the ones above: net-http-Client.salam
WithQuery
func WithQuery(base: str, params: HashMap<str, str>): strThe example for this item is shown with the ones above: net-http-Client.salam
SetCookieLines
func SetCookieLines(r: Response): Vector<str>Set-Cookie header of the response.The example for this item is shown with the ones above: net-http-Context.salam
CookieMap
func CookieMap(r: Response): HashMap<str, str>The example for this item is shown with the ones above: net-http-Client.salam
CookieHeader
func CookieHeader(cookies: HashMap<str, str>): strCookie header.The example for this item is shown with the ones above: net-http-Client.salam
NewClient
func NewClient(): ClientThe example for this item is shown with the ones above: net-http-Context.salam