Warp: A Haskell Web Server - Steve Vinoski

2y ago
43 Views
2 Downloads
1.21 MB
5 Pages
Last View : 1m ago
Last Download : 3m ago
Upload by : Kian Swinton
Transcription

The Functional WebWarp: A Haskell Web ServerMichael Snoyman Suite SolutionsRoughly two years ago, I began work onthe Yesod Web framework. I originallyintended FastCGI for my deployment strategy, but I also created a simple HTTP server forlocal testing, creatively named SimpleServer.Because Yesod targets the Web ApplicationInterface (WAI), a standard interface betweenHaskell Web servers and Web applications, itwas easy to switch back ends for testing andproduction.It didn’t take long before I started gettingfeature requests for SimpleServer: slowly butsurely, features such as chunked transfer encoding and sendfile-based file serving made theirway in. The tipping point was when Matt Brownof Soft Mechanics made some minor tweaksto SimpleServer and found that it was alreadythe fastest Web server in Haskell (see Figure 1).After that, he and I made some modest improvements and released the code as Warp.Very little code in Warp itself is gearedtoward speed. For the most part, it simplybuilds on the shoulders of giants — by relyingon underlying libraries that perform extremelywell, Warp can achieve a lot in fewer than 500lines of code. Let’s explore how Warp uses eachof these libraries, what makes them so powerful,and how they fit together.Glasgow Haskell CompilerThe first library isn’t really a library at all: theGlasgow Haskell Compiler (GHC) is the standardHaskell compiler. It has all the optimizationsyou’d expect of an industrial-strength compiler, such as loop unrolling, extensive inlining, unboxing, and fusion. It even lets usersspecify their own optimizations via rewriterules. In addition, it provides a very sophisticated multi t hreaded runtime. One great thingMAY/JUNE 2011about this runtime is its lightweight threads. Asa result of this feature, Warp simply spawns anew thread for each incoming connection, blissfully unaware of the gymnastics the runtime isperforming under the surface.Part of this abstraction involves convertingsynchronous Haskell I/O calls into asynchronous system calls. Once again, Warp reaps thebenefits by calling simple functions like recvand send, while GHC does the hard work.Up through GHC 6.12, this runtime system was based on the select system call. Thisworked well enough for many tasks but didn’tscale for Web servers. One big feature of theGHC 7 release was a new event manager, writtenby Google’s Johan Tibell and Serpentine’s BryanO’Sullivan. This new runtime uses different system calls (epoll, kqueue, and so on) dependingon what’s available on the target operating system. Additionally, Tibell and O’Sullivan madeextensive enhancements to the data structuresthe manager uses: it now uses a radix trie forstoring callbacks and a priority search queue fortimeouts.The end result: Haskell programs can easilyscale to thousands of simultaneous connections.Programmers can write their code against a verysimple API, spawning new lightweight threadsusing forkIO and calling blocking functionsinside them.EnumeratorA recent move in the Haskell communityhas been adopting the enumerator pattern.This pattern allows for processing streams ofdata in a deterministic manner. This is especially important for Web servers, which mustquickly release scarce resources such as filedescriptors.1089-7801/11/ 26.00 2011 IEEEPublished by the IEEE Computer Society 81

The Functional 16TorGoliath3,237Figure 1. Pong benchmark. Requests/second (higher is better).John Millikin (unaffiliated) wrotethe enumerator package that WAIand Warp use. In this package, thecentral datatype is Iteratee. AnIteratee is a data consumer, receiving chunks of data and performingsome action with them. Iteratee isan instance of Monad, making it easyto compose two Iteratees togetherto build up more complicated actions.(For those not familiar, a Monadis a container that encapsulates acomputation’s side effects. Haskellprogrammers can easily combinedifferent monadic values to build upmore powerful computations.)The flip side of Iteratee isEnumerator, a data producer. AnEnumerator will feed data into anIteratee until either the Enumeratorhas run out of data or the Iterateeno longer accepts more. A simpleexample of the interaction betweenthese two is file input and output:enumFile is an Enumerator that readsdata from a file and streams it into anIteratee, whereas iterHandle is anIteratee that consumes a stream ofbytes and sends them to a handle.A third datatype, an Enumeratee,is a combination of Enumerator82body and sent to the application, orwill be part of the next request.Enumeratees also play an important role in Warp. They ensure thatthe application consumes the entirerequest body before continuingwith the next request, and that theapplication doesn’t consume morebytes than it should for the requestbody. They also convert the responsebody from a stream of Builders (discussed next) to a stream of bytes withchunked transfer encoding applied.www.computer.org/internet/ and Iteratee: it receives a streamof data from an Enumerator andsends a new stream of data to anIteratee.Warp’s entire I/O system is builton top of the Enumerator datatype.Once Warp establishes a connectionand starts a new handler thread, itproduces an Enumerator from theclient socket and pipes that data intoan Iteratee. This Iteratee is whereall request parsing occurs.Enumerator’s built-in chunkingbehavior also works perfectly forWarp as well. The Enumerator optimizes the size of its requested buffers, currently set at 4,096 bytes.The consuming Iteratee, on theother hand, has no concept of thesechunks’ size. Instead, it simply consumes as many bytes as it wants.If there isn’t enough buffered content to complete an operation (forexample, the chunk terminated inthe middle of an HTTP header), thencontrol automatically returns to theEnumerator to provide more output.If too much data was provided, theremainder is left in the Enumeratorto be consumed by the next action.It will either be part of the requestThe simplest way to represent astring in Haskell is as a list of Unicode characters. This has two majorperformance issues: it’s expensive toappend data to a list, and the representation of the list has a lot ofoverhead. Historically, two differentsolutions have existed, one solvingeach issue: Use difference lists instead ofactual lists during data construction, and produce only thefinal output list at the end. Thisexploits the fact that appending to a difference list is an O(1)operation. Represent our data using a packedformat such as ByteString or thenewer Text datatype.The blaze-html package, byJasper Van der Jeugt of GhentUniversity and Simon Meier of ETHZurich, sought to solve both issuesduring HTML content construction.The idea is to work around thecentral concept of a Builder, avalue that knows how it should fillup a memory buffer. Internally, aBuilder is a difference list of thesebuffer-filling actions. Combiningthese two points, we end up with apacked representation of data withefficient append operations. And,just as important, we’re guaranteedthat the bytes will be copied preciselyonce into our final buffer.IEEE INTERNET COMPUTING

Warp: A Haskell Web ServerIt quickly became apparent thatthe Builder abstraction would beuseful outside the context of HTMLgeneration. The Yesod Web framework immediately used it for generating CSS, JavaScript, and JSON.Meier split off the Builder datatypeand its associated functions into aseparate blaze-builder package.WAI and Warp rely heavily onblaze-builder for constructingresponses. Applications always sendtheir response bodies to the serverin the form of Builders. This letsWarp efficiently append the body tothe response headers, meaning that,for many common responses, Warpuses only a single memory buffer and makes a single system call.As a nice finishing touch, blazebuilder provides a helper functionto automatically prepend the lengthof each chunk of an HTTP responsewhen using chunked transfer encoding. This function has taken care ofthe complicated logic of concatenating Builders to an optimal size andbacktracking to fill in the chunk sizein the header, and it’s available forall Haskell HTTP servers to use.Blaze-Builder-EnumeratorAt one point, I needed to write a program at work to modify XML files.Because it was simply modifyingattributes, this was a perfect case fora streaming algorithm, and thus agreat use case for Enumerators. Millikin had already written a parsingwrapper for the C language libxmllibrary, but no method existed forgenerating output.The simplest approach would beto convert each XML event into aByteString. However, this wouldinvolve creating a lot of small buffers. A better approach would be touse Builders, but consuming theentire stream of Builders, concatenating them, and then writing to afile would involve keeping the entirebody in memory, something I wantedto avoid.Instead, I ended up writingan Enumeratee that would take astream of Builders and use them tofill up buffers. When a buffer filled,the Enumeratee would wrap it in aByte String and send it down thepipeline to the Iteratee. This meantthat the code produced optimallysized ByteStrings, with minimalbuffer copying, and used constantmemory. Meier has since taken thecode, improved it, and released it asblaze-builder-enumerator.It turns out that the exact samerequirements exist when writing aWeb server. The application can givethe server chunks of data of any size,and the server wants to concatenatethese into optimally sized buffersto minimize system-call overhead,without using large amounts ofmemory or performing multiple buffercopies.In Warp, when the applicationreturns a ResponseEnumeratorre sponse, the flow control will passback and forth between the serverand the Enumerator. The Enumeratorwill feed chunks of Builders to theserver. The server then fills up amemory buffer using those Builders.Once the buffer is filled, the serverwill send its contents over the socketand release the buffer. This meansthe data is copied precisely once fromthe Builder into the final buffer.Additionally, the server must allocate only a single buffer, so memoryusage is constant.Web Application InterfaceThe WAI is a low-level interfacebetween Web applications and backends in Haskell. It’s generic enoughto support standalone servers suchas Warp, as well as options like theCommon Gateway Interface (CGI),FastCGI, and development serversthat automatically reinterpret yourcode during development. Warp isthe premiere WAI back end.The WAI concept is very simple:an application is a function that takesa request and returns a response. TheRequest datatype contains information such as the requested path, querystrings, request headers, and remotehost/port. One thing noticeably lacking from this list is the request body.To understand why, consider the following type signature:type Application Request Iteratee ByteString IO ResponseThe Application returns itsResponse inside an Iteratee, soit consumes the request body fromthere. As mentioned previously,Warp performs all its operationsinside the Iteratee monad; thismeans that calling the Applicationis simply another step in that process. The beauty of the Enumeratorapproach is that these actions compose together so easily.Three types of responses exist,represented by different data constructors. ResponseFile contains astatus code, a list of response headers, and the path to a file. This allowsback ends like Warp to use an efficient sendfile system call for sending the file contents to the client.ResponseBuilder contains a status code, a list of response headers,and a single Builder value. This isthe most commonly used responsetype. In most programming languages, this would require storing the entire response in memory.Haskell, on the other hand, uses lazyevaluation by default, meaning thevalue will compute on demand. So,we can efficiently encode very largeresponses as this single value, andthe application will consume memory only as needed.The most interesting type ofresponse is ResponseEnumerator,which lets an application produceresponses while interleaving impureactions. One example usage wouldbe to stream a large database responseto the client. Although we coulddo this using ResponseBuilder, itMAY/JUNE 2011 83

The Functional Webwould require reading the entiredatabase response into memory andthen sending it. With Response Enumerator, control will pass betweenan application and the back end. Assoon as Warp has enough data to filla buffer, it will immediately send thedata to the client and then releasethe memory the previous pieces haveconsumed.Request ParsingThe Warp team was able to implement a clear, concise, safe, and efficient request parser, thanks in largepart to Haskell’s high-quality Byte String library (from Don Stewartof Galois, Duncan Coutts of OxfordUniversity, and David Roundy ofOregon State University). The libraryprovides a high-level interface to Cbyte arrays with an elegant mix ofexpressiveness and efficiency. Wecan use ByteStrings in much thesame way as linked lists, throughversions of many idiomatic listfunctions familiar to functionalprogrammers. They also interfacedirectly with standard C I/O facilitieswith zero conversion. The API functions are bounds-checked by default,although unchecked versions arealso available. Warp uses these inseveral cases when the operation isstatically known to be safe.An HTTP request begins witha request line and an unspecifiednumber of header lines. The end ofthe headers is indicated by a blankline. Lines are delimited by pairs ofcarriage return and linefeed characters, and headers are key-valuepairs separated by colons. Scanningthe input for new lines and colonsis performed efficiently via memchr.The request parser then extractsdata using copy-free substringfunctions.Although the protocol syntax isvery simple, a few minor complications exist. A single read block cancontain multiple lines, and linescan span multiple read blocks. We84www.computer.org/internet/ also have several exceptional conditions to look out for: the client mighttake too long to send us data orclose the connection without sending the terminal blank line. We alsoneed to prevent attackers from filling up memory by sending an infinitely long header.GHC’s exceptions and lightweightthreads let us abstract away thetimeout and unexpected end-of-filecases into a single blocking functioncall. Our parser is responsible onlyfor ensuring that the header size isunder the allowed maximum.The parser is implemented as arecursive Iteratee of four arguments: the current accumulatedheader size, two difference lists (oneaccumulating segments of the current header line, and one for completed header lines), and the currentinput buffer. In addition to providingO(1) appends, using difference listshere preserves tail call optimization.Each recursive call consumes somedata, appends it to the current line,and increments the header size. Oncethe parser reaches a line terminal,if the completed line isn’t empty, it’sappended to the list of headers. It thencreates a new difference list for thenext line, and the function recurses.If the completed line is empty, we’vereached the end of the header. Anydata remaining in the input buffergoes back to the Enumeratee, and thefunction returns.Timeout HandlingA relatively recent attack vector forWeb servers is a slowloris attack:an attacker opens as many connections as possible to a server andsends trickles of data across them inan attempt to exhaust the server’sconnection pool. This attack can workespecially well because it requires sofew resources from the attacker. Thestandard response is to introducetimeouts: if a client doesn’t send anydata after a specified amount of time,disconnect the socket.The first version of Warp usedthe timeout handling code includedwith GHC. Unfortunately, this wasnot a very good fit; it didn’t scalewell, and, even worse, introduceddeadlocks into the code. (GHC hassince fixed this bug, but most usersare still running affected versions.)So, Warp needed a more elegantsolution.This was another opportunity forthe Haskell Web development community to shine: Gregory Collins ofGoogle and Jeremy Shaw of See Reason Partners (who work on theSnap and Happstack frameworks,respectively) had already been collaborating on more efficient timeoutcode. They had a great start, butthe initial code was slower than wehoped for. I made two changes totheir approach: The original code used MVars.This is a thread-safe, mutablevariable that would usually be theperfect fit for our use case. Unfortunately, the locking overheadwas simply too much. I switchedto using an IORef instead. Unlikean MVar, an IORef is simply amutable variable without anylocking. However, it provides anatomic modify operation, whichtakes advantage of Haskell’s referential transparency to avoidrace conditions without locking. A lot of complexity was involvedin managing a mutable, threadsafe hash table for storing thetimeout information. Becausewe know that all functional programmers only really know aboutlists, I decided to try them outhere, with much success.The entire timeout library isless than 70 lines of code. It worksby creating a timeout managerthread and an IORef holding a listof handles. Each handle contains anaction to perform on timeout (killing the appropriate thread) and itsIEEE INTERNET COMPUTING

Warp: A Haskell Web Serverstate: active, inactive, paused, orcanceled.The timeout thread simply loopsforever, swapping out the list of handles with an empty list, killing anyinactive threads, and then prepending the remaining handles to themutable variable again. This takesadvantage of two special functionalprogramming features: Haskell can provide an atomicIORef action for “pure” (that is,side-effect free) actions, so swapping out the lists is possible without incurring expensive lockpenalties. Lists in Haskell are generally singly linked, meaning it’s cheap toattach new values to the beginning but expensive at the end.Because we don’t actually careabout preserving handle order inour timeout code, our managercan create a handle difference list([Handle] [Handle]) and onceagain use IORef’s atomic actionsfor prepending the elements tothe list.For the managed threads themselves, the operations are fairlysimple: tickle, pause, and cancelset the state to active, paused, andcanceled, respectively. Once again,this all occurs using IORef’s atomicactions, so there are again no locking issues. The result is slowlorisattack protection, which uses justa few simple actions and a singlemanager thread.Warp allows Haskell developers to write Web applicationsat a high level and still achievevery fast applications. Warp is currently the basis of the Yesod WebFramework’s production deployment,and will be used by the HappstackSilverBulletSecurityPodcastWeb Framework in the near future.Due to Warp’s small code size, it canrun anywhere, from large dedicatedservers to embedded devices.AcknowledgmentsI thank Matt Brown for contributing to the“Request Parsing” section of this article.Michael Snoyman worked as an actuary inthe US insurance industry before moving halfway around the world to Israel.He’s currently a developer at Suite Solutions, providing DITA XML-based content life-cycle implementation services,and is the founder and lead developer ofthe Yesod Web Framework. Snoyman hasa BS in mathematics from the Universityof California, Los Angeles. Contact him atmichael@snoyman.com.Selected CS articles and columnsare also available for free at http://ComputingNow.computer.org.In-depth interviewswith security gurus.Hosted by Gary McGraw.www.computer.org/security/podcasts*Also available at iTunesSponsored byMAY/JUNE 201185

Meier split off the Builder datatype and its associated functions into a separate blaze-builder package. WAI and Warp rely heavily on blaze-builder for constructing responses. Applications always send their response bodies to the server in the form of Builders. This lets Warp efficient

Related Documents:

2.1 Warp Connect vs. Warp 4.0 OS/2 Warp Connect has separate requester's for LAN Server and Peer. Warp Connect will only allow either the LAN Server requester or the Peer requester to be installed. This is because they both share the same file name and directory structure. OS/2 Warp 4 combines the LAN Server requester and the Peer requester into

All things Haskell: haskell.org Tutorial: Learn You a Haskell for Great Good! by Miran Lipova ca Searching for functions: Hoogle Ryan Stansifer (CS, Florida Tech) Learning Haskell 28 March 2019 2 / 3. Learning Hask

Haskell Tutorial: Introduction September 23, 2021 [2]: :opt no-lint 1 Introduction Haskell is a statically typed, purely functional programming language with type inference and lazy evaluation. The first version of Haskell was defined in 1990 and the definition of the Haskell language i

The second section of the Warp Details shows the configuration of the warp Enabled – Enable or Disable the warp. Hidden – Sets whether the warp is hidden or not (If you have a hidden warp the Map System will not display it to any players. But can still be teleported to.) Delay (secs) – Time a player m

Pemrograman Web dengan PHP dan MySQL Achmad Solichin (achmatim@gmail.com) 7 Bab 1 Pengenalan Web Server dan Server Side Scripting Pengenalan Web Server Instalasi dan Konfigurasi Web Server Instalasi dan Konfigurasi PHP Testing Web Server dan PHP Web Server Web Server merupakan sebuah perangk

WITH THE WARP T , .AND TAKE CARE OF YOR EARS! WARP T - MANUAL 3 ENGLISH . and release to spice things up. The WARP T also delivers the kind of crisp, crystal-clear clean tone . Let’s take a closer look: 2.1 FRONT PANEL The cockpit of the WARP T is divided

8. Undo the warp chain and let it lie across the fl oor. 9. Roll the warp onto the warp beam feeding paper between the layers of warp. (For fi ne threads, paper provides a more even bed between the layers than warp sticks.) Remember to occasionally pull down on the paper to remove slack. Page 10

Senior Jazz Combo Wild and unpredictable band of senior musicians in years 10 to 13 for whom anything goes! (Grade 5 with a focus on improvisation). Senior Vocal Group Run by 6th form students for 6th form students, this is an acappella group of mixed voices with high standards of singing. St Bartholomew’s School Orchestra (SBSO) All instrumentalists are expected to perform in the school .