1# http - HTTP/1.1 client and server, in the shape of Go's net/http.2#3# A client sends a request and reads a response; a server accepts connections4# and hands each one to a handler, which is any function of (w, r). Headers5# are kept in a map whose keys are lowercase, so looking one up doesn't6# depend on how the other side chose to spell it.7#8# What is not here: TLS, chunked transfer encoding, keep alive. Every9# connection carries one exchange and is closed.1011net = import("net")12strings = import("strings")13strconv = import("strconv")14bufio = import("bufio")1516# ========== Status Codes ==========1718StatusOK = 20019StatusCreated = 20120StatusAccepted = 20221StatusNoContent = 20422StatusMovedPermanently = 30123StatusFound = 30224StatusSeeOther = 30325StatusNotModified = 30426StatusTemporaryRedirect = 30727StatusPermanentRedirect = 30828StatusBadRequest = 40029StatusUnauthorized = 40130StatusForbidden = 40331StatusNotFound = 40432StatusMethodNotAllowed = 40533StatusRequestTimeout = 40834StatusConflict = 40935StatusPayloadTooLarge = 41336StatusUnsupportedMediaType = 41537StatusTooManyRequests = 42938StatusInternalServerError = 50039StatusNotImplemented = 50140StatusBadGateway = 50241StatusServiceUnavailable = 50342StatusGatewayTimeout = 5044344statusText = {45 200: "OK",46 201: "Created",47 202: "Accepted",48 204: "No Content",49 301: "Moved Permanently",50 302: "Found",51 303: "See Other",52 304: "Not Modified",53 307: "Temporary Redirect",54 308: "Permanent Redirect",55 400: "Bad Request",56 401: "Unauthorized",57 403: "Forbidden",58 404: "Not Found",59 405: "Method Not Allowed",60 408: "Request Timeout",61 409: "Conflict",62 413: "Payload Too Large",63 415: "Unsupported Media Type",64 429: "Too Many Requests",65 500: "Internal Server Error",66 501: "Not Implemented",67 502: "Bad Gateway",68 503: "Service Unavailable",69 504: "Gateway Timeout"70}7172# StatusText returns the reason phrase of a status code, empty when the code73# is not one of the known ones.74StatusText = fn(code) {75 if statusText[code] == null {76 return ""77 }78 return statusText[code]79}8081# ========== Methods ==========8283MethodGet = "GET"84MethodHead = "HEAD"85MethodPost = "POST"86MethodPut = "PUT"87MethodPatch = "PATCH"88MethodDelete = "DELETE"89MethodOptions = "OPTIONS"9091# ========== URLs ==========9293# ParseURL takes a URL apart: Scheme, Host with its port, Path, RawQuery and94# Query, which is the query string already split into a map.95ParseURL = fn(rawurl) {96 scheme = "http"97 rest = rawurl9899 if strings.HasPrefix(rawurl, "http://") {100 rest = slice(rawurl, 7, len(rawurl))101 } else if strings.HasPrefix(rawurl, "https://") {102 return error("http: https is not supported")103 } else if strings.Contains(rawurl, "://") {104 return error("http: unsupported scheme in \"{rawurl}\"")105 }106107 host = rest108 path = "/"109110 if (i = strings.Index(rest, "/")) != -1 {111 host = slice(rest, 0, i)112 path = slice(rest, i, len(rest))113 }114115 if host == "" {116 return error("http: no host in \"{rawurl}\"")117 }118119 query = ""120 if (i = strings.Index(path, "?")) != -1 {121 query = slice(path, i + 1, len(path))122 path = slice(path, 0, i)123 }124125 u = new()126 u.Scheme = scheme127 u.Host = if strings.Index(host, ":") == -1 { host + ":80" } else { host }128 u.Hostname = net.SplitHostPort(u.Host).Host129 u.Path = path130 u.RawQuery = query131 u.Query = ParseQuery(query)132 return u133}134135# ParseQuery reads a query string into a map. A key without a value maps to136# the empty string, the way an empty field does.137ParseQuery = fn(raw) {138 out = {}139 if raw == "" {140 return out141 }142143 pairs = strings.Split(raw, "&")144 for i = 0; i < len(pairs); ++i {145 p = pairs[i]146 if p == "" {147 continue148 }149150 if (j = strings.Index(p, "=")) == -1 {151 out[unescape(p)] = ""152 } else {153 out[unescape(slice(p, 0, j))] = unescape(slice(p, j + 1, len(p)))154 }155 }156 return out157}158159hexValue = fn(c) {160 if c >= "0" && c <= "9" {161 return bytes(c)[0] - 48162 }163 if c >= "a" && c <= "f" {164 return bytes(c)[0] - 87165 }166 if c >= "A" && c <= "F" {167 return bytes(c)[0] - 55168 }169 return -1170}171172# unescape turns %XX back into the byte it stands for, and + into a space.173unescape = fn(s) {174 out = []175 for i = 0; i < len(s); ++i {176 c = s[i]177178 if c == "+" {179 out = append(out, 32)180 } else if c == "%" && i + 2 < len(s) && hexValue(s[i + 1]) >= 0 && hexValue(s[i + 2]) >= 0 {181 out = append(out, hexValue(s[i + 1]) << 4 | hexValue(s[i + 2]))182 i = i + 2183 } else {184 out = append(out, bytes(c)[0])185 }186 }187 return string(bytes(out))188}189190unreserved = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"191hexDigits = "0123456789ABCDEF"192193# Escape writes a string so that it can travel inside a URL.194Escape = fn(s) {195 b = bytes(s)196 out = ""197198 for i = 0; i < len(b); ++i {199 c = string(bytes([b[i]]))200 if strings.Contains(unreserved, c) {201 out = out + c202 } else {203 out = out + "%" + hexDigits[b[i] >> 4] + hexDigits[b[i] & 0x0f]204 }205 }206 return out207}208209# ========== Headers ==========210211# newHeader returns an empty header map with the two ways of reaching into212# it: Get and Set, which don't care about case.213newHeader = fn() {{} }214215# HeaderGet returns the value of a header, or an empty string.216HeaderGet = fn(h, name) {217 v = h[strings.ToLower(name)]218 if v == null {219 return ""220 }221 return v222}223224HeaderSet = fn(h, name, value) {225 h[strings.ToLower(name)] = string(value)226 return h227}228229# canonical writes a header name the way HTTP does: Content-Type.230canonical = fn(name) {231 parts = strings.Split(strings.ToLower(name), "-")232 for i = 0; i < len(parts); ++i {233 if len(parts[i]) > 0 {234 parts[i] = strings.ToUpper(slice(parts[i], 0, 1)) + slice(parts[i], 1, len(parts[i]))235 }236 }237 return strings.Join(parts, "-")238}239240# readHeaders reads the header lines up to the empty one that ends them.241readHeaders = fn(r) {242 h = newHeader()243244 for {245 line = r.ReadLine()246 if line == null || line == "" {247 return h248 }249250 if (i = strings.Index(line, ":")) != -1 {251 name = strings.ToLower(strings.TrimSpace(slice(line, 0, i)))252 h[name] = strings.TrimSpace(slice(line, i + 1, len(line)))253 }254 }255}256257# readBody returns as many bytes as Content-Length says. Without that header258# there is a body only if the sender is going to close the connection to mark259# its end, which is what toEnd is for: true for a response, false for a260# request, where waiting for a close that never comes would hang the server.261readBody = fn(r, h, toEnd) {262 length = HeaderGet(h, "content-length")263264 if length == "" {265 if toEnd {266 return string(r.ReadAll())267 }268 return ""269 }270 if failed(n = strconv.Atoi(length)) {271 return error("http: bad Content-Length \"{length}\"")272 }273 if n <= 0 {274 return ""275 }276 return string(r.Read(n))277}278279# ========== Requests and Responses ==========280281# NewRequest builds a request. Body may be null for the methods that carry282# none.283NewRequest = fn(method, url, body) {284 if failed(u = ParseURL(url)) {285 return u286 }287288 req = new()289 req.Method = if method == null { MethodGet } else { strings.ToUpper(method) }290 req.URL = url291 req.Path = u.Path292 req.RawQuery = u.RawQuery293 req.Query = u.Query294 req.Host = u.Host295 req.Header = newHeader()296 req.Body = if body == null { "" } else { string(body) }297 return req298}299300newResponse = fn() {301 resp = new()302 resp.StatusCode = 0303 resp.Status = ""304 resp.Proto = "HTTP/1.1"305 resp.Header = newHeader()306 resp.Body = ""307 return resp308}309310# ========== Client ==========311312# NewClient returns a client. Timeout is in milliseconds and may be null.313NewClient = fn(timeout) {314 client = new()315 client.Timeout = if timeout == null { 30000 } else { timeout }316317 # Do sends a request and returns the response.318 client.Do = fn(req) {319 if failed(u = ParseURL(req.URL)) {320 return u321 }322 if failed(conn = net.Dial("tcp", u.Host)) {323 return conn324 }325 conn.SetTimeout(client.Timeout)326327 target = u.Path328 if u.RawQuery != "" {329 target = target + "?" + u.RawQuery330 }331332 out = "{req.Method} {target} HTTP/1.1\r\n"333 out = out + "Host: {u.Hostname}\r\n"334 out = out + "Connection: close\r\n"335336 if HeaderGet(req.Header, "user-agent") == "" {337 out = out + "User-Agent: tau-http/1.0\r\n"338 }339340 ks = keys(req.Header)341 for i = 0; i < len(ks); ++i {342 out = out + "{canonical(ks[i])}: {req.Header[ks[i]]}\r\n"343 }344345 if len(req.Body) > 0 {346 out = out + "Content-Length: {len(req.Body)}\r\n"347 }348349 out = out + "\r\n" + req.Body350351 if failed(n = conn.Write(out)) {352 conn.Close()353 return n354 }355356 resp = readResponse(conn)357 conn.Close()358 return resp359 }360361 client.Get = fn(url) {362 if failed(req = NewRequest(MethodGet, url, null)) {363 return req364 }365 return client.Do(req)366 }367368 client.Post = fn(url, contentType, body) {369 if failed(req = NewRequest(MethodPost, url, body)) {370 return req371 }372 HeaderSet(req.Header, "content-type", if contentType == null { "text/plain" } else { contentType })373 return client.Do(req)374 }375376 client.Head = fn(url) {377 if failed(req = NewRequest(MethodHead, url, null)) {378 return req379 }380 return client.Do(req)381 }382383 return client384}385386# readResponse reads a whole response off a connection.387readResponse = fn(conn) {388 r = bufio.NewReader(conn, 4096)389390 line = r.ReadLine()391 if line == null || line == "" {392 return error("http: empty response")393 }394395 parts = strings.SplitN(line, " ", 3)396 if len(parts) < 2 {397 return error("http: malformed status line \"{line}\"")398 }399 if failed(code = strconv.Atoi(parts[1])) {400 return error("http: malformed status code \"{parts[1]}\"")401 }402403 resp = newResponse()404 resp.Proto = parts[0]405 resp.StatusCode = code406 resp.Status = if len(parts) > 2 { parts[2] } else { StatusText(code) }407 resp.Header = readHeaders(r)408409 if failed(body = readBody(r, resp.Header, true)) {410 return body411 }412 resp.Body = body413 return resp414}415416DefaultClient = NewClient(null)417418Get = fn(url) { DefaultClient.Get(url) }419Post = fn(url, contentType, body) { DefaultClient.Post(url, contentType, body) }420Head = fn(url) { DefaultClient.Head(url) }421422# ========== Server ==========423424# newResponseWriter is what a handler writes into. Nothing reaches the425# connection until the handler is done: the status line comes first, and a426# handler is free to change its mind about it until it writes something.427newResponseWriter = fn() {428 w = new()429 w.statusCode = StatusOK430 w.headers = newHeader()431 w.body = ""432433 w.Header = fn() { w.headers }434 w.WriteHeader = fn(code) { w.statusCode = code }435436 w.Write = fn(data) {437 s = string(data)438 w.body = w.body + s439 return len(s)440 }441442 w.WriteString = fn(s) { w.Write(s) }443444 # response is the bytes to send back.445 w.response = fn() {446 out = "HTTP/1.1 {w.statusCode} {StatusText(w.statusCode)}\r\n"447448 HeaderSet(w.headers, "content-length", len(w.body))449 if HeaderGet(w.headers, "content-type") == "" {450 HeaderSet(w.headers, "content-type", "text/plain; charset=utf-8")451 }452 HeaderSet(w.headers, "connection", "close")453454 ks = keys(w.headers)455 for i = 0; i < len(ks); ++i {456 out = out + "{canonical(ks[i])}: {w.headers[ks[i]]}\r\n"457 }458459 return out + "\r\n" + w.body460 }461462 return w463}464465# readRequest reads a request off a connection.466readRequest = fn(conn) {467 r = bufio.NewReader(conn, 4096)468469 line = r.ReadLine()470 if line == null || line == "" {471 return error("http: empty request")472 }473474 parts = strings.Split(line, " ")475 if len(parts) < 2 {476 return error("http: malformed request line \"{line}\"")477 }478479 target = parts[1]480 query = ""481 if (i = strings.Index(target, "?")) != -1 {482 query = slice(target, i + 1, len(target))483 target = slice(target, 0, i)484 }485486 req = new()487 req.Method = parts[0]488 req.Proto = if len(parts) > 2 { parts[2] } else { "HTTP/1.0" }489 req.Path = target490 req.URL = target491 req.RawQuery = query492 req.Query = ParseQuery(query)493 req.Header = readHeaders(r)494 req.Host = HeaderGet(req.Header, "host")495 req.RemoteAddr = conn.RemoteAddr496497 if failed(body = readBody(r, req.Header, false)) {498 return body499 }500 req.Body = body501 return req502}503504# NewServeMux returns a multiplexer. A pattern ending in "/" matches every505# path below it, anything else matches exactly, and the longest pattern that506# matches wins, as in Go.507NewServeMux = fn() {508 mux = new()509 mux.patterns = []510 mux.handlers = {}511512 mux.Handle = fn(pattern, handler) {513 if mux.handlers[pattern] == null {514 mux.patterns = append(mux.patterns, pattern)515 }516 mux.handlers[pattern] = handler517 return null518 }519520 mux.HandleFunc = fn(pattern, handler) { mux.Handle(pattern, handler) }521522 # Handler returns the handler for a path, or null when none matches.523 mux.Handler = fn(path) {524 best = ""525 for i = 0; i < len(mux.patterns); ++i {526 p = mux.patterns[i]527528 matches = if strings.HasSuffix(p, "/") { strings.HasPrefix(path, p) } else { path == p }529 if matches && len(p) > len(best) {530 best = p531 }532 }533534 if best == "" {535 return null536 }537 return mux.handlers[best]538 }539540 mux.ServeHTTP = fn(w, r) {541 h = mux.Handler(r.Path)542 if h == null {543 NotFound(w, r)544 return null545 }546 return h(w, r)547 }548549 return mux550}551552DefaultServeMux = NewServeMux()553554Handle = fn(pattern, handler) { DefaultServeMux.Handle(pattern, handler) }555HandleFunc = fn(pattern, handler) { DefaultServeMux.HandleFunc(pattern, handler) }556557# NotFound is the handler for a path nothing was registered for.558NotFound = fn(w, r) {559 w.WriteHeader(StatusNotFound)560 w.Write("404 page not found\n")561 return null562}563564# Error replies with a message and a status code.565Error = fn(w, msg, code) {566 w.WriteHeader(code)567 w.Write(string(msg) + "\n")568 return null569}570571# Redirect replies with a redirect to url.572Redirect = fn(w, r, url, code) {573 HeaderSet(w.Header(), "location", url)574 w.WriteHeader(if code == null { StatusFound } else { code })575 w.Write("")576 return null577}578579# serve reads one exchange off a connection and answers it.580serve = fn(conn, handler) {581 w = newResponseWriter()582583 if failed(req = readRequest(conn)) {584 w.WriteHeader(StatusBadRequest)585 w.Write("400 bad request\n")586 } else if type(handler) == "closure" {587 handler(w, req)588 } else {589 handler.ServeHTTP(w, req)590 }591592 conn.Write(w.response())593 conn.Close()594 return null595}596597# NewServer returns a server. Handler is a mux or any function of (w, r).598NewServer = fn(addr, handler) {599 server = new()600 server.Addr = addr601 server.Handler = if handler == null { DefaultServeMux } else { handler }602 server.running = false603604 # ListenAndServe accepts connections until Close is called, one tau605 # routine per connection so that a slow client holds up nobody.606 server.ListenAndServe = fn() {607 if failed(ln = net.Listen("tcp", server.Addr)) {608 return ln609 }610611 server.ln = ln612 server.running = true613614 for server.running {615 if failed(conn = ln.Accept()) {616 if !server.running {617 return null618 }619 continue620 }621 tau serve(conn, server.Handler)622 }623624 return null625 }626627 server.Close = fn() {628 server.running = false629 if server.ln != null {630 return server.ln.Close()631 }632 return null633 }634635 return server636}637638# ListenAndServe serves handler on addr until something goes wrong.639ListenAndServe = fn(addr, handler) {640 return NewServer(addr, handler).ListenAndServe()641}