about summary refs log tree commit diff stats
path: root/src/loader/response.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/loader/response.nim')
-rw-r--r--src/loader/response.nim90
1 files changed, 67 insertions, 23 deletions
diff --git a/src/loader/response.nim b/src/loader/response.nim
index a3b7e3e4..8143ccbf 100644
--- a/src/loader/response.nim
+++ b/src/loader/response.nim
@@ -11,6 +11,7 @@ import loader/request
 import monoucha/javascript
 import monoucha/jserror
 import monoucha/quickjs
+import monoucha/tojs
 import types/blob
 import types/color
 import types/opt
@@ -40,10 +41,10 @@ type
     url*: URL #TODO should be urllist?
     unregisterFun*: proc()
     resumeFun*: proc(outputId: int)
-    bodyRead*: EmptyPromise
     internalMessage*: string # should NOT be exposed to JS!
     outputId*: int
     onRead*: proc(response: Response) {.nimcall.}
+    onFinish*: proc(response: Response; success: bool) {.nimcall.}
     opaque*: RootRef
     flags*: set[ResponseFlag]
 
@@ -55,7 +56,6 @@ proc newResponse*(res: int; request: Request; stream: SocketStream;
     res: res,
     url: request.url,
     body: stream,
-    bodyRead: EmptyPromise(),
     outputId: outputId,
     status: status
   )
@@ -83,8 +83,10 @@ proc close*(response: Response) {.jsfunc.} =
   response.bodyUsed = true
   if response.unregisterFun != nil:
     response.unregisterFun()
+    response.unregisterFun = nil
   if response.body != nil:
     response.body.sclose()
+    response.body = nil
 
 func getCharset*(this: Response; fallback: Charset): Charset =
   if "Content-Type" notin this.headers.table:
@@ -103,8 +105,17 @@ func getContentType*(this: Response; fallback = "application/octet-stream"):
   # override buffer mime.types
   return DefaultGuess.guessContentType(this.url.pathname, fallback)
 
+func getContentLength*(this: Response): int64 =
+  this.headers.table.withValue("Content-Length", p):
+    for x in p[]:
+      let u = parseUInt64(x.strip(), allowSign = false)
+      if u.isSome and u.get <= uint64(int64.high):
+        return int64(u.get)
+  return -1
+
 type TextOpaque = ref object of RootObj
   buf: string
+  bodyRead: Promise[JSResult[string]]
 
 const BufferSize = 4096
 
@@ -122,6 +133,16 @@ proc onReadText(response: Response) =
       opaque.buf.setLen(olen)
       break
 
+proc onFinishText(response: Response; success: bool) =
+  let opaque = TextOpaque(response.opaque)
+  let bodyRead = opaque.bodyRead
+  if success:
+    let charset = response.getCharset(CHARSET_UTF_8)
+    bodyRead.resolve(JSResult[string].ok(opaque.buf.decodeAll(charset)))
+  else:
+    let err = newTypeError("NetworkError when attempting to fetch resource")
+    bodyRead.resolve(JSResult[string].err(err))
+
 proc resume*(response: Response) =
   response.resumeFun(response.outputId)
   response.resumeFun = nil
@@ -137,20 +158,20 @@ proc text*(response: Response): Promise[JSResult[string]] {.jsfunc.} =
       .err(newTypeError("Body has already been consumed"))
     p.resolve(err)
     return p
-  let opaque = TextOpaque()
+  let opaque = TextOpaque(bodyRead: newPromise[JSResult[string]]())
   response.opaque = opaque
   response.onRead = onReadText
+  response.onFinish = onFinishText
   response.bodyUsed = true
   response.resume()
-  return response.bodyRead.then(proc(): JSResult[string] =
-    let charset = response.getCharset(CHARSET_UTF_8)
-    ok(opaque.buf.decodeAll(charset))
-  )
+  return opaque.bodyRead
 
 type BlobOpaque = ref object of RootObj
   p: pointer
   len: int
   size: int
+  bodyRead: Promise[JSResult[Blob]]
+  contentType: string
 
 proc onReadBlob(response: Response) =
   let opaque = BlobOpaque(response.opaque)
@@ -168,29 +189,45 @@ proc onReadBlob(response: Response) =
     except ErrorAgain:
       break
 
+proc onFinishBlob(response: Response; success: bool) =
+  let opaque = BlobOpaque(response.opaque)
+  let bodyRead = opaque.bodyRead
+  if success:
+    let p = realloc(opaque.p, opaque.len)
+    opaque.p = nil
+    let blob = if p == nil:
+      newBlob(nil, 0, opaque.contentType, nil)
+    else:
+      newBlob(p, opaque.len, opaque.contentType, deallocBlob)
+    bodyRead.resolve(JSResult[Blob].ok(blob))
+  else:
+    if opaque.p != nil:
+      dealloc(opaque.p)
+      opaque.p = nil
+    let res = newTypeError("Error reading response")
+    bodyRead.resolve(JSResult[Blob].err(res))
+
 proc blob*(response: Response): Promise[JSResult[Blob]] {.jsfunc.} =
   if response.bodyUsed:
     let p = newPromise[JSResult[Blob]]()
     let err = JSResult[Blob].err(newTypeError("Body has already been consumed"))
     p.resolve(err)
     return p
-  let opaque = BlobOpaque()
+  let opaque = BlobOpaque(
+    bodyRead: newPromise[JSResult[Blob]](),
+    contentType: response.getContentType()
+  )
   response.opaque = opaque
   response.onRead = onReadBlob
+  response.onFinish = onFinishBlob
   response.bodyUsed = true
   response.resume()
-  let contentType = response.getContentType()
-  return response.bodyRead.then(proc(): JSResult[Blob] =
-    let p = realloc(opaque.p, opaque.len)
-    opaque.p = nil
-    if p == nil:
-      return ok(newBlob(nil, 0, contentType, nil))
-    ok(newBlob(p, opaque.len, contentType, deallocBlob))
-  )
+  return opaque.bodyRead
 
 type BitmapOpaque = ref object of RootObj
   bmp: Bitmap
   idx: int
+  bodyRead: EmptyPromise
 
 proc onReadBitmap(response: Response) =
   let opaque = BitmapOpaque(response.opaque)
@@ -206,26 +243,33 @@ proc onReadBitmap(response: Response) =
     except ErrorAgain:
       break
 
+proc onFinishBitmap(response: Response; success: bool) =
+  let opaque = BitmapOpaque(response.opaque)
+  opaque.bodyRead.resolve()
+
 proc saveToBitmap*(response: Response; bmp: Bitmap): EmptyPromise =
   assert not response.bodyUsed
-  let opaque = BitmapOpaque(bmp: bmp, idx: 0)
+  let opaque = BitmapOpaque(bmp: bmp, idx: 0, bodyRead: EmptyPromise())
   let size = bmp.width * bmp.height
   bmp.px = cast[seq[RGBAColorBE]](newSeqUninitialized[uint32](size))
   response.opaque = opaque
   if size > 0:
     response.onRead = onReadBitmap
+    response.onFinish = onFinishBitmap
   else:
     response.unregisterFun()
     response.body.sclose()
+    opaque.bodyRead.resolve()
   response.bodyUsed = true
   response.resume()
-  return response.bodyRead
+  return opaque.bodyRead
 
-proc json(ctx: JSContext; this: Response): Promise[JSResult[JSValue]]
-    {.jsfunc.} =
-  return this.text().then(proc(s: JSResult[string]): JSResult[JSValue] =
-    let s = ?s
-    return ok(JS_ParseJSON(ctx, cstring(s), csize_t(s.len), cstring"<input>"))
+proc json(ctx: JSContext; this: Response): Promise[JSValue] {.jsfunc.} =
+  return this.text().then(proc(s: JSResult[string]): JSValue =
+    if s.isNone:
+      return ctx.toJS(s.error)
+    return JS_ParseJSON(ctx, cstring(s.get), csize_t(s.get.len),
+      cstring"<input>")
   )
 
 proc addResponseModule*(ctx: JSContext) =