guildenstern/streamingserver

Server for cases when request body must be processed as a stream (for example, when client is uploading a large dataset to server), or response must sent as a stream (for example, when client is downloading a large dataset from server.)

Example

import guildenstern/[dispatcher, streamingserver]

proc handleUpload() =
  let ok = "ok"
  if not startReceiveMultipart(giveupSecs = 2): (reply(Http400); return)
  while true:
    let (state , chunk) = receiveMultipart()
    case state
      of Fail: break
      of TryAgain: continue
      of Progress: echo chunk
      of Complete: (reply(Http200, ok); break)
  shutdown()

proc onRequest() =
  let html = """<!doctype html><title>StreamCtx</title><body>
  <form action="/upload" method="post" enctype="multipart/form-data" accept-charset="utf-8">
  <input type="file" id="file" name="file">
  <input type="submit">"""
  
  if startsUri("/upload"): handleUpload()
  else: reply(Http200, html)

let server = newStreamingServer(onRequest)
server.start(5050)
joinThread(server.thread)

Procs

proc continueDownload(chunk: string): bool {....raises: [], tags: [], forbids: [].}
proc finishDownload(): bool {.discardable, ...raises: [], tags: [], forbids: [].}
proc hasData(): bool {....raises: [], tags: [], forbids: [].}
While this is true, there are more multipart chunks to receive
proc isStreamingContext(): bool {....raises: [], tags: [], forbids: [].}
proc newStreamingServer(onrequestcallback: proc () {....gcsafe, nimcall, ...raises: [].};
                        loglevel = LogLevel.WARN): HttpServer {....raises: [],
    tags: [], forbids: [].}
proc receiveMultipart(): (SocketState, string) {....gcsafe, raises: [], tags: [],
    forbids: [].}
Tries to receive a chunk from a stream started with startReceiveMultipart. Returns both the socket's state and the possibly received chunk.
proc startDownload(code: HttpCode = Http200; headers: openArray[string] = [];
                   waitMs = 100; giveupSecs = 10): bool {....raises: [], tags: [],
    forbids: [].}

Starts replying http response as Transfer-encoding: chunked. Allows sending large datasets in multiple parts so that main memory is not exhausted. Also supports sending dynamic data, where Content-length header cannot be set.

waitMs is time to sleep if continueDownload is waiting for more data to read. giveupSecs is total time for replying before socket is closed. Continue response with calls to continueDownload. End response with finishDownload.

See examples/streamingdownloadtest.nim for a concrete example.

proc startReceiveMultipart(waitMs = 100; giveupSecs = 10): bool {....gcsafe,
    raises: [], tags: [], forbids: [].}

Starts receiving a multipart/form-data http request as chunks. This is how browsers deliver file uploads to server, see example above.

waitMs is time to sleep before receiveMultipart returns TryAgain. giveupSecs is total time for receiving before socket is closed.

Returns false if content-type header is not multipart/form-data.

Iterators

iterator receiveInChunks(): (SocketState, string) {....gcsafe, raises: [],
    tags: [], forbids: [].}
Receives a http request in chunks, yielding the state of operation and a possibly received new chuck on every iteration. With this, you can receive POST data without worries about main memory usage. See examples/streamingposttest.nim for a concrete working example of how to use this iterator.

Templates

template stream(): untyped
Casts the socketcontext thread local variable into a StreamingContext