about summary refs log tree commit diff stats
path: root/src/loader
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-02-26 15:26:46 +0100
committerbptato <nincsnevem662@gmail.com>2024-02-26 15:26:46 +0100
commit973e8f942855fe9a17f0245948984bf9841f9ffa (patch)
tree8ab60f658881df53901d4a843fd5c090b8c632bc /src/loader
parentcf1e026b85972135170c80e22b83b4567a5d1099 (diff)
downloadchawan-973e8f942855fe9a17f0245948984bf9841f9ffa.tar.gz
loader: clean up regular file loading
* Get rid of sostream hack

This is no longer needed, and was in fact causing loadStream to get
stuck with redirects on regular files (i.e. the common case of receiving
<file on stdin without a -T content type override).

* Unify loading from cache and stdin regular file code paths

Until now, loadFromCache was completely sync. This is not a huge
problem, but it's better to make it async *and* not have two separate
procedures for reading regular files. (In fact, loadFromCache had
*another* bug related to its output fd not being added to outputMap.)

* Extra: remove ansi2html select error handling

It was broken, because it didn't handle read events before the
error. Also unnecessary, since recvData breaks from the loop on n == 0.
Diffstat (limited to 'src/loader')
-rw-r--r--src/loader/loader.nim202
-rw-r--r--src/loader/loaderhandle.nim7
2 files changed, 105 insertions, 104 deletions
diff --git a/src/loader/loader.nim b/src/loader/loader.nim
index fee84782..657abfa9 100644
--- a/src/loader/loader.nim
+++ b/src/loader/loader.nim
@@ -172,32 +172,93 @@ proc pushBuffer(ctx: LoaderContext, output: OutputHandle, buffer: LoaderBuffer):
     output.addBuffer(buffer)
   return pbrDone
 
-proc addFd0(ctx: LoaderContext, handle: LoaderHandle, originalUrl: URL) =
+proc addCacheFile(ctx: LoaderContext, handle: LoaderHandle, originalUrl: URL) =
   let output = handle.output
-  if output.sostream != nil:
-    # replace the fd with the new one in outputMap if stream was
-    # redirected
-    # (kind of a hack, but should always work)
-    ctx.outputMap[output.ostream.fd] = output
-    ctx.outputMap.del(output.sostream.fd)
-    output.clientId = NullStreamId
-  if originalUrl != nil:
-    let tmpf = getTempFile(ctx.config.tmpdir)
-    let ps = newPosixStream(tmpf, O_CREAT or O_WRONLY, 0o600)
-    if ps != nil:
-      output.tee(ps, NullStreamId)
-      let surl = $originalUrl
-      ctx.cacheMap[surl] = tmpf
-      handle.cacheUrl = surl
-
-proc addFd(ctx: LoaderContext, handle: LoaderHandle, originalUrl: URL) =
+  let tmpf = getTempFile(ctx.config.tmpdir)
+  let ps = newPosixStream(tmpf, O_CREAT or O_WRONLY, 0o600)
+  if ps != nil:
+    output.tee(ps, NullStreamId)
+    let surl = $originalUrl
+    ctx.cacheMap[surl] = tmpf
+    handle.cacheUrl = surl
+
+proc addFd(ctx: LoaderContext, handle: LoaderHandle) =
   let output = handle.output
   output.ostream.setBlocking(false)
   handle.istream.setBlocking(false)
   ctx.selector.registerHandle(handle.istream.fd, {Read}, 0)
+  assert handle.istream.fd notin ctx.handleMap
+  assert output.ostream.fd notin ctx.outputMap
   ctx.handleMap[handle.istream.fd] = handle
   ctx.outputMap[output.ostream.fd] = output
-  ctx.addFd0(handle, originalUrl)
+
+type HandleReadResult = enum
+  hrrDone, hrrEmpty, hrrUnregister
+
+# Called whenever there is more data available to read.
+proc handleRead(ctx: LoaderContext, handle: LoaderHandle,
+    unregWrite: var seq[OutputHandle]): HandleReadResult =
+  while true:
+    let buffer = newLoaderBuffer()
+    try:
+      let n = handle.istream.recvData(buffer)
+      if n == 0:
+        return hrrEmpty
+      for output in handle.outputs:
+        if ctx.pushBuffer(output, buffer) == pbrUnregister:
+          unregWrite.add(output)
+      if n < buffer.cap:
+        break
+    except ErrorAgain: # retry later
+      break
+    except ErrorBrokenPipe: # sender died; stop streaming
+      return hrrUnregister
+  hrrDone
+
+# stream is a regular file, so we can't select on it.
+# cachedHandle is used for attaching the output handle to a different
+# LoaderHandle when loadFromCache is called while a download is still ongoing
+# (and thus some parts of the document are not cached yet).
+proc loadStreamRegular(ctx: LoaderContext, handle, cachedHandle: LoaderHandle) =
+  var fail = false
+  while true:
+    var unregWrite: seq[OutputHandle] = @[]
+    case ctx.handleRead(handle, unregWrite)
+    of hrrDone: discard
+    of hrrEmpty: break
+    of hrrUnregister:
+      fail = true
+      break
+    for output in unregWrite:
+      output.parent = nil
+      let i = handle.outputs.find(output)
+      if output.registered:
+        ctx.selector.unregister(output.ostream.fd)
+        output.registered = false
+      handle.outputs.del(i)
+    if handle.outputs.len == 0:
+      # original output died and so did the cache file. (or we didn't have a
+      # cache file in the first place)
+      break
+  for output in handle.outputs:
+    if unlikely(fail):
+      output.ostream.close()
+      output.ostream = nil
+    elif cachedHandle != nil:
+      output.parent = cachedHandle
+      cachedHandle.outputs.add(output)
+      ctx.outputMap[output.ostream.fd] = output
+    elif output.registered:
+      output.parent = nil
+      output.istreamAtEnd = true
+      ctx.outputMap[output.ostream.fd] = output
+    else:
+      assert output.ostream.fd notin ctx.outputMap
+      output.ostream.close()
+      output.ostream = nil
+  handle.outputs.setLen(0)
+  handle.istream.close()
+  handle.istream = nil
 
 proc loadStream(ctx: LoaderContext, handle: LoaderHandle, request: Request,
     originalUrl: URL) =
@@ -211,44 +272,12 @@ proc loadStream(ctx: LoaderContext, handle: LoaderHandle, request: Request,
     doAssert fstat(fdp[], stats) != -1
     handle.istream = ps
     ctx.passedFdMap.del(request.url.host)
-    if S_ISREG(stats.st_mode):
-      # stdin is a regular file, so we can't select on it.
-      let originalUrl = if handle.cached: originalUrl else: nil
+    if S_ISREG(stats.st_mode): # probably stdin, like cha <file
       handle.output.ostream.setBlocking(false)
-      ctx.addFd0(handle, originalUrl)
-      while true:
-        let buffer = newLoaderBuffer()
-        let n = ps.recvData(buffer)
-        if n == 0:
-          break
-        var unregWrite: seq[OutputHandle] = @[]
-        for output in handle.outputs:
-          if ctx.pushBuffer(output, buffer) == pbrUnregister:
-            unregWrite.add(output)
-        for output in unregWrite:
-          output.parent = nil
-          let i = handle.outputs.find(output)
-          if output.registered:
-            ctx.selector.unregister(output.ostream.fd)
-            output.registered = false
-          handle.outputs.del(i)
-        if handle.outputs.len == 0:
-          # original output died and so did the cache file. (or we didn't have a
-          # cache file in the first place)
-          break
-        if n < buffer.cap:
-          break
-      for output in handle.outputs:
-        if output.registered:
-          output.parent = nil
-          output.istreamAtEnd = true
-          ctx.outputMap[output.ostream.fd] = output
-        else:
-          output.ostream.close()
-          output.ostream = nil
-      handle.outputs.setLen(0)
-      handle.istream.close()
-      handle.istream = nil
+      if handle.cached:
+        ctx.addCacheFile(handle, originalUrl)
+      # not loading from cache, so cachedHandle is nil
+      ctx.loadStreamRegular(handle, nil)
   do:
     handle.sendResult(ERROR_FILE_NOT_FOUND, "stream not found")
 
@@ -272,15 +301,17 @@ proc loadResource(ctx: LoaderContext, request: Request, handle: LoaderHandle) =
       handle.loadCGI(request, ctx.config.cgiDir, ctx.config.libexecPath,
         prevurl)
       if handle.istream != nil:
-        let originalUrl = if handle.cached: originalUrl else: nil
-        ctx.addFd(handle, originalUrl)
+        ctx.addFd(handle)
+        if handle.cached:
+          ctx.addCacheFile(handle, originalUrl)
       else:
         handle.close()
     elif request.url.scheme == "stream":
       ctx.loadStream(handle, request, originalUrl)
       if handle.istream != nil:
-        let originalUrl = if handle.cached: originalUrl else: nil
-        ctx.addFd(handle, originalUrl)
+        ctx.addFd(handle)
+        if handle.cached:
+          ctx.addCacheFile(handle, originalUrl)
       else:
         handle.close()
     else:
@@ -308,39 +339,18 @@ proc loadFromCache(ctx: LoaderContext, stream: SocketStream, request: Request) =
       ctx.cacheMap.del(surl)
       handle.close()
       return
+    handle.istream = ps
     handle.sendResult(0)
     handle.sendStatus(200)
     handle.sendHeaders(newHeaders())
     if handle.cached:
       handle.cacheUrl = surl
     output.ostream.setBlocking(false)
-    while true:
-      let buffer = newLoaderBuffer()
-      let n = ps.recvData(buffer)
-      if n == 0:
-        break
-      if ctx.pushBuffer(output, buffer) == pbrUnregister:
-        if output.registered:
-          ctx.selector.unregister(output.ostream.fd)
-        ps.close()
-        return
-      if n < buffer.cap:
-        break
-    ps.close()
+    ctx.loadStreamRegular(handle, cachedHandle)
   do:
     if cachedHandle == nil:
       handle.rejectHandle(ERROR_URL_NOT_IN_CACHE)
       return
-  if cachedHandle != nil:
-    # download is still ongoing; move output to the original handle
-    handle.outputs.setLen(0)
-    output.parent = cachedHandle
-    cachedHandle.outputs.add(output)
-  elif output.registered:
-    output.istreamAtEnd = true
-    ctx.outputMap[output.ostream.fd] = output
-  else:
-    output.ostream.close()
 
 proc onLoad(ctx: LoaderContext, stream: SocketStream) =
   var request: Request
@@ -479,26 +489,6 @@ proc initLoaderContext(fd: cint, config: LoaderConfig): LoaderContext =
       dir &= '/'
   return ctx
 
-# Called whenever there is more data available to read.
-proc handleRead(ctx: LoaderContext, handle: LoaderHandle,
-    unregRead: var seq[LoaderHandle], unregWrite: var seq[OutputHandle]) =
-  while true:
-    let buffer = newLoaderBuffer()
-    try:
-      let n = handle.istream.recvData(buffer)
-      if n == 0:
-        break
-      for output in handle.outputs:
-        if ctx.pushBuffer(output, buffer) == pbrUnregister:
-          unregWrite.add(output)
-      if n < buffer.cap:
-        break
-    except ErrorAgain: # retry later
-      break
-    except ErrorBrokenPipe: # sender died; stop streaming
-      unregRead.add(handle)
-      break
-
 # This is only called when an OutputHandle could not read enough of one (or
 # more) buffers, and we asked select to notify us when it will be available.
 proc handleWrite(ctx: LoaderContext, output: OutputHandle,
@@ -593,7 +583,11 @@ proc runFileLoader*(fd: cint, config: LoaderConfig) =
         if event.fd == ctx.fd: # incoming connection
           ctx.acceptConnection()
         else:
-          ctx.handleRead(ctx.handleMap[event.fd], unregRead, unregWrite)
+          let handle = ctx.handleMap[event.fd]
+          case ctx.handleRead(handle, unregWrite)
+          of hrrDone: discard
+          of hrrEmpty: discard # handled as an error event
+          of hrrUnregister: unregRead.add(handle)
       if Write in event.events:
         ctx.handleWrite(ctx.outputMap[event.fd], unregWrite)
       if Error in event.events:
diff --git a/src/loader/loaderhandle.nim b/src/loader/loaderhandle.nim
index eb6d61ba..24f4a584 100644
--- a/src/loader/loaderhandle.nim
+++ b/src/loader/loaderhandle.nim
@@ -51,6 +51,12 @@ type
       dealloc(buffer.page)
       buffer.page = nil
 
+# for debugging
+func `$`*(buffer: LoaderBuffer): string =
+  var s = newString(buffer.len)
+  copyMem(addr s[0], addr buffer.page[0], buffer.len)
+  return s
+
 # Create a new loader handle, with the output stream ostream.
 proc newLoaderHandle*(ostream: PosixStream, canredir: bool, clientId: StreamId):
     LoaderHandle =
@@ -137,6 +143,7 @@ proc sendHeaders*(handle: LoaderHandle, headers: Headers) =
       let fd = sostream.recvFileHandle()
       output.sostream = sostream
       output.ostream = newPosixStream(fd)
+      output.clientId = NullStreamId
 
 proc recvData*(ps: PosixStream, buffer: LoaderBuffer): int {.inline.} =
   let n = ps.recvData(addr buffer.page[0], buffer.cap)