about summary refs log tree commit diff stats
path: root/src/io
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-04-30 12:19:24 +0200
committerbptato <nincsnevem662@gmail.com>2023-04-30 12:19:24 +0200
commita02c408f933aea6f405ed3c64ab151b01b33ae9e (patch)
tree4946f6b13da782a92a1ecc0f03402abacf2614b3 /src/io
parent05b64a1d8fa95381d756231f665c0b8c79787b67 (diff)
downloadchawan-a02c408f933aea6f405ed3c64ab151b01b33ae9e.tar.gz
WIP fetch
Diffstat (limited to 'src/io')
-rw-r--r--src/io/loader.nim99
-rw-r--r--src/io/request.nim27
2 files changed, 101 insertions, 25 deletions
diff --git a/src/io/loader.nim b/src/io/loader.nim
index bced0298..7c55b2bc 100644
--- a/src/io/loader.nim
+++ b/src/io/loader.nim
@@ -26,6 +26,7 @@ import io/http
 import io/promise
 import io/request
 import io/urlfilter
+import js/javascript
 import ips/serialize
 import ips/serversocket
 import ips/socketstream
@@ -36,8 +37,17 @@ import types/url
 import utils/twtstr
 
 type
-  FileLoader* = object
+  FileLoader* = ref object
     process*: Pid
+    connecting*: Table[int, ConnectData]
+    ongoing*: Table[int, Response]
+    registerFun*: proc(fd: int)
+    unregisterFun*: proc(fd: int)
+
+  ConnectData = object
+    promise: Promise[Response]
+    stream: Stream
+    request: Request
 
   LoaderCommand = enum
     LOAD, QUIT
@@ -192,9 +202,72 @@ proc runFileLoader*(fd: cint, config: LoaderConfig) =
         ctx.handleList.del(idx)
   ctx.exitLoader()
 
-#TODO async requests...
+proc applyHeaders(request: Request, response: Response) =
+  if "Content-Type" in response.headers.table:
+    response.contenttype = response.headers.table["Content-Type"][0].until(';')
+  else:
+    response.contenttype = guessContentType($response.url.path)
+  if "Location" in response.headers.table:
+    if response.status in 301..303 or response.status in 307..308:
+      let location = response.headers.table["Location"][0]
+      let url = parseUrl(location, option(request.url))
+      if url.isSome:
+        if (response.status == 303 and
+            request.httpmethod notin {HTTP_GET, HTTP_HEAD}) or
+            (response.status == 301 or response.status == 302 and
+            request.httpmethod == HTTP_POST):
+          response.redirect = newRequest(url.get, HTTP_GET,
+            mode = request.mode, credentialsMode = request.credentialsMode,
+            destination = request.destination)
+        else:
+          response.redirect = newRequest(url.get, request.httpmethod,
+            body = request.body, multipart = request.multipart,
+            mode = request.mode, credentialsMode = request.credentialsMode,
+            destination = request.destination)
+
+#TODO: add init
+proc fetch*(loader: FileLoader, input: Request): Promise[Response] =
+  let stream = connectSocketStream(loader.process, false, blocking = true)
+  stream.swrite(LOAD)
+  stream.swrite(input)
+  stream.flush()
+  let fd = int(stream.source.getFd())
+  loader.registerFun(fd)
+  let promise = Promise[Response]()
+  loader.connecting[fd] = ConnectData(promise: promise, request: input)
+
+proc newResponse(res: int, request: Request, stream: Stream = nil): Response =
+  return Response(
+    res: res,
+    url: request.url,
+    body: stream
+  )
+
+proc onConnected*(loader: FileLoader, fd: int) =
+  let connectData = loader.connecting[fd]
+  let stream = connectData.stream
+  let promise = connectData.promise
+  let request = connectData.request
+  var res: int
+  stream.sread(res)
+  if res == 0:
+    let response = newResponse(res, request, stream)
+    response.unregisterFun = proc() = loader.unregisterFun(fd)
+    stream.sread(response.status)
+    stream.sread(response.headers)
+    applyHeaders(request, response)
+    response.body = stream
+    loader.ongoing[fd] = response
+    promise.resolve(response)
+  else:
+    #TODO: reject promise instead.
+    let response = newResponse(res, request)
+    promise.resolve(response)
+  loader.connecting.del(fd)
+
 proc doRequest*(loader: FileLoader, request: Request, blocking = true): Response =
   new(result)
+  result.url = request.url
   let stream = connectSocketStream(loader.process, false, blocking = true)
   stream.swrite(LOAD)
   stream.swrite(request)
@@ -203,27 +276,7 @@ proc doRequest*(loader: FileLoader, request: Request, blocking = true): Response
   if result.res == 0:
     stream.sread(result.status)
     stream.sread(result.headers)
-    if "Content-Type" in result.headers.table:
-      result.contenttype = result.headers.table["Content-Type"][0].until(';')
-    else:
-      result.contenttype = guessContentType($request.url.path)
-    if "Location" in result.headers.table:
-      if result.status in 301..303 or result.status in 307..308:
-        let location = result.headers.table["Location"][0]
-        let url = parseUrl(location, some(request.url))
-        if url.isSome:
-          if (result.status == 303 and
-              request.httpmethod notin {HTTP_GET, HTTP_HEAD}) or
-              (result.status == 301 or result.status == 302 and
-              request.httpmethod == HTTP_POST):
-            result.redirect = newRequest(url.get, HTTP_GET,
-              mode = request.mode, credentialsMode = request.credentialsMode,
-              destination = request.destination)
-          else:
-            result.redirect = newRequest(url.get, request.httpmethod,
-              body = request.body, multipart = request.multipart,
-              mode = request.mode, credentialsMode = request.credentialsMode,
-              destination = request.destination)
+    applyHeaders(request, result)
     # Only a stream of the response body may arrive after this point.
     result.body = stream
     if not blocking:
diff --git a/src/io/request.nim b/src/io/request.nim
index 8837a1f9..c499ca8e 100644
--- a/src/io/request.nim
+++ b/src/io/request.nim
@@ -2,6 +2,7 @@ import options
 import streams
 import tables
 
+import bindings/quickjs
 import types/url
 import js/javascript
 import utils/twtstr
@@ -47,11 +48,14 @@ type
 
   Response* = ref object
     body*: Stream
+    bodyUsed* {.jsget.}: bool
     res* {.jsget.}: int
     contenttype* {.jsget.}: string
     status* {.jsget.}: int
     headers* {.jsget.}: HeaderList
     redirect*: Request
+    url*: URL #TODO should be urllist?
+    unregisterFun*: proc()
  
   ReadableStream* = ref object of Stream
     isource*: Stream
@@ -203,12 +207,31 @@ func getOrDefault*(headers: HeaderList, k: string, default = ""): string =
   else:
     default
 
-proc readAll*(response: Response): string {.jsfunc.} =
+proc text*(response: Response): string {.jsfunc.} =
+  #TODO: this looks pretty unsafe.
   result = response.body.readAll()
   response.body.close()
+  response.bodyUsed = true
+  response.unregisterFun()
+
+#TODO: get rid of this
+proc readAll*(response: Response): string {.jsfunc.} =
+  return response.text()
+
+proc Response_json*(ctx: JSContext, this: JSValue, argc: cint, argv: ptr JSValue): JSValue {.cdecl.} =
+  let op = getOpaque0(this)
+  if unlikely(not ctx.isInstanceOf(this, "Response") or op == nil):
+    return JS_ThrowTypeError(ctx, "Value is not an instance of %s", "Response")
+  let response = cast[Response](op)
+  var s = response.text()
+  return JS_ParseJSON(ctx, addr s[0], cast[csize_t](s.len), cstring"<input>")
 
+#TODO: this should be a property of body
 proc close*(response: Response) {.jsfunc.} =
+  #TODO: this looks pretty unsafe
   response.body.close()
+  response.bodyUsed = true
+  response.unregisterFun()
 
 func credentialsMode*(attribute: CORSAttribute): CredentialsMode =
   case attribute
@@ -219,5 +242,5 @@ func credentialsMode*(attribute: CORSAttribute): CredentialsMode =
 
 proc addRequestModule*(ctx: JSContext) =
   ctx.registerType(Request)
-  ctx.registerType(Response)
+  ctx.registerType(Response, extra_funcs = [TabFunc(name: "json", fun: Response_json)])
   ctx.registerType(HeaderList)