blobsets/src/blobsets/httpservers.nim

131 lines
4.1 KiB
Nim
Raw Normal View History

2019-01-20 17:14:32 +01:00
import std/asyncdispatch, std/asynchttpserver, std/parseutils, std/random, std/net, std/strutils, std/tables
import ../blobsets
export net.Port
type
HttpStoreServer = ref object
server: AsyncHttpServer
store: BlobStore
ingests: Table[string, IngestStream]
blobs: Table[BlobId, BlobStream]
# TODO: tables must be purged periodically
rng: Rand
proc newHttpStoreServer*(backend: BlobStore; seed = 1'i64): HttpStoreServer =
2019-01-27 16:18:18 +01:00
## Create a new HTTP server for a given store.
2019-01-20 17:14:32 +01:00
randomize()
HttpStoreServer(
server: newAsyncHttpServer(),
store: backend,
ingests: initTable[string, IngestStream](),
blobs: initTable[BlobId, BlobStream](),
2019-03-16 11:52:24 +01:00
rng: initRand(seed))
2019-01-20 17:14:32 +01:00
func parseRange(range: string): tuple[a: int, b: int] =
## Parse an HTTP byte range string.
var start = skip(range, "bytes=")
if start > 0:
start.inc parseInt(range, result.a, start)
if skipWhile(range, {'-'}, start) == 1:
discard parseInt(range, result.b, start+1)
if result.b < result.a:
reset result
2019-01-27 16:18:18 +01:00
proc blobStream(hss: HttpStoreServer; path: string): BlobStream =
let elems = path.split '/'
if not elems.len == 3:
raise newException(ValueError, "bad GET path " & path)
let
kind = case elems[1]
of $dataBlob: dataBlob
of $metaBlob: metaBlob
else: raise newException(ValueError, "bad GET path " & path)
blob = toBlobId elems[2]
if hss.blobs.contains blob:
result = hss.blobs[blob]
else:
result = hss.store.openBlobStream(blob, kind=kind)
hss.blobs[blob] = result
2019-01-20 17:14:32 +01:00
proc head(hss: HttpStoreServer; req: Request): Future[void] =
2019-01-27 16:18:18 +01:00
discard hss.blobStream(req.url.path)
# cache the stream in the blob table or raise an exception
2019-01-20 17:14:32 +01:00
req.respond(Http200, "")
2019-02-10 13:47:40 +01:00
proc get(hss: HttpStoreServer; req: Request) {.async.} =
2019-01-27 16:18:18 +01:00
let stream = hss.blobStream(req.url.path)
var
pos: BiggestInt
len = blobLeafSize
let range = req.headers.getOrDefault "range"
if range != "":
2019-01-20 17:14:32 +01:00
let
2019-01-27 16:18:18 +01:00
(startPos, endPos) = parseRange range
pos = startPos
len = endPos - startPos
2019-01-27 16:18:18 +01:00
stream.pos = pos
var body = newString(len)
2019-02-10 13:47:40 +01:00
len = await stream.read(body[0].addr, len)
2019-01-27 16:18:18 +01:00
body.setLen len
let headers = newHttpHeaders({"Range": "bytes=$1-$2" % [ $pos, $(pos+len) ]})
2019-02-10 13:47:40 +01:00
await req.respond(Http206, body, headers)
2019-01-20 17:14:32 +01:00
2019-02-10 13:47:40 +01:00
proc putIngest(hss: HttpStoreServer; req: Request) {.async.} =
2019-01-20 17:14:32 +01:00
let stream = hss.ingests[req.url.path]
2019-02-10 13:47:40 +01:00
await stream.ingest(req.body)
await req.respond(Http204, "")
2019-01-20 17:14:32 +01:00
2019-02-10 13:47:40 +01:00
proc postIngest(hss: HttpStoreServer; req: Request) {.async.} =
2019-01-20 17:14:32 +01:00
case req.url.path
2019-01-27 16:18:18 +01:00
of "/ingest/" & $dataBlob:
2019-01-20 17:14:32 +01:00
let
size = parseBiggestInt req.headers["ingest-size"]
key = "/ingest/" & $hss.rng.next
headers = newHttpHeaders({"Location": key})
hss.ingests[key] = hss.store.openIngestStream(size, dataBlob)
2019-02-10 13:47:40 +01:00
await req.respond(Http201, "", headers)
2019-01-27 16:18:18 +01:00
of "/ingest/" & $metaBlob:
2019-01-20 17:14:32 +01:00
let
size = parseBiggestInt req.headers["ingest-size"]
key = "/ingest/" & $hss.rng.next
headers = newHttpHeaders({"Location": key})
hss.ingests[key] = hss.store.openIngestStream(size, metaBlob)
2019-02-10 13:47:40 +01:00
await req.respond(Http201, "", headers)
2019-01-20 17:14:32 +01:00
else:
let
stream = hss.ingests[req.url.path]
2019-02-10 13:47:40 +01:00
(blob, size) = await finish stream
2019-01-20 17:14:32 +01:00
headers = newHttpHeaders({
"blob-id": blob.toHex,
"blob-size": $size,
})
hss.ingests.del req.url.path
2019-02-10 13:47:40 +01:00
await req.respond(Http204, "", headers)
2019-01-20 17:14:32 +01:00
proc serve*(hss: HttpStoreServer; port = Port(80)): Future[void] =
2019-01-27 16:18:18 +01:00
## Serve requests to HTTP store.
2019-02-08 16:57:46 +01:00
proc handleRequest(req: Request) {.async.} =
2019-01-27 16:18:18 +01:00
try:
2019-02-08 16:57:46 +01:00
let fut = case req.reqMethod
2019-01-27 16:18:18 +01:00
of HttpGET:
get(hss, req)
of HttpHEAD:
head(hss, req)
of HttpPUT:
putIngest(hss, req)
of HttpPOST:
postIngest(hss, req)
else:
req.respond(Http501, "method not implemented")
2019-02-08 16:57:46 +01:00
await fut
2019-01-27 16:18:18 +01:00
except KeyError:
2019-03-30 16:25:24 +01:00
await req.respond(Http404, "")
# there is a bug with responding to HEAD with a message
2019-01-27 16:18:18 +01:00
except ValueError:
2019-02-08 16:57:46 +01:00
await req.respond(Http400, getCurrentExceptionMsg())
2019-01-27 16:18:18 +01:00
except:
2019-03-30 16:25:24 +01:00
try: await req.respond(Http500, getCurrentExceptionMsg())
2019-02-08 16:57:46 +01:00
except: discard
2019-01-20 17:14:32 +01:00
hss.server.serve(port, handleRequest)