I'm happy to announce a new 2.1 release of WAI and Warp, version 0.3.1 of http-reverse-proxy, and the release of a brand new package: yesod-websockets. These releases are all connected, so let's start off by describing the major addition to WAI 2.1.
Primary change: responseRawHTTP is built around a request/response pair, and WAI has until now followed that strictly. The three response types allowed (builder, file, and source) all worked around the idea of first consuming a request, then returning a response. For normal HTTP requests, this is adequate. But there are two areas (that I'm aware of at least) where this falls short:
CONNECT
request method requires upgrading to full-duplex communication.To work around this limitation, Warp provides a settingsIntercept
, which allows a special handler to intercept some requests and take control away from the normal WAI request/response pairs. I took this approach initially because many WAI handlers have no means of properly supporting full duplex communications (e.g., CGI). However, this split between normal and non-normal handling makes it very awkward to actually use WebSockets.
So starting with WAI 2.1, we have a fourth response type: responseRaw
. Its type is:
responseRaw
:: (C.Source IO B.ByteString -> C.Sink B.ByteString IO () -> IO ())
-> Response
-> Response
The first parameter is a "raw application:" it takes a Source
of all data coming from the client, a Sink
for sending data to the client, and performs an action with them. The second parameter is a backup response for WAI handlers which do not support responseRaw
; usually this will just be a message like "Your server doesn't support WebSockets."
With this change the old settingsIntercept
from Warp is no longer necessary, and the behavior can be achieved from inside normal WAI applications. This has resulted in a number of changes.
The wai-websockets package has been available for over two years now, thanks to Jasper Van der Jeugt's websockets library. However, until now, it's provided a slightly strange API to work with settingsIntercept
:
intercept :: ConnectionOptions -> ServerApp -> Request -> Maybe (Source IO ByteString -> Connection -> IO ())
ConnectionOptions
and ServerApp
are both part of the websockets library itself. Request
is a WAI request, and it returns a Just
value if the request represents a proper WebSockets request. Starting with version 2.1, there's a much simpler API:
websocketsApp :: ConnectionOptions -> ServerApp -> Request -> Maybe Response
Now, instead of getting back something to tie into Warp's intercept handler, we get back a Response
. If there was no WebSockets request, we get Nothing
, which allows us to write normal, non-WebSockets responses.
This is also the first release of the yesod-websockets package. Previously, there was no good story for how to tie WebSockets into an existing Yesod application. Now, integration is trivial. Let's say we want to write a very basic chat server. We can describe this as a WebSockets application with the following:
chatApp :: WebSocketsT Handler ()
chatApp = do
sendTextData ("Welcome to the chat server, please enter your name." :: Text)
name <- receiveData
sendTextData $ "Welcome, " <> name
App writeChan <- getYesod
readChan <- atomically $ do
writeTChan writeChan $ name <> " has joined the chat"
dupTChan writeChan
race_
(forever $ atomically (readTChan readChan) >>= sendTextData)
(sourceWS $$ mapM_C (\msg ->
atomically $ writeTChan writeChan $ name <> ": " <> msg))
sendTextData
and receiveData
are the core functions. There's also a conduit-based API using sourceWS
and sinkWSText
, as well as some convenience asynchronous helpers (race
and concurrently
). But more interesting is the integration with the existing handler infrastructure:
getHomeR :: Handler Html
getHomeR = do
webSockets chatApp
defaultLayout ...
The webSockets
function takes a WebSockets application and tries to run it. If the client sent a WebSockets request, then the app will be run, and no further Handler
actions will be taken. Otherwise, normal operations will continue. (You can see the full chat example in the Github repo.)
As this is a first release, things are still evolving, so it's not a good idea to base your entire app off of this yet. But if you've been interested in playing with WebSockets, now's a good time to get started. If you end up working on anything, please let me know!
http-reverse-proxyThis is actually the project that kicked off my endeavors into responseRaw
in the first place. I wanted to add support for reverse proxying WebSockets requests, and initially did so without WAI support using settingsIntercept
. But the entire approach felt like a hack. Now with responseRaw
support, all users of http-reverse-proxy
automatically get WebSockets support. (This includes yesod devel
and keter
, by the way.)
I also put together an experimental forward proxy server a few weeks ago, and responseRaw
made that nicer too.
Kazu and I changed a few settings since Warp 2.0, which required a major version bump. We didn't like that the 2.0 settings infrastructure required a major version bump for minor tweaks to settings, so we've introduced a new system for modifying Warp settings. You still start with the defaultSettings
value, but to modify, for example, the port, you use the new setter function:
setPort 8080 defaultSettings
The old record-based accessors have been deprecated. In a future release, we'll be moving them entirely to an internal module.
The concrete settings change was providing the socket address to the onOpen
and onClose
settings. This allows you to write logging functions when connections are opened and closed, and indicate where the connections come from. In addition, you can inspect the SockAddr
in onOpen
and reject a connection immediately. Previously, this needed to be done from the application itself, which meant that more processing was performed before terminating the connection. onOpen/onClose.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4