about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--adapter/img/canvas.nim3
-rw-r--r--src/html/dom.nim45
-rw-r--r--src/img/painter.nim32
-rw-r--r--src/img/path.nim26
-rw-r--r--src/io/bufreader.nim78
-rw-r--r--src/io/bufwriter.nim59
-rw-r--r--src/loader/request.nim20
-rw-r--r--src/types/blob.nim38
-rw-r--r--src/types/formdata.nim25
-rw-r--r--src/types/url.nim26
11 files changed, 167 insertions, 192 deletions
diff --git a/Makefile b/Makefile
index c99165b4..6093c1f6 100644
--- a/Makefile
+++ b/Makefile
@@ -112,10 +112,9 @@ $(OUTDIR_CGI_BIN)/stbi: adapter/img/stbi.nim adapter/img/stb_image.c \
 $(OUTDIR_CGI_BIN)/jebp: adapter/img/jebp.c adapter/img/jebp.h \
 		src/utils/sandbox.nim
 $(OUTDIR_CGI_BIN)/sixel: src/types/color.nim src/utils/sandbox.nim $(twtstr)
-$(OUTDIR_CGI_BIN)/canvas: src/css/cssvalues.nim src/img/bitmap.nim \
-	src/img/painter.nim src/img/path.nim src/io/bufreader.nim \
-	src/types/color.nim src/types/line.nim src/utils/sandbox.nim \
-	$(dynstream)
+$(OUTDIR_CGI_BIN)/canvas: src/img/bitmap.nim src/img/painter.nim \
+	src/img/path.nim src/io/bufreader.nim src/types/color.nim \
+	src/types/line.nim src/utils/sandbox.nim $(dynstream) $(twtstr)
 $(OUTDIR_LIBEXEC)/urlenc: $(twtstr)
 $(OUTDIR_LIBEXEC)/gopher2html: adapter/gophertypes.nim $(twtstr)
 $(OUTDIR_LIBEXEC)/ansi2html: src/types/color.nim $(twtstr)
diff --git a/adapter/img/canvas.nim b/adapter/img/canvas.nim
index 2182e1d9..32d6d37a 100644
--- a/adapter/img/canvas.nim
+++ b/adapter/img/canvas.nim
@@ -10,7 +10,6 @@ import std/os
 import std/posix
 import std/strutils
 
-import css/cssvalues
 import img/bitmap
 import img/painter
 import img/path
@@ -121,7 +120,7 @@ proc main() =
             var text: string
             var x, y: float64
             var color: ARGBColor
-            var align: CSSTextAlign
+            var align: CanvasTextAlign
             r.sread(text)
             r.sread(x)
             r.sread(y)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index e46d0078..9fc393c0 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -360,7 +360,7 @@ type
     # CanvasPathDrawingStyles
     lineWidth: float64
     # CanvasTextDrawingStyles
-    textAlign: CSSTextAlign
+    textAlign: CanvasTextAlign
     # CanvasPath
     path: Path
 
@@ -595,7 +595,7 @@ proc strokePath(ctx: CanvasRenderingContext2D; path: Path; color: ARGBColor) =
       w.swrite(color)
 
 proc fillText(ctx: CanvasRenderingContext2D; text: string; x, y: float64;
-    color: ARGBColor; align: CSSTextAlign) =
+    color: ARGBColor; align: CanvasTextAlign) =
   if ctx.ps != nil:
     ctx.ps.withPacketWriter w:
       w.swrite(pcFillText)
@@ -606,7 +606,7 @@ proc fillText(ctx: CanvasRenderingContext2D; text: string; x, y: float64;
       w.swrite(align)
 
 proc strokeText(ctx: CanvasRenderingContext2D; text: string; x, y: float64;
-    color: ARGBColor; align: CSSTextAlign) =
+    color: ARGBColor; align: CanvasTextAlign) =
   if ctx.ps != nil:
     ctx.ps.withPacketWriter w:
       w.swrite(pcStrokeText)
@@ -826,22 +826,12 @@ proc getLineDash(ctx: CanvasRenderingContext2D): seq[float64] {.jsfunc.} =
 
 # CanvasTextDrawingStyles
 proc textAlign(ctx: CanvasRenderingContext2D): string {.jsfget.} =
-  case ctx.state.textAlign
-  of TextAlignStart: return "start"
-  of TextAlignEnd: return "end"
-  of TextAlignLeft: return "left"
-  of TextAlignRight: return "right"
-  of TextAlignCenter: return "center"
-  else: doAssert false
+  return $ctx.state.textAlign
 
 proc textAlign(ctx: CanvasRenderingContext2D; s: string) {.jsfset.} =
-  ctx.state.textAlign = case s
-  of "start": TextAlignStart
-  of "end": TextAlignEnd
-  of "left": TextAlignLeft
-  of "right": TextAlignRight
-  of "center": TextAlignCenter
-  else: ctx.state.textAlign
+  let x = parseEnumNoCase[CanvasTextAlign](s)
+  if x.isSome:
+    ctx.state.textAlign = x.get
 
 # CanvasPath
 proc closePath(ctx: CanvasRenderingContext2D) {.jsfunc.} =
@@ -859,19 +849,30 @@ proc quadraticCurveTo(ctx: CanvasRenderingContext2D; cpx, cpy, x,
 
 proc arcTo(ctx: CanvasRenderingContext2D; x1, y1, x2, y2, radius: float64):
     Err[DOMException] {.jsfunc.} =
-  return ctx.state.path.arcTo(x1, y1, x2, y2, radius)
+  if radius < 0:
+    return errDOMException("Expected positive radius, but got negative",
+      "IndexSizeError")
+  ctx.state.path.arcTo(x1, y1, x2, y2, radius)
+  return ok()
 
 proc arc(ctx: CanvasRenderingContext2D; x, y, radius, startAngle,
     endAngle: float64; counterclockwise = false): Err[DOMException]
     {.jsfunc.} =
-  return ctx.state.path.arc(x, y, radius, startAngle, endAngle,
-    counterclockwise)
+  if radius < 0:
+    return errDOMException("Expected positive radius, but got negative",
+      "IndexSizeError")
+  ctx.state.path.arc(x, y, radius, startAngle, endAngle, counterclockwise)
+  return ok()
 
 proc ellipse(ctx: CanvasRenderingContext2D; x, y, radiusX, radiusY, rotation,
     startAngle, endAngle: float64; counterclockwise = false): Err[DOMException]
     {.jsfunc.} =
-  return ctx.state.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle,
-    endAngle, counterclockwise)
+  if radiusX < 0 or radiusY < 0:
+    return errDOMException("Expected positive radius, but got negative",
+      "IndexSizeError")
+  ctx.state.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle,
+    counterclockwise)
+  return ok()
 
 proc rect(ctx: CanvasRenderingContext2D; x, y, w, h: float64) {.jsfunc.} =
   ctx.state.path.rect(x, y, w, h)
diff --git a/src/img/painter.nim b/src/img/painter.nim
index 0479bd77..c86ad0a2 100644
--- a/src/img/painter.nim
+++ b/src/img/painter.nim
@@ -1,20 +1,27 @@
 import std/algorithm
 import std/unicode
 
-import css/cssvalues
 import img/bitmap
 import img/path
 import types/color
 import types/line
 import types/vector
 
-type CanvasFillRule* = enum
-  cfrNonZero = "nonzero"
-  cfrEvenOdd = "evenodd"
+type
+  CanvasFillRule* = enum
+    cfrNonZero = "nonzero"
+    cfrEvenOdd = "evenodd"
 
-type PaintCommand* = enum
-  pcSetDimensions, pcFillRect, pcStrokeRect, pcFillPath, pcStrokePath,
-  pcFillText, pcStrokeText
+  PaintCommand* = enum
+    pcSetDimensions, pcFillRect, pcStrokeRect, pcFillPath, pcStrokePath,
+    pcFillText, pcStrokeText
+
+  CanvasTextAlign* = enum
+    ctaStart = "start"
+    ctaEnd = "end"
+    ctaLeft = "left"
+    ctaRight = "right"
+    ctaCenter = "center"
 
 # https://en.wikipedia.org/wiki/Bresenham's_line_algorithm#All_cases
 proc plotLineLow(bmp: Bitmap; x1, y1, x2, y2: int; color: ARGBColor) =
@@ -181,7 +188,7 @@ proc drawBitmap(a, b: Bitmap; p: Vector2D) =
         a.setpxb(ax, ay, b.getpx(x, y))
 
 proc fillText*(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
-    textAlign: CSSTextAlign) =
+    textAlign: CanvasTextAlign) =
   var w = 0f64
   var glyphs: seq[Bitmap] = @[]
   for r in text.runes:
@@ -191,15 +198,14 @@ proc fillText*(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
   var x = x
   #TODO rtl
   case textAlign
-  of TextAlignLeft, TextAlignStart: discard
-  of TextAlignRight, TextAlignEnd: x -= w
-  of TextAlignCenter: x -= w / 2
-  else: doAssert false
+  of ctaLeft, ctaStart: discard
+  of ctaRight, ctaEnd: x -= w
+  of ctaCenter: x -= w / 2
   for glyph in glyphs:
     bmp.drawBitmap(glyph, Vector2D(x: x, y: y - 8))
     x += float64(glyph.width)
 
 proc strokeText*(bmp: Bitmap; text: string; x, y: float64; color: ARGBColor;
-    textAlign: CSSTextAlign) =
+    textAlign: CanvasTextAlign) =
   #TODO
   bmp.fillText(text, x, y, color, textAlign)
diff --git a/src/img/path.nim b/src/img/path.nim
index 5c1ce680..c5db0bea 100644
--- a/src/img/path.nim
+++ b/src/img/path.nim
@@ -4,8 +4,6 @@ import std/math
 
 import types/line
 import types/vector
-import js/domexception
-import types/opt
 
 type
   Path* = ref object
@@ -303,13 +301,10 @@ proc bezierCurveTo*(path: Path; cp0x, cp0y, cp1x, cp1y, x, y: float64) =
   let p = Vector2D(x: x, y: y)
   path.addBezierSegment(cp0, cp1, p)
 
-proc arcTo*(path: Path; x1, y1, x2, y2, radius: float64): Err[DOMException] =
+proc arcTo*(path: Path; x1, y1, x2, y2, radius: float64) =
   for v in [x1, y1, x2, y2, radius]:
     if classify(v) in {fcInf, fcNegInf, fcNan}:
-      return ok()
-  if radius < 0:
-    return errDOMException("Expected positive radius, but got negative",
-      "IndexSizeError")
+      return
   path.ensureSubpath(x1, y1)
   #TODO this should be transformed by the inverse of the transformation matrix
   let v0 = path.subpaths[^1].points[^1]
@@ -333,7 +328,6 @@ proc arcTo*(path: Path; x1, y1, x2, y2, radius: float64): Err[DOMException] =
     )
     path.addStraightSegment(tv0)
     path.addArcSegment(origin, tv2, radius, true) #TODO always inner?
-  return ok()
 
 func resolveEllipsePoint(o: Vector2D; angle, radiusX, radiusY,
     rotation: float64): Vector2D =
@@ -350,13 +344,10 @@ func resolveEllipsePoint(o: Vector2D; angle, radiusX, radiusY,
   return Vector2D(x: relx, y: rely).rotate(rotation) + o
 
 proc arc*(path: Path; x, y, radius, startAngle, endAngle: float64;
-    counterclockwise: bool): Err[DOMException] =
+    counterclockwise: bool) =
   for v in [x, y, radius, startAngle, endAngle]:
     if classify(v) in {fcInf, fcNegInf, fcNan}:
-      return ok()
-  if radius < 0:
-    return errDOMException("Expected positive radius, but got negative",
-      "IndexSizeError")
+      return
   let o = Vector2D(x: x, y: y)
   var s = resolveEllipsePoint(o, startAngle, radius, radius, 0)
   var e = resolveEllipsePoint(o, endAngle, radius, radius, 0)
@@ -369,16 +360,12 @@ proc arc*(path: Path; x, y, radius, startAngle, endAngle: float64;
   else:
     path.moveTo(s)
   path.addArcSegment(o, e, radius, abs(startAngle - endAngle) < PI)
-  return ok()
 
 proc ellipse*(path: Path; x, y, radiusX, radiusY, rotation, startAngle,
-    endAngle: float64; counterclockwise: bool): Err[DOMException] =
+    endAngle: float64; counterclockwise: bool) =
   for v in [x, y, radiusX, radiusY, rotation, startAngle, endAngle]:
     if classify(v) in {fcInf, fcNegInf, fcNan}:
-      return ok()
-  if radiusX < 0 or radiusY < 0:
-    return errDOMException("Expected positive radius, but got negative",
-      "IndexSizeError")
+      return
   let o = Vector2D(x: x, y: y)
   var s = resolveEllipsePoint(o, startAngle, radiusX, radiusY, rotation)
   var e = resolveEllipsePoint(o, endAngle, radiusX, radiusY, rotation)
@@ -391,7 +378,6 @@ proc ellipse*(path: Path; x, y, radiusX, radiusY, rotation, startAngle,
   else:
     path.moveTo(s)
   path.addEllipseSegment(o, e, radiusX, radiusY)
-  return ok()
 
 proc rect*(path: Path; x, y, w, h: float64) =
   for v in [x, y, w, h]:
diff --git a/src/io/bufreader.nim b/src/io/bufreader.nim
index 8dc7b0a2..46f8e0f3 100644
--- a/src/io/bufreader.nim
+++ b/src/io/bufreader.nim
@@ -2,39 +2,29 @@ import std/options
 import std/sets
 import std/tables
 
-import img/bitmap
 import io/dynstream
-import loader/request
-import types/blob
 import types/color
-import types/formdata
 import types/opt
-import types/url
 
 type BufferedReader* = object
   buffer: seq[uint8]
   bufIdx: int
-  recvAux: seq[FileHandle] #TODO assert on unused ones
+  recvAux*: seq[FileHandle] #TODO assert on unused ones
 
 proc sread*(reader: var BufferedReader; n: var SomeNumber)
 proc sread*[T](reader: var BufferedReader; s: var set[T])
 proc sread*[T: enum](reader: var BufferedReader; x: var T)
 proc sread*(reader: var BufferedReader; s: var string)
 proc sread*(reader: var BufferedReader; b: var bool)
-proc sread*(reader: var BufferedReader; url: var URL)
 proc sread*(reader: var BufferedReader; tup: var tuple)
 proc sread*[I, T](reader: var BufferedReader; a: var array[I, T])
 proc sread*(reader: var BufferedReader; s: var seq)
 proc sread*[U, V](reader: var BufferedReader; t: var Table[U, V])
 proc sread*(reader: var BufferedReader; obj: var object)
 proc sread*(reader: var BufferedReader; obj: var ref object)
-proc sread*(reader: var BufferedReader; part: var FormDataEntry)
-proc sread*(reader: var BufferedReader; blob: var Blob)
 proc sread*[T](reader: var BufferedReader; o: var Option[T])
 proc sread*[T, E](reader: var BufferedReader; o: var Result[T, E])
-proc sread*(reader: var BufferedReader; c: var ARGBColor) {.inline.}
-proc sread*(reader: var BufferedReader; o: var RequestBody)
-proc sread*(reader: var BufferedReader; bmp: var NetworkBitmap)
+proc sread*(reader: var BufferedReader; c: var ARGBColor)
 
 proc initReader*(stream: DynStream; len, auxLen: int): BufferedReader =
   assert len != 0
@@ -57,7 +47,7 @@ template withPacketReader*(stream: DynStream; r, body: untyped) =
     var r = stream.initPacketReader()
     body
 
-proc readData(reader: var BufferedReader; buffer: pointer; len: int) =
+proc readData*(reader: var BufferedReader; buffer: pointer; len: int) =
   assert reader.bufIdx + len <= reader.buffer.len
   copyMem(buffer, addr reader.buffer[reader.bufIdx], len)
   reader.bufIdx += len
@@ -94,18 +84,6 @@ proc sread*(reader: var BufferedReader; b: var bool) =
     assert n == 0u8
     b = false
 
-proc sread*(reader: var BufferedReader; url: var URL) =
-  var s: string
-  reader.sread(s)
-  if s == "":
-    url = nil
-  else:
-    let x = newURL(s)
-    if x.isSome:
-      url = x.get
-    else:
-      url = nil
-
 proc sread*(reader: var BufferedReader; tup: var tuple) =
   for f in tup.fields:
     reader.sread(f)
@@ -142,38 +120,6 @@ proc sread*(reader: var BufferedReader; obj: var ref object) =
     new(obj)
     reader.sread(obj[])
 
-proc sread*(reader: var BufferedReader; part: var FormDataEntry) =
-  var isstr: bool
-  reader.sread(isstr)
-  if isstr:
-    part = FormDataEntry(isstr: true)
-  else:
-    part = FormDataEntry(isstr: false)
-  reader.sread(part.name)
-  reader.sread(part.filename)
-  if part.isstr:
-    reader.sread(part.svalue)
-  else:
-    reader.sread(part.value)
-
-proc sread*(reader: var BufferedReader; blob: var Blob) =
-  var isWebFile: bool
-  reader.sread(isWebFile)
-  blob = if isWebFile: WebFile() else: Blob()
-  if isWebFile:
-    reader.sread(WebFile(blob).name)
-  var hasFd: bool
-  reader.sread(hasFd)
-  if hasFd:
-    blob.fd = some(reader.recvAux.pop())
-  reader.sread(blob.ctype)
-  reader.sread(blob.size)
-  if blob.size > 0:
-    let buffer = alloc(blob.size)
-    reader.readData(blob.buffer, int(blob.size))
-    blob.buffer = buffer
-    blob.deallocFun = deallocBlob
-
 proc sread*[T](reader: var BufferedReader; o: var Option[T]) =
   var x: bool
   reader.sread(x)
@@ -204,21 +150,3 @@ proc sread*[T, E](reader: var BufferedReader; o: var Result[T, E]) =
 
 proc sread*(reader: var BufferedReader; c: var ARGBColor) =
   reader.sread(uint32(c))
-
-proc sread*(reader: var BufferedReader; o: var RequestBody) =
-  var t: RequestBodyType
-  reader.sread(t)
-  o = RequestBody(t: t)
-  case t
-  of rbtNone: discard
-  of rbtString: reader.sread(o.s)
-  of rbtMultipart: reader.sread(o.multipart)
-  of rbtOutput: reader.sread(o.outputId)
-
-proc sread*(reader: var BufferedReader; bmp: var NetworkBitmap) =
-  bmp = NetworkBitmap()
-  reader.sread(bmp.width)
-  reader.sread(bmp.height)
-  reader.sread(bmp.cacheId)
-  reader.sread(bmp.imageId)
-  reader.sread(bmp.contentType)
diff --git a/src/io/bufwriter.nim b/src/io/bufwriter.nim
index ed4cb725..77b8ebd8 100644
--- a/src/io/bufwriter.nim
+++ b/src/io/bufwriter.nim
@@ -5,21 +5,16 @@ import std/options
 import std/sets
 import std/tables
 
-import img/bitmap
 import io/dynstream
-import loader/request
-import types/blob
 import types/color
-import types/formdata
 import types/opt
-import types/url
 
 type BufferedWriter* = object
   stream: DynStream
   buffer: ptr UncheckedArray[uint8]
   bufSize: int
   bufLen: int
-  sendAux: seq[FileHandle]
+  sendAux*: seq[FileHandle]
 
 proc `=destroy`(writer: var BufferedWriter) =
   if writer.buffer != nil:
@@ -31,20 +26,15 @@ proc swrite*[T](writer: var BufferedWriter; s: set[T])
 proc swrite*[T: enum](writer: var BufferedWriter; x: T)
 proc swrite*(writer: var BufferedWriter; s: string)
 proc swrite*(writer: var BufferedWriter; b: bool)
-proc swrite*(writer: var BufferedWriter; url: URL)
 proc swrite*(writer: var BufferedWriter; tup: tuple)
 proc swrite*[I, T](writer: var BufferedWriter; a: array[I, T])
 proc swrite*[T](writer: var BufferedWriter; s: openArray[T])
 proc swrite*[U, V](writer: var BufferedWriter; t: Table[U, V])
 proc swrite*(writer: var BufferedWriter; obj: object)
 proc swrite*(writer: var BufferedWriter; obj: ref object)
-proc swrite*(writer: var BufferedWriter; part: FormDataEntry)
-proc swrite*(writer: var BufferedWriter; blob: Blob)
 proc swrite*[T](writer: var BufferedWriter; o: Option[T])
 proc swrite*[T, E](writer: var BufferedWriter; o: Result[T, E])
-proc swrite*(writer: var BufferedWriter; c: ARGBColor) {.inline.}
-proc swrite*(writer: var BufferedWriter; o: RequestBody)
-proc swrite*(writer: var BufferedWriter; bmp: NetworkBitmap)
+proc swrite*(writer: var BufferedWriter; c: ARGBColor)
 
 const InitLen = sizeof(int) * 2
 const SizeInit = max(64, InitLen)
@@ -81,7 +71,7 @@ template withPacketWriter*(stream: DynStream; w, body: untyped) =
     w.flush()
     w.deinit()
 
-proc writeData(writer: var BufferedWriter; buffer: pointer; len: int) =
+proc writeData*(writer: var BufferedWriter; buffer: pointer; len: int) =
   let targetLen = writer.bufLen + len
   let missing = targetLen - writer.bufSize
   if missing > 0:
@@ -118,12 +108,6 @@ proc swrite*(writer: var BufferedWriter; b: bool) =
   else:
     writer.swrite(0u8)
 
-proc swrite*(writer: var BufferedWriter; url: URL) =
-  if url != nil:
-    writer.swrite(url.serialize())
-  else:
-    writer.swrite("")
-
 proc swrite*(writer: var BufferedWriter; tup: tuple) =
   for f in tup.fields:
     writer.swrite(f)
@@ -152,28 +136,6 @@ proc swrite*(writer: var BufferedWriter; obj: ref object) =
   if obj != nil:
     writer.swrite(obj[])
 
-proc swrite*(writer: var BufferedWriter; part: FormDataEntry) =
-  writer.swrite(part.isstr)
-  writer.swrite(part.name)
-  writer.swrite(part.filename)
-  if part.isstr:
-    writer.swrite(part.svalue)
-  else:
-    writer.swrite(part.value)
-
-#TODO clean up this mess
-proc swrite*(writer: var BufferedWriter; blob: Blob) =
-  if blob.fd.isSome:
-    writer.sendAux.add(blob.fd.get)
-  writer.swrite(blob of WebFile)
-  if blob of WebFile:
-    writer.swrite(WebFile(blob).name)
-  writer.swrite(blob.fd.isSome)
-  writer.swrite(blob.ctype)
-  writer.swrite(blob.size)
-  if blob.size > 0:
-    writer.writeData(blob.buffer, int(blob.size))
-
 proc swrite*[T](writer: var BufferedWriter; o: Option[T]) =
   writer.swrite(o.isSome)
   if o.isSome:
@@ -190,18 +152,3 @@ proc swrite*[T, E](writer: var BufferedWriter; o: Result[T, E]) =
 
 proc swrite*(writer: var BufferedWriter; c: ARGBColor) =
   writer.swrite(uint32(c))
-
-proc swrite*(writer: var BufferedWriter; o: RequestBody) =
-  writer.swrite(o.t)
-  case o.t
-  of rbtNone: discard
-  of rbtString: writer.swrite(o.s)
-  of rbtMultipart: writer.swrite(o.multipart)
-  of rbtOutput: writer.swrite(o.outputId)
-
-proc swrite*(writer: var BufferedWriter; bmp: NetworkBitmap) =
-  writer.swrite(bmp.width)
-  writer.swrite(bmp.height)
-  writer.swrite(bmp.cacheId)
-  writer.swrite(bmp.imageId)
-  writer.swrite(bmp.contentType)
diff --git a/src/loader/request.nim b/src/loader/request.nim
index 2af5dcf3..c210898d 100644
--- a/src/loader/request.nim
+++ b/src/loader/request.nim
@@ -2,6 +2,8 @@ import std/options
 import std/tables
 
 import html/script
+import io/bufreader
+import io/bufwriter
 import loader/headers
 import monoucha/fromjs
 import monoucha/javascript
@@ -90,6 +92,24 @@ type
 
 jsDestructor(JSRequest)
 
+proc swrite*(writer: var BufferedWriter; o: RequestBody) =
+  writer.swrite(o.t)
+  case o.t
+  of rbtNone: discard
+  of rbtString: writer.swrite(o.s)
+  of rbtMultipart: writer.swrite(o.multipart)
+  of rbtOutput: writer.swrite(o.outputId)
+
+proc sread*(reader: var BufferedReader; o: var RequestBody) =
+  var t: RequestBodyType
+  reader.sread(t)
+  o = RequestBody(t: t)
+  case t
+  of rbtNone: discard
+  of rbtString: reader.sread(o.s)
+  of rbtMultipart: reader.sread(o.multipart)
+  of rbtOutput: reader.sread(o.outputId)
+
 proc contentLength*(body: RequestBody): int =
   case body.t
   of rbtNone: return 0
diff --git a/src/types/blob.nim b/src/types/blob.nim
index c0278bde..cc69fd7c 100644
--- a/src/types/blob.nim
+++ b/src/types/blob.nim
@@ -2,6 +2,8 @@ import std/options
 import std/posix
 import std/strutils
 
+import io/bufreader
+import io/bufwriter
 import monoucha/fromjs
 import monoucha/javascript
 import monoucha/jstypes
@@ -25,6 +27,42 @@ type
 jsDestructor(Blob)
 jsDestructor(WebFile)
 
+# Forward declarations
+proc deallocBlob*(opaque, p: pointer) {.raises: [].}
+
+#TODO it would be nice if we had a separate fd type that does sendAux;
+# this solution fails when blob isn't swritten by some module that does
+# not import it (just as a transitive dependency).
+proc swrite*(writer: var BufferedWriter; blob: Blob) =
+  if blob.fd.isSome:
+    writer.sendAux.add(blob.fd.get)
+  writer.swrite(blob of WebFile)
+  if blob of WebFile:
+    writer.swrite(WebFile(blob).name)
+  writer.swrite(blob.fd.isSome)
+  writer.swrite(blob.ctype)
+  writer.swrite(blob.size)
+  if blob.size > 0:
+    writer.writeData(blob.buffer, int(blob.size))
+
+proc sread*(reader: var BufferedReader; blob: var Blob) =
+  var isWebFile: bool
+  reader.sread(isWebFile)
+  blob = if isWebFile: WebFile() else: Blob()
+  if isWebFile:
+    reader.sread(WebFile(blob).name)
+  var hasFd: bool
+  reader.sread(hasFd)
+  if hasFd:
+    blob.fd = some(reader.recvAux.pop())
+  reader.sread(blob.ctype)
+  reader.sread(blob.size)
+  if blob.size > 0:
+    let buffer = alloc(blob.size)
+    reader.readData(blob.buffer, int(blob.size))
+    blob.buffer = buffer
+    blob.deallocFun = deallocBlob
+
 proc newBlob*(buffer: pointer; size: int; ctype: string;
     deallocFun: DeallocFun; opaque: pointer = nil): Blob =
   return Blob(
diff --git a/src/types/formdata.nim b/src/types/formdata.nim
index 9ce881f0..c57eda10 100644
--- a/src/types/formdata.nim
+++ b/src/types/formdata.nim
@@ -1,5 +1,7 @@
 import std/strutils
 
+import io/bufreader
+import io/bufwriter
 import io/dynstream
 import monoucha/javascript
 import types/blob
@@ -21,6 +23,29 @@ type
 
 jsDestructor(FormData)
 
+proc swrite*(writer: var BufferedWriter; part: FormDataEntry) =
+  writer.swrite(part.isstr)
+  writer.swrite(part.name)
+  writer.swrite(part.filename)
+  if part.isstr:
+    writer.swrite(part.svalue)
+  else:
+    writer.swrite(part.value)
+
+proc sread*(reader: var BufferedReader; part: var FormDataEntry) =
+  var isstr: bool
+  reader.sread(isstr)
+  if isstr:
+    part = FormDataEntry(isstr: true)
+  else:
+    part = FormDataEntry(isstr: false)
+  reader.sread(part.name)
+  reader.sread(part.filename)
+  if part.isstr:
+    reader.sread(part.svalue)
+  else:
+    reader.sread(part.value)
+
 iterator items*(this: FormData): FormDataEntry {.inline.} =
   for entry in this.entries:
     yield entry
diff --git a/src/types/url.nim b/src/types/url.nim
index 9e323847..b676a2b5 100644
--- a/src/types/url.nim
+++ b/src/types/url.nim
@@ -5,6 +5,8 @@ import std/strutils
 import std/tables
 import std/unicode
 
+import io/bufreader
+import io/bufwriter
 import lib/punycode
 import monoucha/fromjs
 import monoucha/javascript
@@ -85,6 +87,30 @@ type
 jsDestructor(URL)
 jsDestructor(URLSearchParams)
 
+# Forward declarations
+proc parseURL*(input: string; base = none(URL); override = none(URLState)):
+    Option[URL]
+func serialize*(url: URL; excludefragment = false; excludepassword = false):
+    string
+
+proc swrite*(writer: var BufferedWriter; url: URL) =
+  if url != nil:
+    writer.swrite(url.serialize())
+  else:
+    writer.swrite("")
+
+proc sread*(reader: var BufferedReader; url: var URL) =
+  var s: string
+  reader.sread(s)
+  if s == "":
+    url = nil
+  else:
+    let x = parseURL(s)
+    if x.isSome:
+      url = x.get
+    else:
+      url = nil
+
 const EmptyPath = URLPath(opaque: true, s: "")
 const EmptyHost = Host(t: htDomain, domain: "")