Skip to content

v0.28.0 - Quality Release

Compare
Choose a tag to compare
@haf haf released this 20 May 08:34
· 1756 commits to master since this release

This release contains a number of improvements to how bad input to Suave is handled. Previously we've done it the happy path. This has meant that bad input; especially so when it's input that's not parsed by a WebPart, but by the server, could cause a 'failwith' to throw an exception, cancelling the job (that serves the request) and leaving the Tcp connection hanging in an ever-connected state. By extension, this has meant an attacker could send many malformed requests that hang the TCP socket (requiring an equal number of sockets to be open from the attacker's machine(s)).

No more, we say, and with v0.28, Suave returns 400 Bad Request when it for example gets malforms HTML forms or requests without the Host-header.

More Improvements

We've improved how the WebSocket-code handles under different error cases, which means a more stable implementation.

SocketOp

There have been a number of breaking changes, mostly in the Suave.Utils namespace and in Suave.Sockets. One of the larger changes is that the socket workflow builder is now brought into scope through open Suave.Sockets.Control. The value that monad/workflow-builder operates on, SocketOp has been recognised to be pretty versatile for error handling and control flow with asynchronous interleaved sockets with error handling (that's a mouthful!), so it's gotten its own module SocketOp in Suave.Sockets. A few of the utility functions around lifting async/task to socket have been moved here: { ofAsync, ofTask }.

The special part about this monad is that Suave uses its Error value in the Choice2Of2-case -- if its Case is SocketError then the connection is terminated without further ado, but if it's InputDataError Suave now takes care to ferry the error all the way to the HTTP response body to tell the caller about the error in its ways. You can do the same if you're using SocketOp, e.g. like exemplified in examples/WebSocket, examples/Example/CounterDemo.fs; so it's a really nice abstraction for writing safe code with.

This monad is structured very similar to Choice, so you have functions like:

  • mreturn - create a new successful value
  • abort - create a new unsuccessful value
  • orInputError - says that something is wrong with the input on a protocol level and that it's therefore a bad request (user input error) -- the error already present is overwritten with the errorMsg parameter.
  • orInputErrorf - same as the above, but let's you do something with the existing error message through the callback function passed
  • bind - Bind the result successful result of the SocketOp to fCont
  • bindError - Bind the error result of the SocketOp to fCont
  • map - Map f over the contained successful value in SocketOp
  • mapError - Map f over the error value in SocketOp
  • ofAsync - lift a Async<'a> type to the SocketOp
  • ofTask - lift a Task type to the SocketOp

You can also open SocketOpOperators in this same namespace, to gain access to:

  • (@|!) - SocketOp.orInputError
  • (@|!!) - SocketOp.orInputErrorf
  • (@|>) - SocketOp.bindError

For example, here's how you write to a Server-Sent Event Stream and sleep async 100 ms before writing again:

  let write i =
    socket {
      let msg = { id = i; data = string i; ``type`` = None }
      do! msg |> send out
      return! SocketOp.ofAsync (Async.Sleep 100)
    }

More Changes

  • There were a few straggling snake_case that were switched over to camelCase. If you get a compilation error for one of these, just try camelCase.
  • The web server can now handle multipart/mixed content types that are nested in multipart/form when a form field has multiple values. This is a nice way to handle multiple values instead of using value[] keys and parsing in your own code - the main benefactor of this is form files.
  • You'll find a few utility methods return Choice1Of2 or Choice2Of2 instead of Option -- this is part of the major refactor towards providing contextual error messages everywhere and failing gracefully.