about summary refs log tree commit diff stats
path: root/src/loader/loaderhandle.nim
blob: 7a9b3434a8018a5ee435a6492609e219362110e2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import std/net
import std/streams

import io/multistream
import io/posixstream
import io/serialize
import io/socketstream
import loader/headers

type LoaderHandle* = ref object
  ostream: Stream
  # Stream for taking input
  istream*: PosixStream
  # Only the first handle can be redirected, because a) mailcap can only
  # redirect the first handle and b) async redirects would result in race
  # conditions that would be difficult to untangle.
  canredir: bool
  sostream: Stream # saved ostream when redirected
  sostream_suspend: Stream # saved ostream when suspended
  fd: int

# Create a new loader handle, with the output stream ostream.
proc newLoaderHandle*(ostream: Stream, canredir: bool): LoaderHandle =
  return LoaderHandle(
    ostream: ostream,
    canredir: canredir,
    fd: int(SocketStream(ostream).source.getFd())
  )

proc getFd*(handle: LoaderHandle): int =
  return handle.fd

proc addOutputStream*(handle: LoaderHandle, stream: Stream) =
  if likely(handle.sostream_suspend != nil):
    let ms = newMultiStream(handle.sostream_suspend, stream)
    handle.sostream_suspend = ms
  else:
    # In buffer, addOutputStream is used as follows:
    # * suspend handle
    # * tee handle (-> call addOutputStream)
    # * resume handle
    # This means that this code path will never be executed, as
    # sostream_suspend is never nil when the function is called.
    # (Feel free to remove this assertion if this changes.)
    doAssert false
    let ms = newMultiStream(handle.ostream, stream)
    handle.ostream = ms

proc sendResult*(handle: LoaderHandle, res: int, msg = "") =
  handle.ostream.swrite(res)
  if res == 0: # success
    assert msg == ""
  else: # error
    handle.ostream.swrite(msg)

proc sendStatus*(handle: LoaderHandle, status: int) =
  handle.ostream.swrite(status)

proc sendHeaders*(handle: LoaderHandle, headers: Headers) =
  handle.ostream.swrite(headers)
  if handle.canredir:
    var redir: bool
    handle.ostream.sread(redir)
    if redir:
      let fd = SocketStream(handle.ostream).recvFileHandle()
      handle.sostream = handle.ostream
      let stream = newPosixStream(fd)
      handle.ostream = stream

proc sendData*(handle: LoaderHandle, p: pointer, nmemb: int) =
  handle.ostream.writeData(p, nmemb)

proc sendData*(handle: LoaderHandle, s: string) =
  if s.len > 0:
    handle.sendData(unsafeAddr s[0], s.len)

proc suspend*(handle: LoaderHandle) =
  handle.sostream_suspend = handle.ostream
  handle.ostream = newStringStream()

proc resume*(handle: LoaderHandle) =
  let ss = handle.ostream
  handle.ostream = handle.sostream_suspend
  handle.sostream_suspend = nil
  handle.sendData(ss.readAll())
  ss.close()

proc close*(handle: LoaderHandle) =
  if handle.sostream != nil:
    try:
      handle.sostream.swrite(true)
    except IOError:
      # ignore error, that just means the buffer has already closed the stream
      discard
    handle.sostream.close()
  handle.ostream.close()
  if handle.istream != nil:
    handle.istream.close()