Standard library · Network

net/http

HTTP servers and clients: handlers, text and JSON responses, cookies and sending requests.

import net.http

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:

Things people get wrong:

  • Register every route before Use. The server keeps a copy of the router.
  • Post, Put and Patch label a body as a form (application/x-www-form-urlencoded) unless you pass a Content-Type; send JSON with PostWith and that header.
  • The client does not follow redirects unless you ask. A 302 comes back as a 302 with its Location header; set max_redirects in RequestOptions (or call SetMaxRedirects on 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, and timeout_ms caps the whole request.
  • Ok means a 2xx status. When the server could not be reached at all, Status is 0 and Error says 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.

net-http-guide-api.salam
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)
end
Output201 /books/1 {"id":1,"title":"The Little Prince"} [{"id":1,"title":"The Little Prince"},{"id":2,"title":"Rumi"}] {"id":2,"title":"Rumi"} 404 {"error":"no such book"} 400 {"error": "value does not match the expected type"}

Nothing 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.

net-http-guide-forms.salam
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)))
end
Output302 /board 400 name and text are required <ul><li>Sara: see you at 5 &amp; bring tea</li></ul> 'bring tea' found in 1 message(s)

Anything 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 &amp; 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.

net-http-guide-groups.salam
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"))
end
Outputok 401 Bearer {"error":"sign in first"} {"user":"maryam"} 204 404

A 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.

net-http-guide-static.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")
end
Output200 text/html; charset=utf-8 <h1>Welcome</h1> text/css body { margin: 0 } 404 404 Salam

Running 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.

net-http-guide-server.salam
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)
end

Run 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.

net-http-guide-client.salam
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
end
OutputPost: 415 {"error":"expected JSON, got application/x-www-form-urlencoded"} PostWith: 201 true id 31 Get: 302 Location: /v2/tickets dead server: 0 false could not connect to 127.0.0.1:1

Gzip 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: close unless the answer is framed. The raw calls stop as soon as an answer with a Content-Length or 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).
  • ok means a 2xx status, not "an answer arrived". Check status to tell a 404 from a dead server.
  • A str stops at the first zero byte, so binary bodies stay in data; RawText cuts 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.

net-http-raw-post-json.salam
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
end
Outputstatus: 201 true location: /users/7 id: 7 body: {"id": 7, "name": "Sara", "auth": "Bearer abc123"}

Content-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.

net-http-raw-status-errors.salam
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, "/")
end
Output/missing answered 404 with {"error": "no such page"} /busy answered 503 with try again later /nobody-listens failed: could not connect to 127.0.0.1:1 ftp endpoint ok: false / failed: the endpoint could not be parsed

A 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.

net-http-raw-api-client.salam
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)
end
OutputSara from Shiraz 3 x pen 1 x notebook

Chunked 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.

net-http-raw-chunked.salam
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)
end
Outputbody: news: all quiet silent server: false 0 the request timed out

A 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.

net-http-raw-binary.salam
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)
end
Outputtrue 200 image/png bytes received: 12 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d is a PNG: true RawText keeps 8 bytes

A 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.

net-http-raw-https.salam
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
end

When 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.

net-http-guide-limits.salam
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)
end
Output200 landed true 200 the response body is larger than the 5-byte limit 0 the request timed out 200 10 200

Which function for which job

JobFunction
Read a path parameter, query value or form fieldParam, Query, Form
Read the raw body or a request headerBody, Header
Answer with text, JSON or HTMLText, Json, Html
Choose the status or add a headerSetStatus, SetHeader
Send the browser somewhere elseRedirect
Hold the request in a struct instead of the bare idCtx, then the same Param, Json and the rest
A request with a time limit, a size limit or redirectsDo with RequestOptions, or SetTimeout, SetMaxRedirects on a client
One request from a programGet, Post, GetWith, PostWith, Request
Several requests that share cookiesNewClient
A request with headers you write yourself, or a binary bodyParseEndpoint, SendRaw, SendRawTimeout, SendRawTLS
Read the bytes of a raw answerRawResponse data and len, RawText, then FreeRaw
Check the answerOk 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
The address of one server: the host, the port and whether it is secure.
NameTypeDescription
hoststr = ""The host.
portint = 443The port: the one in the URL, else 80 for http and 443 for https.
securebool = trueConnect with TLS.
okbool = falseThe address was read correctly; false for anything that does not start with http:// or https://.
net-http-SendRaw.salam
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()
end
Outputtrue api.example.com 8443 true true 200 hello from the server [] text/plain true 200 21 hello from the server 200 21 true 1048576

Context

struct Context
The context of one request; the same numeric id the handler takes, in a struct.
NameTypeDescription
hi64The context id.
net-http-Context.salam
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
end
Outputhello Maryam (name=Maryam) hello again Maryam (1) <p>1</p> 202 {"id": "7", "path": "/books/7"} GET 1|a=1&b=2|application/x-www-form-urlencoded 302 true

Server

struct Server
An HTTP server.
NameTypeDescription
portint = 0The port.
hoststr = "0.0.0.0"The listen address; 0.0.0.0 by default, meaning every address.
routerrouter.RouterThe router for the requests.
hide_tokensbool = falseHide the server version in the responses.
access_logbool = falseLog every request to the output.
net-http-Server.salam
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)
end

RawResponse

struct RawResponse
A response with a binary body.
NameTypeDescription
statusint = 0The status code.
headstr = ""The headers.
datavoid*The address of the body.
lenint = 0The length of the body in bytes, after chunked decoding.
okbool = falseThe status is 2xx.
errorstr = ""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

struct Cookie
One response cookie.
NameTypeDescription
namestr = ""The name.
valuestr = ""The value.
pathstr = "/"The path the cookie is sent for; / by default.
domainstr = ""The domain; empty means this host.
max_ageint = 0The lifetime in seconds.
has_max_agebool = falseA lifetime was set; without one the cookie goes when the browser closes.
securebool = falseSend it over HTTPS only.
http_onlybool = trueKeep it out of reach of the page JavaScript; true by default.
same_sitestr = "Lax"The SameSite policy; Lax by default.
net-http-Cookie.salam
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")
end
Outputtheme dark / true Lax false 3600 true true salamlang.ir -1 true a b c

Response

struct Response
The response to a client request.
NameTypeDescription
statusint = 0The status code.
bodystr = ""The body.
headstr = ""The raw headers.
errstr = ""The error message, when the request did not get through or was refused for a limit; the status is 0 when nothing came back.
urlstr = ""The address of the last request, after redirects.
net-http-Client.salam
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) != ""
end
Outputtrue 200 GET t1 [] true ["sid=abc; Path=/; SameSite=Lax; HttpOnly"] abc true true POST a PUT t1 b DELETE DELETE t1 PATCH c PATCH t1 d OPTIONS OPTIONS t1 200 200 PUT e GET POST raw f q=hello%20world http://x.ir/s?q=hello%20world 2 en true 2 1 en false PUT c1 g PATCH h PUT i DELETE PATCH j OPTIONS 200 false true

BytesResponse

struct BytesResponse
A response whose body is raw bytes, for downloads such as archives and images that a str would cut at the first zero byte.
NameTypeDescription
statusint = 0The status code.
headstr = ""The raw headers.
bodycompress.ByteBufThe body as a compress.ByteBuf; free it with compress.BufFree.
errstr = ""The error message, when the request did not get through; the status is then 0.
urlstr = ""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 RequestOptions
The limits of a request: idle_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).
NameTypeDescription
idle_msint = 30000How long one read or write may stall, in milliseconds.
timeout_msint = 0The time for the whole request, redirects included; 0 means none.
max_bodyint = 268435456The largest body in bytes, measured after gzip is unpacked.
max_redirectsint = 0How many redirects to follow; 0 follows none.
net-http-Do.salam
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)
end
Output200 landed true 200 the response body is larger than the 5-byte limit 0 the request timed out 200 10 200

Client

struct Client
An HTTP client that keeps fixed headers and cookies between requests; a simple browser.
NameTypeDescription
cookiesHashMapThe cookies it took from the responses; each is sent back only to the host that set it.
headersHashMapThe headers sent with every request.
cookie_hostsHashMapWhich host set each cookie in cookies.
cookie_secureHashMapThe cookies that came with the Secure attribute; they are sent back over HTTPS only, never over plain HTTP.
optionsRequestOptionsThe limits of this client: a RequestOptions.
musync.MutexThe 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)
Sets a header for every request from now on.

The example for this item is shown with the ones above: net-http-Context.salam

SetCookie

func SetCookie(name: str, value: str)
Adds a cookie by hand; it is sent to the host of the first request only.

The example for this item is shown with the ones above: net-http-Client.salam

ClearCookies

func ClearCookies()
Clears every cookie.

The example for this item is shown with the ones above: net-http-Client.salam

SetTimeout

func SetTimeout(total_ms: int)
Sets the time for a whole request, in milliseconds; 0 means no limit.

The example for this item is shown with the ones above: net-http-Do.salam

SetIdleTimeout

func SetIdleTimeout(idle_ms: int)
Sets how long one read or write may stall, in milliseconds.

The example for this item is shown with the ones above: net-http-Do.salam

SetMaxRedirects

func SetMaxRedirects(n: int)
Sets how many redirects a request follows; 0 follows none.

The example for this item is shown with the ones above: net-http-Do.salam

SetMaxBody

func SetMaxBody(bytes: int)
Sets the largest body a response may have, in bytes.

The example for this item is shown with the ones above: net-http-Do.salam

Free

func Free()
Releases the lock and the maps of the client.

The example for this item is shown with the ones above: net-http-Do.salam

Request

func Request(method: str, url: str, body: str): Response
A request with any method and body; the cookies of the response are kept.

The 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): Response
A request with the header text written out by you; the cookies and headers of the client are added.

The 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): BytesResponse
Like RequestRaw, 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>): Response
The same, with extra headers for this request alone.

The example for this item is shown with the ones above: net-http-Client.salam

Get

func Get(url: str): Response
A GET request with the cookies and headers of the client.

The example for this item is shown with the ones above: net-http-Context.salam

Post

func Post(url: str, body: str): Response
A POST request with the cookies and headers of the client.

The example for this item is shown with the ones above: net-http-Context.salam

Put

func Put(url: str, body: str): Response
A PUT request with the cookies and headers of the client.

The example for this item is shown with the ones above: net-http-Client.salam

Delete

func Delete(url: str): Response
A DELETE request with the cookies and headers of the client.

The example for this item is shown with the ones above: net-http-Client.salam

Patch

func Patch(url: str, body: str): Response
A PATCH request with the cookies and headers of the client.

The example for this item is shown with the ones above: net-http-Client.salam

Options

func Options(url: str): Response
An OPTIONS request with the cookies and headers of the client.

The example for this item is shown with the ones above: net-http-Client.salam

Head

func Head(url: str): Response
A HEAD request with the cookies and headers of the client.

The 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"
The server version sent in the 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
Undoes percent encoding and turns + into a space.

The example for this item is shown with the ones above: net-http-Cookie.salam

ParseEndpoint

func ParseEndpoint(endpoint: str): Endpoint
Reads an address such as https://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): str
The value of one header in the header text; the name is case insensitive.

The example for this item is shown with the ones above: net-http-SendRaw.salam

Host

func Host(e: Endpoint): str
The host of an Endpoint: api.example.com for https://api.example.com:8443/v1.
net-http-EndpointInfo.salam
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"))
end
Outputtrue api.example.com 8443 true true example.com 80 false false

Port

func Port(e: Endpoint): int
The port of an Endpoint: 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): bool
Whether an Endpoint 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): bool
Whether ParseEndpoint 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)
After how many milliseconds an idle keep-alive connection is closed; for every server.

The example for this item is shown with the ones above: net-http-Server.salam

SetMaxHeaderBytes

func SetMaxHeaderBytes(n: int)
The greatest size of the request headers; larger ones are rejected with a 431.

The example for this item is shown with the ones above: net-http-Server.salam

SetMaxBodyBytes

func SetMaxBodyBytes(n: int)
The greatest body size; larger ones are rejected with a 413.

The example for this item is shown with the ones above: net-http-Server.salam

SetKeepAliveMaxRequests

func SetKeepAliveMaxRequests(n: int)
The greatest number of requests over one connection.

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): bool
Answers with a file from disk as the body, streamed in pieces so a large file never sits in memory; the length and the content type are set for you. The status and headers you set are kept, and a HEAD request gets only the headers. It returns false when the file cannot be opened, and then nothing is changed.
net-http-FileBody.salam
import 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)
end
Outputtrue 17 true 200 hello from a file 19622 200 text/plain 206 from

LocalPort

func LocalPort(h: i64): int
The port of the server socket this request came in on; useful when one handler serves several ports.

The example for this item is shown with the ones above: net-http-FileBody.salam

Status

func Status(h: i64): int
The response status code. Given a request handle inside a handler, it is the status set so far (200 until you change it).
net-http-Handle.salam
import 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"))
end
Outputtrue 200 hello from the server {"id": "42", "format": "pdf", "raw": "format=pdf"} 42 201 POST /books Maryam salam body: data text/html; charset=utf-8 <h1>hello</h1> 302 / 404

FileRange

func FileRange(h: i64, path: str, content_type: str, offset: int, length: int): bool
Like File 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): bool
Reads the size and the modification time (Unix seconds) of a file with one system call, into the two variables you pass. It returns false when the file cannot be read. Outside Linux on x64 and arm64 the time is 0.

The example for this item is shown with the ones above: net-http-FileBody.salam

Ctx

func Ctx(h: i64): Context
Turns the numeric id of the handler into a Context. 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): str
The request method, such as GET.

The example for this item is shown with the ones above: net-http-Handle.salam

Path

func Path(ctx: Context): str
The request path without the query.

The example for this item is shown with the ones above: net-http-Handle.salam

Body

func Body(ctx: Context): str
The request body.

The example for this item is shown with the ones above: net-http-Handle.salam

BodyLen

func BodyLen(ctx: Context): int
The length of the request body in bytes, zero bytes included; use it with BodyBytes.
net-http-Upload.salam
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)
end
Outputtrue 200 4 2 application/octet-stream 83 0 255 0 200 note::7 photo:pic.bin:4 note=holiday 400 not multipart

BodyBytes

func BodyBytes(ctx: Context): void*
A pointer to the request body bytes, for uploads and other binary bodies that 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.MultipartParts
Reads a multipart/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)
Sends 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
The whole query string after the ?, 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): str
One query parameter, decoded.

The example for this item is shown with the ones above: net-http-Handle.salam

func Header(ctx: Context, name: str): str
One request header; the name is case insensitive.

The example for this item is shown with the ones above: net-http-Handle.salam

Param

func Param(ctx: Context, name: str): str
One path parameter, such as id 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): str
One form field from an application/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 response status code; 200 by default.

The example for this item is shown with the ones above: net-http-Handle.salam

SetHeader

func SetHeader(ctx: Context, k: str, v: str)
Sets one response header. A second call with the same name, spelled the same way, replaces the value; a different spelling such as 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)
Adds a response header without replacing a value already stored under the same name, so a response can carry several Set-Cookie headers.
net-http-AddHeader.salam
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
end
Output200 one two a=1; Path=/ b=2; Path=/

Html

func Html(ctx: Context, html: str)
Sends an HTML response.

The example for this item is shown with the ones above: net-http-Handle.salam

Json

func Json(ctx: Context, json: str)
Sends a JSON response; you build the JSON text yourself.

The example for this item is shown with the ones above: net-http-Handle.salam

Text

func Text(ctx: Context, text: str)
Sends a text response.

The example for this item is shown with the ones above: net-http-Handle.salam

Redirect

func Redirect(ctx: Context, url: str)
Redirects to another address with a 302 and an empty body.

The example for this item is shown with the ones above: net-http-Handle.salam

Cookies

func Cookies(ctx: Context): HashMap<str, str>
Every cookie of the request.

The example for this item is shown with the ones above: net-http-Context.salam

CookieValue

func CookieValue(ctx: Context, name: str): str
The value of one cookie; an empty string when there is none.

The example for this item is shown with the ones above: net-http-Context.salam

HasCookie

func HasCookie(ctx: Context, name: str): bool
True when the request carries this cookie.

The example for this item is shown with the ones above: net-http-Context.salam

SetCookie

func SetCookie(ctx: Context, c: Cookie)
Sets a cookie in the response.

The example for this item is shown with the ones above: net-http-Context.salam

ClearCookie

func ClearCookie(ctx: Context, name: str)
Tells the browser to delete the cookie.

The example for this item is shown with the ones above: net-http-Context.salam

ConnOf

func ConnOf(ctx: Context): tcp.Conn
The TCP connection under the request; for upgrading to another protocol.

The 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): RawResponse
Sends the request head you wrote and n 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): RawResponse
The same, with a time limit in milliseconds for each read and write; 0 means none.

The 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): RawResponse
Like SendRawTimeout, 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)
Frees the memory of a raw response.

The example for this item is shown with the ones above: net-http-SendRaw.salam

RawText

func RawText(r: RawResponse): str
The body of a raw response as a string, cut at the first zero byte.

The example for this item is shown with the ones above: net-http-SendRaw.salam

RawLimit

func RawLimit(): int
The greatest response size the raw calls read, in bytes; 256 MB by default.

The example for this item is shown with the ones above: net-http-SendRaw.salam

SetRawLimit

func SetRawLimit(bytes: int)
Changes that limit for the whole program; zero or less is ignored.

The example for this item is shown with the ones above: net-http-SendRaw.salam

NewServer

func NewServer(port: int): Server
A new server on the given port.

The example for this item is shown with the ones above: net-http-Handle.salam

Use

func Use(s &: Server, r: router.Router)
Hands the router to the server.

The example for this item is shown with the ones above: net-http-Handle.salam

HideTokens

func HideTokens(s &: Server)
Takes the server version out of the response headers.

The example for this item is shown with the ones above: net-http-Server.salam

SetHost

func SetHost(s &: Server, host: str)
Sets the listen address; 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)
Logs every request to the output with its method, path and status.

The example for this item is shown with the ones above: net-http-Server.salam

Run

func Run(s: Server)
Runs the server and does not return until the program ends.

The example for this item is shown with the ones above: net-http-Server.salam

RunBackground

func RunBackground(s: Server): bool
Runs the server in another thread and returns at once. True when listening on the port started, false when the port is not available, because another program holds it for instance.

The 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)
Runs an HTTPS server with the given certificate and key.

The example for this item is shown with the ones above: net-http-Server.salam

Method

func Method(h: i64): str
The request method, such as GET.

The example for this item is shown with the ones above: net-http-Handle.salam

Path

func Path(h: i64): str
The request path without the query.

The example for this item is shown with the ones above: net-http-Handle.salam

Body

func Body(h: i64): str
The request body.

The example for this item is shown with the ones above: net-http-Handle.salam

BodyLen

func BodyLen(h: i64): int
The length of the request body in bytes, zero bytes included; use it with BodyBytes.

The example for this item is shown with the ones above: net-http-Upload.salam

BodyBytes

func BodyBytes(h: i64): void*
A pointer to the request body bytes, for uploads and other binary bodies that 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.MultipartParts
Reads a multipart/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): str
One query parameter, decoded.

The example for this item is shown with the ones above: net-http-Handle.salam

RawQuery

func RawQuery(h: i64): str
The whole query string after the ?, 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

func Header(h: i64, name: str): str
One request header; the name is case insensitive.

The example for this item is shown with the ones above: net-http-Handle.salam

Param

func Param(h: i64, name: str): str
One path parameter, such as id 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): str
One form field from an application/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 response status code; 200 by default.

The example for this item is shown with the ones above: net-http-Handle.salam

SetHeader

func SetHeader(h: i64, k: str, v: str)
Sets one response header. A second call with the same name, spelled the same way, replaces the value; a different spelling such as 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)
Adds a response header without replacing a value already stored under the same name, so a response can carry several 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)
Sends an HTML response.

The example for this item is shown with the ones above: net-http-Handle.salam

Json

func Json(h: i64, json: str)
Sends a JSON response; you build the JSON text yourself.

The example for this item is shown with the ones above: net-http-Handle.salam

Text

func Text(h: i64, text: str)
Sends a text response.

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)
Sends 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): str
Hands a string the handler built to the context, so it is freed when the request ends; do not hand it a string that came from the context itself, such as the body.

The example for this item is shown with the ones above: net-http-Handle.salam

Redirect

func Redirect(h: i64, loc: str)
Redirects to another address with a 302 and an empty body.

The example for this item is shown with the ones above: net-http-Handle.salam

IsEdge

func IsEdge(): bool
True when the program runs at the edge, on Cloudflare Workers through WebAssembly.

The example for this item is shown with the ones above: net-http-Server.salam

NewCookie

func NewCookie(name: str, value: str): Cookie
A session cookie that goes when the browser closes.

The 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): Cookie
A cookie with a lifetime in seconds.

The example for this item is shown with the ones above: net-http-Cookie.salam

ExpiredCookie

func ExpiredCookie(name: str, path: str, domain: str): Cookie
An expired cookie, to delete one the browser already holds.

The example for this item is shown with the ones above: net-http-Cookie.salam

Cookies

func Cookies(h: i64): HashMap<str, str>
Every cookie of the request.

The example for this item is shown with the ones above: net-http-Context.salam

CookieValue

func CookieValue(h: i64, name: str): str
The value of one cookie; an empty string when there is none.

The example for this item is shown with the ones above: net-http-Context.salam

HasCookie

func HasCookie(h: i64, name: str): bool
True when the request carries this cookie.

The example for this item is shown with the ones above: net-http-Context.salam

SetCookie

func SetCookie(h: i64, c: Cookie)
Sets a cookie in the response.

The example for this item is shown with the ones above: net-http-Context.salam

ClearCookie

func ClearCookie(h: i64, name: str)
Tells the browser to delete the cookie.

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)
Clears a cookie that was set with a particular path or domain.

The example for this item is shown with the ones above: net-http-Client.salam

NewHeaders

func NewHeaders(): HashMap<str, str>
An empty map for headers or parameters.

The example for this item is shown with the ones above: net-http-Handle.salam

Get

func Get(url: str): Response
Sends a GET request. Redirects are not followed; a read that stalls for 30 seconds fails.

The example for this item is shown with the ones above: net-http-Handle.salam

Post

func Post(url: str, body: str): Response
A POST request with a body, sent as application/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>): Response
Like Get, 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>): Response
Like Post, 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): Response
A request with any method, headers and body.

The 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
A request with the header text written out by you; every line has to end with \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>): BytesResponse
Like GetWith, 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): Response
Sends a request with the limits in a RequestOptions.

The example for this item is shown with the ones above: net-http-Do.salam

Ok

func Ok(r: Response): bool
True when the status is 2xx. A request that got no answer has status 0, so Ok 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): int
The response status code. Given a request handle inside a handler, it is the status set so far (200 until you change it).

The example for this item is shown with the ones above: net-http-Handle.salam

Body

func Body(r: Response): str
The request body.

The example for this item is shown with the ones above: net-http-Handle.salam

Error

func Error(r: Response): str
The error message; empty when the request succeeded.

The example for this item is shown with the ones above: net-http-Client.salam

Headers

func Headers(r: Response): HashMap<str, str>
The response headers as a map; the names exactly as the server sent them, in the same case.

The example for this item is shown with the ones above: net-http-Client.salam

GetHeader

func GetHeader(r: Response, name: str): str
The value of one header; the name is case insensitive.

The example for this item is shown with the ones above: net-http-Handle.salam

Raw

func Raw(r: Response): str
The complete text of the response.

The example for this item is shown with the ones above: net-http-Client.salam

RawHeaders

func RawHeaders(r: Response): str
The header text of the response.

The example for this item is shown with the ones above: net-http-Client.salam

HeaderValues

func HeaderValues(r: Response, name: str): Vector<str>
Every value of a header that came more than once, such as Set-Cookie.

The example for this item is shown with the ones above: net-http-Client.salam

Put

func Put(url: str, body: str): Response
A PUT request.

The 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>): Response
Like Put, with extra headers.

The example for this item is shown with the ones above: net-http-Client.salam

Delete

func Delete(url: str): Response
A DELETE request.

The example for this item is shown with the ones above: net-http-Client.salam

DeleteWith

func DeleteWith(url: str, headers: HashMap<str, str>): Response
Like Delete, 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): Response
A PATCH request.

The 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>): Response
Like Patch, with extra headers.

The example for this item is shown with the ones above: net-http-Client.salam

Options

func Options(url: str): Response
An OPTIONS request.

The example for this item is shown with the ones above: net-http-Client.salam

OptionsWith

func OptionsWith(url: str, headers: HashMap<str, str>): Response
Like Options, with extra headers.

The example for this item is shown with the ones above: net-http-Client.salam

func Head(url: str): Response
A HEAD request; only the headers come back.

The example for this item is shown with the ones above: net-http-Client.salam

HeadWith

func HeadWith(url: str, headers: HashMap<str, str>): Response
Like Head, 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>): str
Turns a map into a form body, a=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>): str
Adds the parameters to an address as a query string.

The example for this item is shown with the ones above: net-http-Client.salam

SetCookieLines

func SetCookieLines(r: Response): Vector<str>
Every 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 names and values of the cookies the response set.

The example for this item is shown with the ones above: net-http-Client.salam

CookieHeader

func CookieHeader(cookies: HashMap<str, str>): str
Turns a cookie map into the value of a Cookie header.

The example for this item is shown with the ones above: net-http-Client.salam

NewClient

func NewClient(): Client
A new client with its own lock, so threads can share it.

The example for this item is shown with the ones above: net-http-Context.salam