pax_global_header00006660000000000000000000000064122333513340014511gustar00rootroot0000000000000052 comment=d1d8cdb937daf5ba94324da736ac4b399e4fb371 erlang-cowboy-0.8.6+dfsg1/000077500000000000000000000000001223335133400152745ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/.gitignore000066400000000000000000000001331223335133400172610ustar00rootroot00000000000000.cowboy.plt .eunit deps doc/*.css doc/*.html doc/*.png doc/edoc-info ebin logs test/*.beam erlang-cowboy-0.8.6+dfsg1/AUTHORS000066400000000000000000000015521223335133400163470ustar00rootroot00000000000000Cowboy is available thanks to the work of: Loïc Hoguin Magnus Klaar Anthony Ramine Adam Cammack Ali Sabil Tom Burdick James Fish Paul Oliver Slava Yurin Vladimir Dronnikov Yurii Rashkovskii Andrew Majorov Egobrain Josh Toft Steven Gravell Andrew Thompson Hunter Morris Ivan Lisenkov Seletskiy Stanislav Tristan Sloughter 0x00F6 0xAX Adam Cammmack Andre Graf Andrzej Sliwa Blake Gentry Bob Ippolito Boris Faure Cameron Bytheway Cristian Hancila Dave Peticolas David Kelly DeadZen Dmitry Groshev Hans Ulrich Niedermann Ivan Blinkov Jeremy Ong Jesper Louis Andersen Josh Allmann Josh Marchán José Valim Julian Squires Mathieu Lecarme Max Lapshin Michiel Hakvoort Ori Bar Pablo Vieytes Radosław Szymczyszyn Richard Ramsden Roberto Ostinelli Sergey Rublev Sergey Urbanovich Seven Du Thomas Nordström Tim Dysinger Unix1 alisdair sullivan dbmercer derdesign rambocoder serge erlang-cowboy-0.8.6+dfsg1/CHANGELOG.md000066400000000000000000000545071223335133400171200ustar00rootroot00000000000000CHANGELOG ========= 0.8.6 ----- * Make sure Cowboy compiles on R16B01 * Update Ranch to 0.8.4 * Add experimental support for the x-webkit-deflate-frame Websocket extension This allows Cowboy to handle compressed Websocket frames, lowering the amount of data that needs to be sent over the socket. The extension will only be used if compression was enabled using the `compress` protocol option. * Add experimental SPDY support SPDY is a new protocol implemented by most browsers. It is the basis for what will become HTTP/2.0. To use SPDY, you need to call `start_spdy` where you would have used `start_https` before. This protocol is still incomplete. It cannot accept request bodies yet, making most methods other than GET and HEAD not too useful at this point. * Allow an empty method list in allowed_methods * The charset parameter of content-types is now always lowercase * Don't overwrite the stacktrace when a REST handler crashes * Don't crash when the Cookie header is empty * Don't crash on invalid Accept-Encoding header when replying 0.8.5 ----- * Add the Cowboy Function Reference Everything documented in the function reference is the API that will make it to Cowboy 1.0. * Use erlang.mk The project is of course still compatible with rebar and can be used as a dependency just fine. * Update Ranch to 0.8.3 * Remove cowboy_req:fragment/1 No well-written client is sending the fragment with the URL. * Add cowboy_req:set_resp_body_fun(chunked, Fun, Req) * Improve various typespecs * Change the return value of cowboy_req:version/1 We now have 'HTTP/1.1' instead of {1, 1} and 'HTTP/1.0' instead of {1, 0}. * Change the return value of REST accept callbacks The Path return value becomes {true, Path}. * Change the return value of REST charsets_provided/2 It was incorrectly expecting a list of tuples instead of a list of charsets. * Move various types to the cowboy module * cowboy_http:version() to cowboy:http_version() * cowboy_http:headers() to cowboy:http_headers() * cowboy_http:status() to cowboy:http_status() * cowboy_protocol:onrequest_fun() to cowboy:onrequest_fun() * cowboy_protocol:onresponse_fun() to cowboy:onresponse_fun() * Add type cowboy_protocol:opts() * Fix a REST bug with the OPTIONS method * Fix a REST bug where iso-8859-1 would be incoditionally selected 0.8.4 ----- * Cookie names are now back to being case sensitive This should be more in line with what browsers do and what users would expect. * REST is no longer experimental and is documented * REST behavior fixed when used with the POST method Removes process_post, post_is_create, create_path, created_path callbacks. It is up to the resource accept callback to decide what to do when the POST method is used. Depending on the return value Cowboy will determine if the resource was created or not. * Removes the put_path meta value in REST * Fix an issue in REST with the PATCH method Content-types were not normalized as expected, preventing the use of the binary form for content-types. * Add default operations for the OPTIONS method in REST The default will be to set the Allow header in the response based on the return value from allowed_methods. * Add default content_types_provided "text/html" maps to to_html This allows non-HEAD/GET methods to work without defining the callback explicitly. * Reject invalid content-types explicitly in REST * Don't accept TRACE or CONNECT methods by default in REST * Remove cowboy_req:peer_addr/1 Because each server's proxy situation differs, it is better that this function is implemented by the application directly. The X-Forwarded-For header can now be parsed using cowboy_req:parse_header/2. * Switch the arguments to cowboy_req:stream_body/2 They were in the wrong order compared to the rest of the module. * Add parser for the Range header * Do not crash if connection times out while sending a file using sendfile * Ensure we can fetch the body in the info/3 function of loop handlers * Update Ranch to 0.8.1 * Reorganize and optimize the test suites 0.8.3 ----- * Remove init_stream/5, add stream_body/2 It's better to allow configuring the streamed chunk size on a per chunk basis. Also easier to use. * Update Ranch to 0.8.0 Much faster. Also improved stability. 0.8.2 ----- * Add error_hook and ssl_hello_world example * Greatly improve the performance of body reading operations The streamed chunk size is now configurable through the new function cowboy_req:init_stream/5. * Add cowboy_req:body/2 and cowboy_req:body_qs/2 These functions take an additional argument indicating the maximum size of the body. They will return {error, badlength} if the size is too large, or {error, chunked} if the body was sent using the chunked Transfer-Encoding and its size cannot be determined. The function body/1 is now an alias to body/2 with a maximum body size of 8MB. Likewise, the function body_qs/1 is an alias of body_qs/2 with a maximum body size of 16KB. * Properly handle explicit identity Transfer-Encoding in body_length/1 * Small but noticeable performance improvement in the critical path We stopped using binary:match/2 in favor of custom functions. This makes Cowboy 0.5ms faster per request. * Prevent loop handlers from awakening after sending a response * Optimize cowboy_static initialization code * Make path checks in cowboy_static cross-platform * Allow '*' for REST content types parameters in content_types_provided * Fix cowboy_router types * Update Ranch to 0.6.2; adds support for two new SSL options * Improve documentation 0.8.1 ----- * Add eventsource, web_server examples; improve rest_pastebin example * Add cowboy:set_env/3 to more conveniently update the dispatch list * Add cowboy_sub_protocol behaviour * Fix cowboy_req:has_body/1 when Content-Length == 0 * Fix passing of state to websocket_terminate/3 on server close * Fix compilation with +native * Compile with more warnings enabled by default; fix warnings * Set the socket in passive mode after the loop handler terminates * Improve typespecs 0.8.0 ----- * This release drops R14 compatibility Behaviours now use the -callback attribute which is supported only since R15B. * Add a user guide * Add or update many examples Add basic_auth, compress_response, cookie, elixir_hello_world, markdown_middleware, rest_pastebin, rest_stream_response and websocket examples. Rename the static example to static_world for clarity. * Add CONTRIBUTING.md file * Use Ranch 0.6.1 for connection handling To start listeners you can now use cowboy:start_http/4 for HTTP, and cowboy:start_https/4 for HTTPS. The proper transport and protocol modules will be used. * Add protection against slowloris vulnerability This protection is always enabled and has no impact on the performance of the system. * Add a better routing syntax * If a binding is used twice in routing, values must now be identical * Add support for a configurable chain of middlewares Routing and handling are now two separate middlewares that can be replaced as needed. * Fix application dependencies The crypto application must be started before Cowboy. The inets application is no longer needed. A few functions from that application were used by mistake in the REST code. * Shorten the name of many modules * cowboy_http_protocol becomes cowboy_protocol * cowboy_http_req becomes cowboy_req * cowboy_http_rest becomes cowboy_rest * cowboy_http_static becomes cowboy_static * cowboy_http_websocket becomes cowboy_websocket * Introduce the cowboy_req:req() opaque type The include/http.hrl file was removed. Users are expected to use the cowboy_req API to access or modify the Req object. This required a lot of changes so cleanup and optimizations were performed where possible. * Add many cowboy_req functions * cowboy_req:delete_resp_header/2 deletes a previously set resp header * cowboy_req:set_meta/3 sets metadata in the Req object * cowboy_req:to_list/1 converts the Req object to a list of key/values * cowboy_req:fragment/1 returns the request URL fragment * cowboy_req:host_url/1 returns the request URL without the path or qs * cowboy_req:url/1 returns the full request URL * cowboy_req:set_resp_body_fun/2 for body streaming with no known length * Improve the body streaming interface in cowboy_req The function now receives the Transport and Socket directly as arguments. * Rename or drop many cowboy_req functions * cowboy_req:raw_host/1 becomes cowboy_req:host/1, old function dropped * cowboy_req:raw_path/1 becomes cowboy_req:path/1, old function dropped * cowboy_req:raw_qs/1 becomes cowboy_req:qs/1 * Remove cowboy_req:body/2 * Remove cowboy_req:transport/1 * Change the signature of many cowboy_req functions * parse_header now returns {ok, any(), Req} instead of {any(), Req} * body_qs now returns {ok, QsVals, Req} instead of {QsVals, Req} * multipart_data now returns {headers, Headers, Req} instead of {{headers, Headers}, Req} and {body, Body, Req} instead of {{body, Body}, Req} * set_resp_* functions now return Req instead of {ok, Req} * has_body now returns boolean() * Rewrote cookie code In short we now do the same thing as PHP when setting cookies. This allows us to be fairly confident that our code will work on the vast majority of browsers. * Fix consistency issues caused by erlang:decode_packet/3 * The method is now always a case sensitive binary string * Note that standard method names are uppercase (e.g. <<"GET">>) * Header names are now always lowercase binary string * The max_line_length cowboy_protocol option was replaced by 3 new options: * max_request_line_length, defaults to 4096 bytes * max_header_name_length, defaults to 64 bytes * max_header_value_length, defaults to 4096 bytes * Add max_headers option, limiting the number of headers; defaults to 100 * The max_keepalive option now defaults to 100 instead of infinity * Change terminate/2 to terminate/3 in the HTTP handler interface * Enhance the loop handler API * Connection close is now better detected * Fix an internal message leak * Enhance the Websocket API * Change a websocket error from {error, protocol} to {error, badframe} * Allow websocket handlers to reply more than one frame * Check for errors when calling Transport:send/2 to avoid crashes * Add close, {close, Payload}, {close, StatusCode, Payload}, ping, pong frame types for replies * Ensure websocket_terminate is always called * Improve timeout handling * Remove support for the old hixie76 protocol * Add parsing support for Sec-Websocket-Protocol * Check for UTF-8 correctness of text frames * Perform unmasking and UTF-8 validation on the fly * Reject clients that send unmasked frames * Add cowboy_websocket:close_code/0 type * Enhance the REST API * Fix charset handling * Add PATCH support * Add created_path callback, used if create_path was not defined * Make sure rest_terminate is always called * Improved HTTP standard compatibility * Revised status code used in responses * Implement authorization header parsing * Add opt-in automatic response body compression * Improve lager compatibility We format errors in a special way so that lager can recognize Cowboy errors and put them on a single line. * Remove the urldecode cowboy_protocol option * Add cowboy_protocol:onrequest_fun/0 and :onresponse_fun/0 types * Add the body data to onresponse_fun/0 callback * Avoid a duplicate HTTP reply in cowboy_websocket:upgrade_error/1 * Fix use of the Vary header, was named Variances in the previous code * Improve returned status code for HTTP and REST * Fix charsets_provided return value * Allow passing {M, F} for the mimetype function to cowboy_static * Can now upgrade protocols with {upgrade, protocol, P, Req, Opts} * Cowboy now only expects universal time, never local time * Do not try skipping the body if the connection is to be closed * Add cowboy_bstr:to_upper/1, cowboy_bstr:capitalize_token/1 * Many, many optimizations for the most critical code path 0.6.1 ----- * Add hello_world, rest_hello_world, chunked_hello_world, echo_get, echo_post and static examples. * Add support for the "Expect: 100-continue" header. * Keep the original 'Host' header value instead of modifying it. * Fix use of parsed headers cache. * REST: fix the matching of charsets. * REST: allow <<"type/subtype">> format for content_types_accepted. * Improve typespecs. 0.6.0 ----- * Add multipart support * Add chunked transfer decoding support Done by reworking the body reading API. Now all the body reading goes through the cowboy_http_req:stream_body/1 function. This function takes care of handling both the Transfer-Encoding and the Content-Encoding, returning properly decoded data ready for consumption. * Add fragmented websocket messages support Properly tested by the addition of the Autobahn websocket test suite to our toolbox. All tests pass except a few related to UTF-8 handling, as Cowboy does no checks on that end at this point. * Add 'onrequest' and 'onresponse' hooks The first can be used for all the special cases you may have that can't be dealt with otherwise. It's also pretty good for writing access logs or rewriting URLs. The second can be used for logging errors or replacing error pages, amongst others. * Add cowboy:get_protocol_options/1 and cowboy:set_protocol_options/2 These functions allow for retrieving a listener's protocol options, and for modifying them while the listener is running. This is most useful to upgrade the dispatch list. The upgrade applies to all the future connections. * Add the sockname/1 function to TCP and SSL transports * Improve SSL transport support Add support for specifying the ciphers. Add CA support. Make specifying the password optional. * Add new HTTP status codes from RFC 6585 * Add a 'file' option to cowboy_http_static This allows for mapping /folder/ paths to a /folder/index.html file. * Add the '*' catch all Content-Type for REST * Add {halt, Req, State} as a possible return value for REST * Add absolute URI support for requests * Add cowboy_http:x_www_form_urlencoded/2 * Various REST bug fixes * Do not send chunked replies for HTTP/1.0 connections * Fix a DST bug in the cookies code * Fix a bug with setting cookie values containing slashes * Fix a small timer leak when using loop/websocket timeouts * Make charset and media type parsing more relaxed This is to accomodate some widely used broken clients. * Make error messages more readable * Fix and improve type specifications * Fix a bug preventing documentation from being generated * Small improvements to the documentation * Rework the HTTP test suite The suite now uses an integrated Cowboy HTTP client. The client is currently experimental and shouldn't be used. * Add many many tests. 0.4.0 ----- * Set the cowboy_listener process priority to high As it is the central process used by all incoming requests we need to set its priority to high to avoid timeouts that would happen otherwise when reaching a huge number of concurrent requests. * Add cowboy:child_spec/6 for embedding in other applications * Add cowboy_http_rest, an experimental REST protocol support Based on the Webmachine diagram and documentation. It is a new implementation, not a port, therefore a few changes have been made. However all the callback names are the same and should behave similarly to Webmachine. There is currently no documentation other than the Webmachine resource documentation and the comments found in cowboy_http_rest, which itself should be fairly easy to read and understand. * Add cowboy_http_static, an experimental static file handler Makes use of the aforementioned REST protocol support to deliver files with proper content type and cache headers. Note that this uses the new file:sendfile support when appropriate, which currently requires the VM to be started with the +A option defined, else errors may randomly appear. * Add cowboy_bstr module for binary strings related functions * Add cowboy_http module for HTTP parsing functions This module so far contains various functions for HTTP header parsing along with URL encoding and decoding. * Remove quoted from the default dependencies This should make Cowboy much easier to compile and use by default. It is of course still possible to use quoted as your URL decoding library in Cowboy thanks to the newly added urldecode option. * Fix supervisor spec for non dynamic modules to allow upgrades to complete * Add cowboy:accept_ack/1 for a cleaner handling of the shoot message Before, when the listener accepted a connection, the newly created process was waiting for a message containing the atom 'shoot' before proceeding. This has been replaced by the cowboy:accept_ack/1 function. This function should be used where 'shoot' was received because the contents of the message have changed (and could change again in the distant future). * Update binary parsing expressions to avoid hype crashes More specifically, /bits was replaced by /binary. * Rename the type cowboy_dispatcher:path_tokens/0 to tokens/0 * Remove the cowboy_clock:date/0, time/0 and datetime/0 types The calendar module exports those same types properly since R14B04. * Add cacertfile configuration option to cowboy_ssl_transport * Add cowboy_protocol behaviour * Remove -Wbehaviours dialyzer option unavailable in R15B * Many tests and specs improvements ### cowboy_http_req * Fix a crash when reading the request body * Add parse_header/2 and parse_header/3 The following headers can now be semantically parsed: Connection, Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Length, Content-Type, If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since, Upgrade * Add set_resp_header/3, set_resp_cookie/4 and set_resp_body/2 These functions allow handlers to set response headers and body without having to reply directly. * Add set_resp_body_fun/3 This function allows handlers to stream the body of the response using the given fun. The size of the response must be known beforehand. * Add transport/1 to obtain the transport and socket for the request This allows handlers to have low-level socket access in those cases where they do need it, like when streaming a response body with set_resp_body_fun/3. * Add peer_addr/1 This function tries to guess the real peer IP based on the HTTP headers received. * Add meta/2 and meta/3 to save useful protocol information Currently used to save the Websocket protocol version currently used, and to save request information in the REST protocol handler. * Add reply/2 and reply/3 aliases to reply/4 * Add upgrade_reply/3 for protocol upgrades ### cowboy_http_protocol * Add the {urldecode, fun urldecode/2} option Added when quoted was removed from the default build. Can be used to tell Cowboy to use quoted or any other URL decoding routine. * Add the max_keepalive option * Add the max_line_length option * Allow HTTP handlers to stop during init/3 To do so they can return {shutdown, Req, State}. * Add loops support in HTTP handlers for proper long-polling support A loop can be entered by returning either of {loop, Req, State}, {loop, Req, State, hibernate}, {loop, Req, State, Timeout} or {loop, Req, State, Timeout, hibernate} from init/3. Loops are useful when we cannot reply immediately and instead are waiting for an Erlang message to be able to complete the request, as would typically be done for long-polling. Loop support in the protocol means that timeouts and hibernating are well tested and handled so you can use those options without worrying. It is recommended to set the timeout option. When a loop is started, handle/2 will never be called so it does not need to be defined. When the request process receives an Erlang message, it will call the info/3 function with the message as the first argument. Like in OTP, you do need to set timeout and hibernate again when returning from info/3 to enable them until the next call. * Fix the sending of 500 errors when handlers crash Now we send an error response when no response has been sent, and do nothing more than close the connection if anything did get sent. * Fix a crash when the server is sent HTTP responses * Fix HTTP timeouts handling when the Request-Line wasn't received * Fix the handling of the max number of empty lines between requests * Fix the handling of HEAD requests * Fix HTTP/1.0 Host header handling * Reply status 400 if we receive an unexpected value or error for headers * Properly close when the application sends "Connection: close" header * Close HTTP connections on all errors * Improve the error message for HTTP handlers ### cowboy_http_websocket * Add websocket support for all versions up to RFC 6455 Support isn't perfect yet according to the specifications, but is working against all currently known client implementations. * Allow websocket_init/3 to return with the hibernate option set * Add {shutdown, Req} return value to websocket_init/3 to fail an upgrade * Fix websocket timeout handling * Fix error messages: wrong callback name was reported on error * Fix byte-by-byte websocket handling * Fix an issue when using hixie-76 with certain proxies * Fix a crash in the hixie-76 handshake * Fix the handshake when SSL is used on port 443 * Fix a crash in the handshake when cowboy_http_req:compact/1 is used * Fix handshake when a query string is present * Fix a crash when the Upgrade header contains more than one token 0.2.0 ----- * Initial release. erlang-cowboy-0.8.6+dfsg1/CONTRIBUTING.md000066400000000000000000000116751223335133400175370ustar00rootroot00000000000000Contributing ============ Introduction ------------ This document describes the usages and rules to follow when contributing to this project. It uses the uppercase keywords SHOULD for optional but highly recommended conditions and MUST for required conditions. `git` is a distributed source code versioning system. This document refers to three different repositories hosting the source code of the project. `Your local copy` refers to the copy of the repository that you have on your computer. The remote repository `origin` refers to your fork of the project's repository that you can find in your GitHub account. The remote repository `upstream` refers to the official repository for this project. Following this document will ensure prompt merging of your work in the `master` branch of the project. Reporting bugs -------------- Upon identifying a bug or a DoS vulnerability, you SHOULD submit a ticket, regardless of your plans for fixing it. If you plan to fix the bug, you SHOULD discuss your plans to avoid having your work rejected. Upon identifying a security vulnerability in Erlang/OTP that leaves Cowboy vulnerable to attack, you SHOULD consult privately with the Erlang/OTP team to get the issue resolved. Upon identifying a security vulnerability in Cowboy's `cowboy_static` module, you SHOULD submit a ticket, regardless of your plans for fixing it. Please ensure that all necessary details to reproduce are listed. You then SHOULD inform users on the mailing list about the issue, advising that they use another means for sending static files until the issue is resolved. Upon identifying a security vulnerability in any other part of Cowboy, you SHOULD contact us directly by email. Please ensure that all necessary details to reproduce are listed. Before implementing a new feature, you SHOULD submit a ticket for discussion on your plans. The feature might have been rejected already, or the implementation might already be decided. Cloning ------- You MUST fork the project's repository to your GitHub account by clicking on the `Fork` button. Then, from your fork's page, copy the `Git Read-Only` URL to your clipboard. You MUST perform the following commands in the folder you choose, replacing `$URL` by the URL you just copied, `$UPSTREAM_URL` by the `Git Read-Only` project of the official repository, and `$PROJECT` by the name of this project. ``` bash $ git clone "$URL" $ cd $PROJECT $ git remote add upstream $UPSTREAM_URL ``` Branching --------- Before starting working on the code, you MUST update to `upstream`. The project is always evolving, and as such you SHOULD always strive to keep up to date when submitting patches to make sure they can be merged without conflicts. To update the current branch to `upstream`, you can use the following commands. ``` bash $ git fetch upstream $ git rebase upstream/master ``` It may ask you to stash your changes, in which case you stash with: ``` bash $ git stash ``` And put your changes back in with: ``` bash $ git stash pop ``` You SHOULD use these commands both before working on your patch and before submitting the pull request. If conflicts arise it is your responsability to deal with them. You MUST create a new branch for your work. First, ensure you are on `master`. You MUST update `master` to `upstream` before doing anything. Then create a new branch `$BRANCH` and switch to it. ``` bash $ git checkout -b $BRANCH ``` You MUST use a an insightful branch name. If you later need to switch back to an existing branch `$BRANCH`, you can use: ``` bash $ git checkout $BRANCH ``` Source editing -------------- The following rules MUST be followed: * Indentation uses horizontal tabs (1 tab = 4 columns) * Do NOT align code; only indentation is allowed * Lines MUST NOT span more than 80 columns The following rules SHOULD be followed: * Write small functions whenever possible * Avoid having too many clauses containing clauses containing clauses Committing ---------- You MUST ensure that all commits pass all tests and do not have extra Dialyzer warnings. You MUST put all the related work in a single commit. Fixing a bug is one commit, adding a feature is one commit, adding two features is two commits. You MUST write a proper commit title and message. The commit title MUST be at most 72 characters; it is the first line of the commit text. The second line of the commit text MUST be left blank. The third line and beyond is the commit message. You SHOULD write a commit message. If you do, you MUST make all lines smaller than 80 characters. You SHOULD explain what the commit does, what references you used and any other information that helps understanding your work. Submitting the pull request --------------------------- You MUST push your branch `$BRANCH` to GitHub, using the following command: ``` bash $ git push origin $BRANCH ``` You MUST then submit the pull request by using the GitHub interface. You SHOULD provide an explanatory message and refer to any previous ticket related to this patch. erlang-cowboy-0.8.6+dfsg1/LICENSE000066400000000000000000000013651223335133400163060ustar00rootroot00000000000000Copyright (c) 2011-2013, Loïc Hoguin Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. erlang-cowboy-0.8.6+dfsg1/Makefile000066400000000000000000000010301223335133400167260ustar00rootroot00000000000000# See LICENSE for licensing information. PROJECT = cowboy # Options. COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol CT_SUITES = eunit http spdy ws PLT_APPS = crypto public_key ssl # Dependencies. DEPS = ranch TEST_DEPS = ct_helper dep_ranch = https://github.com/extend/ranch.git 0.8.4 dep_ct_helper = https://github.com/extend/ct_helper.git master # Standard targets. include erlang.mk # Extra targets. .PHONY: autobahn autobahn: clean clean-deps deps app build-tests @mkdir -p logs/ @$(CT_RUN) -suite autobahn_SUITE erlang-cowboy-0.8.6+dfsg1/README.md000066400000000000000000000017111223335133400165530ustar00rootroot00000000000000Cowboy ====== Cowboy is a small, fast and modular HTTP server written in Erlang. Goals ----- Cowboy aims to provide a **complete** HTTP stack in a **small** code base. It is optimized for **low latency** and **low memory usage**, in parts because it uses **binary strings**. Cowboy provides **routing** capabilities, selectively dispatching requests to handlers written in Erlang. Because it uses Ranch for managing connections, Cowboy can easily be **embedded** in any other application. No parameterized module. No process dictionary. **Clean** Erlang code. Getting Started --------------- * [Read the guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide) * [Check the manual](http://ninenines.eu/docs/en/cowboy/HEAD/manual) * Look at the examples in the `examples/` directory Support ------- * Official IRC Channel: #ninenines on irc.freenode.net * [Mailing Lists](http://lists.ninenines.eu) * [Commercial Support](http://ninenines.eu/support) erlang-cowboy-0.8.6+dfsg1/ROADMAP.md000066400000000000000000000022501223335133400167000ustar00rootroot00000000000000ROADMAP ======= This document explains in as much details as possible the list of planned changes and work to be done on the Cowboy server. It is intended to be exhaustive but some elements might still be missing. All the following items must be done before Cowboy 1.0 is released. * Parse support for all standard HTTP/1.1 headers * Support for multipart requests and responses * Convenience API for extracting query string and body information, similar to PHP's $_GET, $_POST and $_FILES * Add Range support to REST * SPDY support We are only interested in supporting existing implementations, not the full protocol, as this protocol has been abandoned in favor of HTTP/2.0 * Complete the user guide The following items pertain to Ranch, but are equally important. * Resizing the acceptor pool We should be able to add more acceptors to a pool but also to remove some of them as needed * Add Transport:secure/0 Currently Cowboy checks if a connection is secure by checking if its name is 'ssl'. This isn't a very modular solution, adding an API function that returns whether a connection is secure would fix that issue erlang-cowboy-0.8.6+dfsg1/doc/000077500000000000000000000000001223335133400160415ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/doc/README.md000066400000000000000000000006171223335133400173240ustar00rootroot00000000000000Cowboy documentation ==================== Documentation for Cowboy is available online at the following addresses: * [README](http://ninenines.eu/docs/en/cowboy/HEAD/README) * [User Guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide/introduction) This folder is used for generating the Reference Manual. You can generate it yourself by typing `make docs` in the top-level Cowboy directory. erlang-cowboy-0.8.6+dfsg1/doc/overview.edoc000066400000000000000000000002011223335133400205340ustar00rootroot00000000000000@author Loc Hoguin @copyright 2011-2012 Loc Hoguin @version HEAD @title Small, fast, modular HTTP server. erlang-cowboy-0.8.6+dfsg1/erlang.mk000066400000000000000000000107511223335133400171010ustar00rootroot00000000000000# Copyright (c) 2013, Loïc Hoguin # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # Verbosity and tweaks. V ?= 0 appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; appsrc_verbose = $(appsrc_verbose_$(V)) erlc_verbose_0 = @echo " ERLC " $(filter-out %.dtl,$(?F)); erlc_verbose = $(erlc_verbose_$(V)) dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); dtl_verbose = $(dtl_verbose_$(V)) gen_verbose_0 = @echo " GEN " $@; gen_verbose = $(gen_verbose_$(V)) .PHONY: all clean-all app clean deps clean-deps docs clean-docs \ build-tests tests build-plt dialyze # Deps directory. DEPS_DIR ?= $(CURDIR)/deps export DEPS_DIR ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS)) ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) # Application. ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec COMPILE_FIRST ?= COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) all: deps app clean-all: clean clean-deps clean-docs $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs app: ebin/$(PROJECT).app $(eval MODULES := $(shell find ebin -name \*.beam \ | sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//')) $(appsrc_verbose) cat src/$(PROJECT).app.src \ | sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \ > ebin/$(PROJECT).app define compile_erl $(erlc_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ \ $(COMPILE_FIRST_PATHS) $(1) endef define compile_dtl $(dtl_verbose) erl -noshell -pa ebin/ deps/erlydtl/ebin/ -eval ' \ Compile = fun(F) -> \ Module = list_to_atom( \ string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \ erlydtl_compiler:compile(F, Module, [{out_dir, "ebin/"}]) \ end, \ _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \ init:stop()' endef ebin/$(PROJECT).app: src/*.erl $(wildcard src/*.core) $(wildcard templates/*.dtl) @mkdir -p ebin/ $(if $(strip $(filter-out %.dtl,$?)), \ $(call compile_erl,$(filter-out %.dtl,$?))) $(if $(strip $(filter %.dtl,$?)), \ $(call compile_dtl,$(filter %.dtl,$?))) clean: $(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump # Dependencies. define get_dep @mkdir -p $(DEPS_DIR) git clone -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1) cd $(DEPS_DIR)/$(1) ; git checkout -q $(word 2,$(dep_$(1))) endef define dep_target $(DEPS_DIR)/$(1): $(call get_dep,$(1)) endef $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) deps: $(ALL_DEPS_DIRS) @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done clean-deps: @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep clean; done # Documentation. docs: clean-docs $(gen_verbose) erl -noshell \ -eval 'edoc:application($(PROJECT), ".", []), init:stop().' clean-docs: $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info # Tests. $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) build-test-deps: $(ALL_TEST_DEPS_DIRS) @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done build-tests: build-test-deps $(gen_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o test/ \ $(wildcard test/*.erl test/*/*.erl) -pa ebin/ CT_RUN = ct_run \ -no_auto_compile \ -noshell \ -pa ebin $(DEPS_DIR)/*/ebin \ -dir test \ -logdir logs # -cover test/cover.spec CT_SUITES ?= CT_SUITES_FULL = $(addsuffix _SUITE,$(CT_SUITES)) tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}' tests: clean deps app build-tests @mkdir -p logs/ @$(CT_RUN) -suite $(CT_SUITES_FULL) $(gen_verbose) rm -f test/*.beam # Dialyzer. PLT_APPS ?= DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \ -Wunmatched_returns # -Wunderspecs build-plt: deps app @dialyzer --build_plt --output_plt .$(PROJECT).plt \ --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS) dialyze: @dialyzer --src src --plt .$(PROJECT).plt --no_native $(DIALYZER_OPTS) erlang-cowboy-0.8.6+dfsg1/examples/000077500000000000000000000000001223335133400171125ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/README.md000066400000000000000000000030041223335133400203660ustar00rootroot00000000000000Cowboy Examples =============== * [basic_auth](./basic_auth): basic HTTP authorization with REST * [chunked_hello_world](./chunked_hello_world): demonstrates chunked data transfer with two one-second delays * [compress_response](./compress_response) send a response body compressed if the client supports it * [cookie](./cookie): set cookies from server and client side * [echo_get](./echo_get): parse and echo a GET query string * [echo_post](./echo_post): parse and echo a POST parameter * [elixir_hello_world](./elixir_hello_world): simplest example application with Elixir * [error_hook](./error_hook): provide custom error pages * [eventsource](./eventsource): eventsource emitter and consumer * [hello_world](./hello_world): simplest example application * [markdown_middleware](./markdown_middleware): static file handler with markdown preprocessor * [rest_hello_world](./rest_hello_world): return the data type that matches the request type (ex: html, text, json) * [rest_pastebin](./rest_pastebin): create text objects and return the data type that matches the request type (html, text) * [rest_stream_response](./rest_stream_response): stream results from a data store * [ssl_hello_world](./ssl_hello_world): simplest SSL application * [static_world](./static_world): static file handler * [web_sever](./web_server): serves files with lists directory entries * [websocket](./websocket): websocket example erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/000077500000000000000000000000001223335133400212145ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/README.md000066400000000000000000000014611223335133400224750ustar00rootroot00000000000000Cowboy Basic Authorization Rest Hello World =========================================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then run any given command or point your browser to the indicated URL. Examples -------- ### Get 401 ``` bash $ curl -i http://localhost:8080 HTTP/1.1 401 Unauthorized connection: keep-alive server: Cowboy date: Sun, 20 Jan 2013 14:10:27 GMT content-length: 0 www-authenticate: Restricted ``` ### Get 200 ``` bash $ curl -i -u "Alladin:open sesame" http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Sun, 20 Jan 2013 14:11:12 GMT content-length: 16 content-type: text/plain Hello, Alladin! ``` erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/rebar.config000066400000000000000000000001261223335133400234750ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/src/000077500000000000000000000000001223335133400220035ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/src/basic_auth.app.src000066400000000000000000000004461223335133400254010ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, basic_auth, [ {description, "Cowboy Basic HTTP Authorization example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {basic_auth_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/src/basic_auth.erl000066400000000000000000000004131223335133400246070ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(basic_auth). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(basic_auth). erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/src/basic_auth_app.erl000066400000000000000000000006651223335133400254600ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(basic_auth_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), basic_auth_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/src/basic_auth_sup.erl000066400000000000000000000006041223335133400255000ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(basic_auth_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/src/toppage_handler.erl000066400000000000000000000014111223335133400256400ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Basic authorization Hello world handler. -module(toppage_handler). -export([init/3]). -export([content_types_provided/2]). -export([is_authorized/2]). -export([hello_to_text/2]). init(_Transport, _Req, []) -> {upgrade, protocol, cowboy_rest}. is_authorized(Req, S) -> {ok, Auth, Req1} = cowboy_req:parse_header(<<"authorization">>, Req), case Auth of {<<"basic">>, {User = <<"Alladin">>, <<"open sesame">>}} -> {true, Req1, User}; _ -> {{false, <<"Basic realm=\"cowboy\"">>}, Req1, S} end. content_types_provided(Req, State) -> {[ {<<"text/plain">>, hello_to_text} ], Req, State}. hello_to_text(Req, User) -> {<< <<"Hello, ">>/binary, User/binary, <<"!\n">>/binary >>, Req, User}. erlang-cowboy-0.8.6+dfsg1/examples/basic_auth/start.sh000077500000000000000000000003301223335133400227040ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s basic_auth \ -eval "io:format(\"Get 401: curl -i http://localhost:8080~n\")." \ -eval "io:format(\"Get 200: curl -i -u \\\"Alladin:open sesame\\\" http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/000077500000000000000000000000001223335133400231255ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/README.md000066400000000000000000000011351223335133400244040ustar00rootroot00000000000000Cowboy Chunked Hello World ========================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then run the given command or point your browser to the indicated URL. Example ------- ```bash $ time curl -i http://localhost:8080 HTTP/1.1 200 OK transfer-encoding: chunked connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:24:16 GMT Hello World Chunked! curl -i http://localhost:8080 0.01s user 0.00s system 0% cpu 2.015 total ``` erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/rebar.config000066400000000000000000000001261223335133400254060ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/src/000077500000000000000000000000001223335133400237145ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/src/chunked_hello_world.app.src000066400000000000000000000004631223335133400312220ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, chunked_hello_world, [ {description, "Cowboy Chunked Hello World example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {chunked_hello_world_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/src/chunked_hello_world.erl000066400000000000000000000004351223335133400304350ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(chunked_hello_world). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(chunked_hello_world). erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/src/chunked_hello_world_app.erl000066400000000000000000000007071223335133400312770ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(chunked_hello_world_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), chunked_hello_world_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/src/chunked_hello_world_sup.erl000066400000000000000000000006151223335133400313240ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(chunked_hello_world_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/src/toppage_handler.erl000066400000000000000000000010571223335133400275570ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Chunked hello world handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> {ok, Req2} = cowboy_req:chunked_reply(200, Req), ok = cowboy_req:chunk("Hello\r\n", Req2), ok = timer:sleep(1000), ok = cowboy_req:chunk("World\r\n", Req2), ok = timer:sleep(1000), ok = cowboy_req:chunk("Chunked!\r\n", Req2), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/chunked_hello_world/start.sh000077500000000000000000000001721223335133400246210ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s chunked_hello_world \ -eval "io:format(\"Run: curl -i http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/compress_response/000077500000000000000000000000001223335133400226635ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/compress_response/README.md000066400000000000000000000046441223335133400241520ustar00rootroot00000000000000Cowboy Compress Response ======================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. Example ------- ``` bash $ curl -i http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Mon, 07 Jan 2013 18:42:29 GMT content-length: 909 A cowboy is an animal herder who tends cattle on ranches in North America, traditionally on horseback, and often performs a multitude of other ranch- related tasks. The historic American cowboy of the late 19th century arose from the vaquero traditions of northern Mexico and became a figure of special significance and legend. A subtype, called a wrangler, specifically tends the horses used to work cattle. In addition to ranch work, some cowboys work for or participate in rodeos. Cowgirls, first defined as such in the late 19th century, had a less-well documented historical role, but in the modern world have established the ability to work at virtually identical tasks and obtained considerable respect for their achievements. There are also cattle handlers in many other parts of the world, particularly South America and Australia, who perform work similar to the cowboy in their respective nations. $ curl -i --compressed http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Mon, 07 Jan 2013 18:42:30 GMT content-encoding: gzip content-length: 510 A cowboy is an animal herder who tends cattle on ranches in North America, traditionally on horseback, and often performs a multitude of other ranch- related tasks. The historic American cowboy of the late 19th century arose from the vaquero traditions of northern Mexico and became a figure of special significance and legend. A subtype, called a wrangler, specifically tends the horses used to work cattle. In addition to ranch work, some cowboys work for or participate in rodeos. Cowgirls, first defined as such in the late 19th century, had a less-well documented historical role, but in the modern world have established the ability to work at virtually identical tasks and obtained considerable respect for their achievements. There are also cattle handlers in many other parts of the world, particularly South America and Australia, who perform work similar to the cowboy in their respective nations. ``` erlang-cowboy-0.8.6+dfsg1/examples/compress_response/rebar.config000066400000000000000000000001261223335133400251440ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/compress_response/src/000077500000000000000000000000001223335133400234525ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/compress_response/src/compress_response.app.src000066400000000000000000000004551223335133400305170ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, compress_response, [ {description, "Cowboy Compress Response example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {compress_response_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/compress_response/src/compress_response.erl000066400000000000000000000004311223335133400277250ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(compress_response). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(compress_response). erlang-cowboy-0.8.6+dfsg1/examples/compress_response/src/compress_response_app.erl000066400000000000000000000007271223335133400305750ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(compress_response_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {compress, true}, {env, [{dispatch, Dispatch}]} ]), compress_response_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/compress_response/src/compress_response_sup.erl000066400000000000000000000006131223335133400306160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(compress_response_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/compress_response/src/toppage_handler.erl000066400000000000000000000024351223335133400273160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Compress response handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> BigBody = <<"A cowboy is an animal herder who tends cattle on ranches in North America, traditionally on horseback, and often performs a multitude of other ranch- related tasks. The historic American cowboy of the late 19th century arose from the vaquero traditions of northern Mexico and became a figure of special significance and legend. A subtype, called a wrangler, specifically tends the horses used to work cattle. In addition to ranch work, some cowboys work for or participate in rodeos. Cowgirls, first defined as such in the late 19th century, had a less-well documented historical role, but in the modern world have established the ability to work at virtually identical tasks and obtained considerable respect for their achievements. There are also cattle handlers in many other parts of the world, particularly South America and Australia, who perform work similar to the cowboy in their respective nations.\n">>, {ok, Req2} = cowboy_req:reply(200, [], BigBody, Req), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/compress_response/start.sh000077500000000000000000000002011223335133400243500ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s compress_response \ -eval "io:format(\"Point your browser at http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/cookie/000077500000000000000000000000001223335133400203635ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/cookie/README.md000066400000000000000000000006161223335133400216450ustar00rootroot00000000000000Cowboy Cookie ============= To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. This example allows you to use any path you want to try to show that cookies are defined site-wide. Try it in your browser! erlang-cowboy-0.8.6+dfsg1/examples/cookie/rebar.config000066400000000000000000000002501223335133400226420ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {erlydtl, ".*", {git, "https://github.com/evanmiller/erlydtl.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/cookie/src/000077500000000000000000000000001223335133400211525ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/cookie/src/cookie.app.src000066400000000000000000000004141223335133400237120ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, cookie, [ {description, "Cowboy Cookie example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {cookie_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/cookie/src/cookie.erl000066400000000000000000000004031223335133400231240ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(cookie). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(cookie). erlang-cowboy-0.8.6+dfsg1/examples/cookie/src/cookie_app.erl000066400000000000000000000006551223335133400237750ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(cookie_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {'_', toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), cookie_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/cookie/src/cookie_sup.erl000066400000000000000000000006001223335133400240120ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(cookie_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/cookie/src/toppage_handler.erl000066400000000000000000000013761223335133400250210ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Cookie handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> NewValue = integer_to_list(random:uniform(1000000)), Req2 = cowboy_req:set_resp_cookie( <<"server">>, NewValue, [{path, <<"/">>}], Req), {ClientCookie, Req3} = cowboy_req:cookie(<<"client">>, Req2), {ServerCookie, Req4} = cowboy_req:cookie(<<"server">>, Req3), {ok, Body} = toppage_dtl:render([ {client, ClientCookie}, {server, ServerCookie} ]), {ok, Req5} = cowboy_req:reply(200, [{<<"content-type">>, <<"text/html">>}], Body, Req4), {ok, Req5, State}. terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/cookie/start.sh000077500000000000000000000001661223335133400220620ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s cookie \ -eval "io:format(\"Point your browser at http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/cookie/templates/000077500000000000000000000000001223335133400223615ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/cookie/templates/toppage.dtl000066400000000000000000000006461223335133400245330ustar00rootroot00000000000000 Cowboy Cookie Example

Cowboy Cookie Example

Refresh the page to see the next cookie.

Cookie Set Server-Side

{{ server }}

Cookie Set Client-Side

{{ client }}

erlang-cowboy-0.8.6+dfsg1/examples/echo_get/000077500000000000000000000000001223335133400206675ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/echo_get/README.md000066400000000000000000000011061223335133400221440ustar00rootroot00000000000000Cowboy GET Echo =============== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. You can change the GET parameter to check that the handler is echoing properly. Example ------- ``` bash $ curl -i "http://localhost:8080/?echo=saymyname" HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:09:04 GMT content-length: 9 Content-Encoding: utf-8 saymyname ``` erlang-cowboy-0.8.6+dfsg1/examples/echo_get/rebar.config000066400000000000000000000001261223335133400231500ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/echo_get/src/000077500000000000000000000000001223335133400214565ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/echo_get/src/echo_get.app.src000066400000000000000000000004221223335133400245210ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, echo_get, [ {description, "Cowboy GET echo example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {echo_get_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/echo_get/src/echo_get.erl000066400000000000000000000004071223335133400237400ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(echo_get). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(echo_get). erlang-cowboy-0.8.6+dfsg1/examples/echo_get/src/echo_get_app.erl000066400000000000000000000006611223335133400246020ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(echo_get_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), echo_get_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/echo_get/src/echo_get_sup.erl000066400000000000000000000006021223335133400246240ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(echo_get_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/echo_get/src/toppage_handler.erl000066400000000000000000000013331223335133400253160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc GET echo handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> {Method, Req2} = cowboy_req:method(Req), {Echo, Req3} = cowboy_req:qs_val(<<"echo">>, Req2), {ok, Req4} = echo(Method, Echo, Req3), {ok, Req4, State}. echo(<<"GET">>, undefined, Req) -> cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req); echo(<<"GET">>, Echo, Req) -> cowboy_req:reply(200, [{<<"content-encoding">>, <<"utf-8">>}], Echo, Req); echo(_, _, Req) -> %% Method not allowed. cowboy_req:reply(405, Req). terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/echo_get/start.sh000077500000000000000000000002031223335133400223560ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s echo_get \ -eval "io:format(\"Point your browser at http://localhost:8080/?echo=test~n\")." erlang-cowboy-0.8.6+dfsg1/examples/echo_post/000077500000000000000000000000001223335133400210755ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/echo_post/README.md000066400000000000000000000013701223335133400223550ustar00rootroot00000000000000Cowboy POST Echo ================ To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. You can change the GET parameter to check that the handler is echoing properly. Then run the following command, replacing STRING_TO_ECHO by the string you want to echo. Check the ```curl_post.sh``` file for details. ``` ./curl_post.sh STRING_TO_ECHO ``` Example ------- ``` bash $ curl -i -d echo=echomeplz http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:12:36 GMT content-length: 9 Content-Encoding: utf-8 echomeplz ``` erlang-cowboy-0.8.6+dfsg1/examples/echo_post/curl_post.sh000077500000000000000000000000631223335133400234450ustar00rootroot00000000000000#!/bin/sh curl -i -d echo=$1 http://localhost:8080 erlang-cowboy-0.8.6+dfsg1/examples/echo_post/rebar.config000066400000000000000000000001261223335133400233560ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/echo_post/src/000077500000000000000000000000001223335133400216645ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/echo_post/src/echo_post.app.src000066400000000000000000000004251223335133400251400ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, echo_post, [ {description, "Cowboy POST echo example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {echo_post_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/echo_post/src/echo_post.erl000066400000000000000000000004111223335133400243470ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(echo_post). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(echo_post). erlang-cowboy-0.8.6+dfsg1/examples/echo_post/src/echo_post_app.erl000066400000000000000000000006631223335133400252200ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(echo_post_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), echo_post_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/echo_post/src/echo_post_sup.erl000066400000000000000000000006031223335133400252410ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(echo_post_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/echo_post/src/toppage_handler.erl000066400000000000000000000016771223335133400255370ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc POST echo handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> {Method, Req2} = cowboy_req:method(Req), HasBody = cowboy_req:has_body(Req2), {ok, Req3} = maybe_echo(Method, HasBody, Req2), {ok, Req3, State}. maybe_echo(<<"POST">>, true, Req) -> {ok, PostVals, Req2} = cowboy_req:body_qs(Req), Echo = proplists:get_value(<<"echo">>, PostVals), echo(Echo, Req2); maybe_echo(<<"POST">>, false, Req) -> cowboy_req:reply(400, [], <<"Missing body.">>, Req); maybe_echo(_, _, Req) -> %% Method not allowed. cowboy_req:reply(405, Req). echo(undefined, Req) -> cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req); echo(Echo, Req) -> cowboy_req:reply(200, [{<<"content-encoding">>, <<"utf-8">>}], Echo, Req). terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/echo_post/start.sh000077500000000000000000000001571223335133400225740ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s echo_post \ -eval "io:format(\"Run ./curl_post.sh STRING_TO_ECHO~n\")." erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/000077500000000000000000000000001223335133400230005ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/README.md000066400000000000000000000015271223335133400242640ustar00rootroot00000000000000Elixir Hello World ================== This is an example of running Cowboy with [Elixir](http://elixir-lang.org). You need Elixir installed ([instructions here](http://elixir-lang.org/getting_started/1.html)) to run this example. After installing Elixir, you should have both `elixir` and `mix` executables available. You also need [rebar](https://github.com/rebar/rebar) in your PATH to compile dependencies. Then type the following command: ``` mix deps.get ``` The command above will fetch all dependencies and compile them. You can then start the Erlang node with the following command: ``` mix run --no-halt ``` Then point your browser to localhost:8080. Example ------- ``` bash $ curl -i http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:10:25 GMT content-length: 12 Hello world! ``` erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/lib/000077500000000000000000000000001223335133400235465ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/lib/elixir_hello_world.ex000066400000000000000000000006501223335133400277730ustar00rootroot00000000000000defmodule ElixirHelloWorld do use Application.Behaviour def start(_type, _args) do dispatch = :cowboy_router.compile([ {:_, [{"/", ElixirHelloWorld.TopPageHandler, []}]} ]) {:ok, _} = :cowboy.start_http(:http, 100, [port: 8080], [env: [dispatch: dispatch]]) ElixirHelloWorld.Supervisor.start_link end end erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/lib/elixir_hello_world/000077500000000000000000000000001223335133400274345ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/lib/elixir_hello_world/supervisor.ex000066400000000000000000000003501223335133400322110ustar00rootroot00000000000000defmodule ElixirHelloWorld.Supervisor do use Supervisor.Behaviour def start_link do :supervisor.start_link(__MODULE__, []) end def init([]) do children = [] supervise children, strategy: :one_for_one end end erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/lib/elixir_hello_world/top_page_handler.ex000066400000000000000000000004311223335133400332630ustar00rootroot00000000000000defmodule ElixirHelloWorld.TopPageHandler do def init(_transport, req, []) do {:ok, req, nil} end def handle(req, state) do {:ok, req} = :cowboy_req.reply(200, [], "Hello world!", req) {:ok, req, state} end def terminate(_reason, _req, _state), do: :ok end erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/mix.exs000066400000000000000000000006421223335133400243200ustar00rootroot00000000000000defmodule ElixirHelloWorld.Mixfile do use Mix.Project def project do [ app: :elixir_hello_world, version: "0.0.1", deps: deps ] end # Configuration for the OTP application def application do [ mod: { ElixirHelloWorld, [] }, applications: [:cowboy] ] end defp deps do [ {:ranch, github: "extend/ranch", tag: "0.8.4"}, {:cowboy, github: "extend/cowboy"} ] end end erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/test/000077500000000000000000000000001223335133400237575ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/test/elixir_hello_world_test.exs000066400000000000000000000002271223335133400314260ustar00rootroot00000000000000Code.require_file "../test_helper.exs", __FILE__ defmodule ElixirHelloWorldTest do use ExUnit.Case test "the truth" do assert true end end erlang-cowboy-0.8.6+dfsg1/examples/elixir_hello_world/test/test_helper.exs000066400000000000000000000000151223335133400270120ustar00rootroot00000000000000ExUnit.start erlang-cowboy-0.8.6+dfsg1/examples/error_hook/000077500000000000000000000000001223335133400212635ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/error_hook/README.md000066400000000000000000000010061223335133400225370ustar00rootroot00000000000000Cowboy Error Hook ================= To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. Example ------- ``` bash $ curl -i http://localhost:8080 HTTP/1.1 404 Not Found connection: keep-alive server: Cowboy date: Wed, 27 Feb 2013 23:32:55 GMT content-length: 56 404 Not Found: "/" is not the path you are looking for. ``` erlang-cowboy-0.8.6+dfsg1/examples/error_hook/rebar.config000066400000000000000000000001261223335133400235440ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/error_hook/src/000077500000000000000000000000001223335133400220525ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/error_hook/src/error_hook.app.src000066400000000000000000000004331223335133400255130ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, error_hook, [ {description, "Cowboy error handler example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {error_hook_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/error_hook/src/error_hook.erl000066400000000000000000000004131223335133400247250ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(error_hook). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(error_hook). erlang-cowboy-0.8.6+dfsg1/examples/error_hook/src/error_hook_app.erl000066400000000000000000000007101223335133400255650ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(error_hook_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', []} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]}, {onresponse, fun error_hook_responder:respond/4} ]), error_hook_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/error_hook/src/error_hook_responder.erl000066400000000000000000000015021223335133400270060ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(error_hook_responder). -export([respond/4]). respond(404, Headers, <<>>, Req) -> {Path, Req2} = cowboy_req:path(Req), Body = <<"404 Not Found: \"", Path/binary, "\" is not the path you are looking for.\n">>, Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers, {<<"content-length">>, integer_to_list(byte_size(Body))}), {ok, Req3} = cowboy_req:reply(404, Headers2, Body, Req2), Req3; respond(Code, Headers, <<>>, Req) when is_integer(Code), Code >= 400 -> Body = ["HTTP Error ", integer_to_list(Code), $\n], Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers, {<<"content-length">>, integer_to_list(iolist_size(Body))}), {ok, Req2} = cowboy_req:reply(Code, Headers2, Body, Req), Req2; respond(_Code, _Headers, _Body, Req) -> Req. erlang-cowboy-0.8.6+dfsg1/examples/error_hook/src/error_hook_sup.erl000066400000000000000000000006041223335133400256160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(error_hook_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/error_hook/start.sh000077500000000000000000000001721223335133400227570ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s error_hook \ -eval "io:format(\"Point your browser at http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/eventsource/000077500000000000000000000000001223335133400214545ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/eventsource/README.md000066400000000000000000000006701223335133400227360ustar00rootroot00000000000000Cowboy EventSource ================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Uses Cowboy's loop functionality to continuously send events to the browser. Example ------- Point your browser to http://localhost:8080 to see EventSource in action with any modern browser (not IE). erlang-cowboy-0.8.6+dfsg1/examples/eventsource/priv/000077500000000000000000000000001223335133400224345ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/eventsource/priv/index.html000066400000000000000000000021151223335133400244300ustar00rootroot00000000000000 Hi!
erlang-cowboy-0.8.6+dfsg1/examples/eventsource/rebar.config000066400000000000000000000002511223335133400237340ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/eventsource/src/000077500000000000000000000000001223335133400222435ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/eventsource/src/eventsource.app.src000066400000000000000000000004331223335133400260750ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, eventsource, [ {description, "Cowboy EventSource example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {eventsource_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/eventsource/src/eventsource.erl000066400000000000000000000004151223335133400253110ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(eventsource). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(eventsource). erlang-cowboy-0.8.6+dfsg1/examples/eventsource/src/eventsource_app.erl000066400000000000000000000011551223335133400261530ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(eventsource_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/eventsource", eventsource_handler, []}, {"/", cowboy_static, [ {directory, {priv_dir, eventsource, []}}, {file, <<"index.html">>}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), eventsource_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/eventsource/src/eventsource_handler.erl000066400000000000000000000013671223335133400270150ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc EventSource emitter. -module(eventsource_handler). -export([init/3]). -export([info/3]). -export([terminate/3]). init(_Transport, Req, []) -> Headers = [{<<"content-type">>, <<"text/event-stream">>}], {ok, Req2} = cowboy_req:chunked_reply(200, Headers, Req), erlang:send_after(1000, self(), {message, "Tick"}), {loop, Req2, undefined, 5000}. info({message, Msg}, Req, State) -> ok = cowboy_req:chunk(["id: ", id(), "\ndata: ", Msg, "\n\n"], Req), erlang:send_after(1000, self(), {message, "Tick"}), {loop, Req, State}. terminate(_Reason, _Req, _State) -> ok. id() -> {Mega, Sec, Micro} = erlang:now(), Id = (Mega * 1000000 + Sec) * 1000000 + Micro, integer_to_list(Id, 16). erlang-cowboy-0.8.6+dfsg1/examples/eventsource/src/eventsource_sup.erl000066400000000000000000000006051223335133400262010ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(eventsource_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/eventsource/start.sh000077500000000000000000000001741223335133400231520ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s eventsource \ -eval "io:format(\"Point your browser at http://localhost:8080/~n\")." erlang-cowboy-0.8.6+dfsg1/examples/hello_world/000077500000000000000000000000001223335133400214245ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/hello_world/README.md000066400000000000000000000007261223335133400227100ustar00rootroot00000000000000Cowboy Hello World ================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. Example ------- ``` bash $ curl -i http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:10:25 GMT content-length: 12 Hello world! ``` erlang-cowboy-0.8.6+dfsg1/examples/hello_world/rebar.config000066400000000000000000000001261223335133400237050ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/hello_world/src/000077500000000000000000000000001223335133400222135ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/hello_world/src/hello_world.app.src000066400000000000000000000004331223335133400260150ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, hello_world, [ {description, "Cowboy Hello World example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {hello_world_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/hello_world/src/hello_world.erl000066400000000000000000000004151223335133400252310ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(hello_world). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(hello_world). erlang-cowboy-0.8.6+dfsg1/examples/hello_world/src/hello_world_app.erl000066400000000000000000000006671223335133400261020ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(hello_world_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), hello_world_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/hello_world/src/hello_world_sup.erl000066400000000000000000000006051223335133400261210ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(hello_world_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/hello_world/src/toppage_handler.erl000066400000000000000000000006011223335133400260500ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Hello world handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> {ok, Req2} = cowboy_req:reply(200, [], <<"Hello world!">>, Req), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/hello_world/start.sh000077500000000000000000000001731223335133400231210ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s hello_world \ -eval "io:format(\"Point your browser at http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/000077500000000000000000000000001223335133400231315ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/README.md000066400000000000000000000011161223335133400244070ustar00rootroot00000000000000Cowboy Middleware ================= To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Cowboy will serve all the files you put in the priv/ directory. If you request a .html file that has corresponding .md file that has been modified more recently than the .html file, the markdown file will be converted to HTML and served by Cowboy. HTML5 Video Example ------------------- Open http://localhost:8080/video.html in your favorite browser. erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/priv/000077500000000000000000000000001223335133400241115ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/priv/video.md000066400000000000000000000004131223335133400255370ustar00rootroot00000000000000HTML5 Video With Markdown ========================= Videos taken from [TechSlides](http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5/) erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/rebar.config000066400000000000000000000003771223335133400254220ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", "master"}}, {erlmarkdown, ".*", {git, "git://github.com/ericbmerritt/erlmarkdown.git", "rv"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/src/000077500000000000000000000000001223335133400237205ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/src/markdown_converter.erl000066400000000000000000000013571223335133400303430ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(markdown_converter). -behaviour(cowboy_middleware). -export([execute/2]). execute(Req, Env) -> {[Path], Req1} = cowboy_req:path_info(Req), case filename:extension(Path) of <<".html">> -> maybe_generate_markdown(resource_path(Path)); _Ext -> ok end, {ok, Req1, Env}. maybe_generate_markdown(Path) -> ModifiedAt = filelib:last_modified(source_path(Path)), GeneratedAt = filelib:last_modified(Path), case ModifiedAt > GeneratedAt of true -> erlmarkdown:conv_file(source_path(Path), Path); false -> ok end. resource_path(Path) -> {ok, Cwd} = file:get_cwd(), filename:join([Cwd, "priv", Path]). source_path(Path) -> << (filename:rootname(Path))/binary, ".md" >>. erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/src/markdown_middleware.app.src000066400000000000000000000005151223335133400312300ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, markdown_middleware, [ {description, "Cowboy static file handler example with middleware component."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {markdown_middleware_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/src/markdown_middleware.erl000066400000000000000000000004351223335133400304450ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(markdown_middleware). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(markdown_middleware). erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/src/markdown_middleware_app.erl000066400000000000000000000012041223335133400313000ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(markdown_middleware_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/[...]", cowboy_static, [ {directory, {priv_dir, markdown_middleware, []}}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]}, {middlewares, [cowboy_router, markdown_converter, cowboy_handler]} ]), markdown_middleware_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/src/markdown_middleware_sup.erl000066400000000000000000000006151223335133400313340ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(markdown_middleware_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/markdown_middleware/start.sh000077500000000000000000000002161223335133400246240ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s markdown_middleware \ -eval "io:format(\"Point your browser at http://localhost:8080/video.html~n\")." erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/000077500000000000000000000000001223335133400224615ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/README.md000066400000000000000000000026101223335133400237370ustar00rootroot00000000000000Cowboy Rest Hello World ======================= To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then run any given command or point your browser to the indicated URL. Examples -------- ### Get HTML ``` bash $ curl -i http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:15:52 GMT content-length: 136 Content-Type: text/html Vary: Accept REST Hello World!

REST Hello World as HTML!

``` ### Get JSON ``` bash $ curl -i -H "Accept: application/json" http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:16:46 GMT content-length: 24 Content-Type: application/json Vary: Accept {"rest": "Hello World!"} ``` ### Get text ``` bash $ curl -i -H "Accept: text/plain" http://localhost:8080 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:18:35 GMT content-length: 25 Content-Type: text/plain Vary: Accept REST Hello World as text! ``` ### Get a 406 ``` bash $ curl -i -H "Accept: text/css" http://localhost:8080 HTTP/1.1 406 Not Acceptable connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:18:51 GMT content-length: 0 ``` erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/rebar.config000066400000000000000000000001261223335133400247420ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/src/000077500000000000000000000000001223335133400232505ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/src/rest_hello_world.app.src000066400000000000000000000004521223335133400301100ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, rest_hello_world, [ {description, "Cowboy REST Hello World example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {rest_hello_world_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/src/rest_hello_world.erl000066400000000000000000000004271223335133400273260ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(rest_hello_world). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(rest_hello_world). erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/src/rest_hello_world_app.erl000066400000000000000000000007011223335133400301610ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(rest_hello_world_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), rest_hello_world_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/src/rest_hello_world_sup.erl000066400000000000000000000006121223335133400302110ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(rest_hello_world_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/src/toppage_handler.erl000066400000000000000000000015541223335133400271150ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Hello world handler. -module(toppage_handler). -export([init/3]). -export([content_types_provided/2]). -export([hello_to_html/2]). -export([hello_to_json/2]). -export([hello_to_text/2]). init(_Transport, _Req, []) -> {upgrade, protocol, cowboy_rest}. content_types_provided(Req, State) -> {[ {<<"text/html">>, hello_to_html}, {<<"application/json">>, hello_to_json}, {<<"text/plain">>, hello_to_text} ], Req, State}. hello_to_html(Req, State) -> Body = <<" REST Hello World!

REST Hello World as HTML!

">>, {Body, Req, State}. hello_to_json(Req, State) -> Body = <<"{\"rest\": \"Hello World!\"}">>, {Body, Req, State}. hello_to_text(Req, State) -> {<<"REST Hello World as text!">>, Req, State}. erlang-cowboy-0.8.6+dfsg1/examples/rest_hello_world/start.sh000077500000000000000000000006521223335133400241600ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s rest_hello_world \ -eval "io:format(\"Get HTML: curl -i http://localhost:8080~n\")." \ -eval "io:format(\"Get JSON: curl -i -H \\\"Accept: application/json\\\" http://localhost:8080~n\")." \ -eval "io:format(\"Get text: curl -i -H \\\"Accept: text/plain\\\" http://localhost:8080~n\")." \ -eval "io:format(\"Get a 406: curl -i -H \\\"Accept: text/css\\\" http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/000077500000000000000000000000001223335133400217545ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/README.md000066400000000000000000000027061223335133400232400ustar00rootroot00000000000000Cowboy Rest Hello World ======================= To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then run any given command or point your browser to the indicated URL. Examples -------- To upload something to the paste application, you can use curl like: ``` | curl -i --data-urlencode paste@- localhost:8080 ``` or to upload my_file: ``` curl -i --data-urlencode paste@my_file localhost:8080 ``` The URL of your data will be in the location header. Alternately, you can visit http://localhost:8080 with your favorite web browser and submit your paste via the form. Code that has been pasted can be highlighted with ?lang= option if you have [highlight](http://www.andre-simon.de/doku/highlight/en/highlight.html) installed (although pygments or any other should work just fine). For example: ``` curl -i --data-urlencode paste@priv/index.html localhost:8080 curl ``` Will show the text of the html file. If your terminal supports color sequences and highlight is installed: ``` curl ?lang=html ``` Will show a syntax highlighted version of the source file. If you open the same URL in your web browser and your web browser tells cowboy that it prefers html files, you will see the file highlighted with html/css markup. Firefox is known to work. erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/priv/000077500000000000000000000000001223335133400227345ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/priv/index.html000066400000000000000000000007741223335133400247410ustar00rootroot00000000000000 Simple Pastebin

Simple Pastebin

You can paste your text into the text field to submit, or you can capture the output of a command with:

command | curl -i --data-urlencode paste@- localhost:8080
erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/priv/index.txt000066400000000000000000000003061223335133400246030ustar00rootroot00000000000000Simple Pastebin --------------- You can paste your text into the text field to submit, or you can capture the output of a command with: | curl -i --data-urlencode paste@- localhost:8080 erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/rebar.config000066400000000000000000000001261223335133400242350ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/src/000077500000000000000000000000001223335133400225435ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/src/rest_pastebin.app.src000066400000000000000000000004651223335133400267020ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, rest_pastebin, [ {description, "Cowboy REST Pastebin example inspired by sprunge."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {rest_pastebin_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/src/rest_pastebin.erl000066400000000000000000000004211223335133400261060ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(rest_pastebin). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(rest_pastebin). erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/src/rest_pastebin_app.erl000066400000000000000000000007061223335133400267540ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(rest_pastebin_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/[:paste_id]", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), rest_pastebin_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/src/rest_pastebin_sup.erl000066400000000000000000000006071223335133400270030ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(rest_pastebin_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/src/toppage_handler.erl000066400000000000000000000067711223335133400264160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Pastebin handler. -module(toppage_handler). %% REST Callbacks -export([init/3]). -export([allowed_methods/2]). -export([content_types_provided/2]). -export([content_types_accepted/2]). -export([resource_exists/2]). %% Callback Callbacks -export([create_paste/2]). -export([paste_html/2]). -export([paste_text/2]). init(_Transport, _Req, []) -> % For the random number generator: {X, Y, Z} = now(), random:seed(X, Y, Z), {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"GET">>, <<"POST">>], Req, State}. content_types_provided(Req, State) -> {[ {{<<"text">>, <<"plain">>, []}, paste_text}, {{<<"text">>, <<"html">>, []}, paste_html} ], Req, State}. content_types_accepted(Req, State) -> {[{{<<"application">>, <<"x-www-form-urlencoded">>, []}, create_paste}], Req, State}. resource_exists(Req, _State) -> case cowboy_req:binding(paste_id, Req) of {undefined, Req2} -> {true, Req2, index}; {PasteID, Req2} -> case valid_path(PasteID) and file_exists(PasteID) of true -> {true, Req2, PasteID}; false -> {false, Req2, PasteID} end end. create_paste(Req, State) -> PasteID = new_paste_id(), {ok, [{<<"paste">>, Paste}], Req3} = cowboy_req:body_qs(Req), ok = file:write_file(full_path(PasteID), Paste), case cowboy_req:method(Req3) of {<<"POST">>, Req4} -> {<<$/, PasteID/binary>>, Req4, State}; {_, Req4} -> {true, Req4, State} end. paste_html(Req, index) -> {read_file("index.html"), Req, index}; paste_html(Req, Paste) -> {Style, Req2} = cowboy_req:qs_val(<<"lang">>, Req, plain), {format_html(Paste, Style), Req2, Paste}. paste_text(Req, index) -> {read_file("index.txt"), Req, index}; paste_text(Req, Paste) -> {Style, Req2} = cowboy_req:qs_val(<<"lang">>, Req, plain), {format_text(Paste, Style), Req2, Paste}. % Private read_file(Name) -> {ok, Binary} = file:read_file(full_path(Name)), Binary. full_path(Name) -> {ok, Cwd} = file:get_cwd(), filename:join([Cwd, "priv", Name]). file_exists(Name) -> case file:read_file_info(full_path(Name)) of {ok, _Info} -> true; {error, _Reason} -> false end. valid_path(<<>>) -> true; valid_path(<<$., _T/binary>>) -> false; valid_path(<<$/, _T/binary>>) -> false; valid_path(<<_Char, T/binary>>) -> valid_path(T). new_paste_id() -> Initial = random:uniform(62) - 1, new_paste_id(<>, 7). new_paste_id(Bin, 0) -> Chars = <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890">>, << <<(binary_part(Chars, B, 1))/binary>> || <> <= Bin >>; new_paste_id(Bin, Rem) -> Next = random:uniform(62) - 1, new_paste_id(<>, Rem - 1). format_html(Paste, plain) -> Text = escape_html_chars(read_file(Paste)), <<"", "paste", "
", Text/binary, "
\n">>; format_html(Paste, Lang) -> highlight(full_path(Paste), Lang, "html"). format_text(Paste, plain) -> read_file(Paste); format_text(Paste, Lang) -> highlight(full_path(Paste), Lang, "ansi"). highlight(Path, Lang, Type) -> Path1 = binary_to_list(Path), Lang1 = binary_to_list(Lang), os:cmd(["highlight --syntax=", Lang1, " --doc-title=paste ", " --out-format=", Type, " --include-style ", Path1]). % Escape some HTML characters that might make a fuss escape_html_chars(Bin) -> << <<(escape_html_char(B))/binary>> || <> <= Bin >>. escape_html_char($<) -> <<"<">>; escape_html_char($>) -> <<">">>; escape_html_char($&) -> <<"&">>; escape_html_char(C) -> <>. erlang-cowboy-0.8.6+dfsg1/examples/rest_pastebin/start.sh000077500000000000000000000006041223335133400234500ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s rest_pastebin \ -eval "io:format(\"Upload: echo foo | curl -i --data-urlencode paste@- localhost:8080~n\")." \ -eval "io:format(\"Get: curl ~n\")." \ -eval "io:format(\"Get with highlighting: curl ?lang=~n\")." \ -eval "io:format(\"To get html, point your browser to http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/000077500000000000000000000000001223335133400233605ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/README.md000066400000000000000000000022361223335133400246420ustar00rootroot00000000000000Cowboy REST Streaming Responses =============================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` This example simulates streaming a large amount of data from a data store one record at a time in CSV format. It also uses a constraint to ensure that the last segment of the route is an integer. Examples -------- ### Get records with a field 2 value of 1 ``` bash $ curl -i localhost:8080 HTTP/1.1 200 OK transfer-encoding: identity server: Cowboy date: Sun, 10 Feb 2013 19:32:16 GMT connection: close content-type: text/csv DBUZGQ0C,1,28 BgoQAxMV,1,6 DAYEFxER,1,18 ... ``` ### Get records with a field 2 value of 4 ``` bash $ curl -i localhost:8080/4 HTTP/1.1 200 OK transfer-encoding: identity server: Cowboy date: Sun, 10 Feb 2013 19:34:31 GMT connection: close content-type: text/csv ABcFDxcE,4,42 DgYQCgEE,4,5 CA8BBhYD,4,10 ... ``` ### Get a 404 ``` bash $ curl -i localhost:8080/foo HTTP/1.1 404 Not Found connection: keep-alive server: Cowboy date: Sun, 10 Feb 2013 19:36:16 GMT content-length: 0 ``` erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/rebar.config000066400000000000000000000001261223335133400256410ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/src/000077500000000000000000000000001223335133400241475ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/src/rest_stream_response.app.src000066400000000000000000000004551223335133400317110ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, rest_stream_response, [ {description, "Cowboy REST with streaming."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {rest_stream_response_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/src/rest_stream_response.erl000066400000000000000000000004371223335133400311250ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(rest_stream_response). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(rest_stream_response). erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/src/rest_stream_response_app.erl000066400000000000000000000016051223335133400317630ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(rest_stream_response_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Table = ets:new(stream_tab, []), generate_rows(Table, 1000), Dispatch = cowboy_router:compile([ {'_', [ {"/[:v1]", [{v1, int}], toppage_handler, Table} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), rest_stream_response_sup:start_link(). stop(_State) -> ok. generate_rows(_Table, 0) -> ok; generate_rows(Table, N) -> ets:insert(Table, {key(), val(), val()}), generate_rows(Table, N - 1). key() -> key(10). key(N) -> key(<< (random:uniform(26) - 1) >>, N - 1). key(Acc, 0) -> binary_part(base64:encode(Acc), 0, 8); key(Acc, N) -> key(<< Acc/binary, (random:uniform(26) - 1) >>, N - 1). val() -> random:uniform(50). erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/src/rest_stream_response_sup.erl000066400000000000000000000006161223335133400320130ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(rest_stream_response_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/src/toppage_handler.erl000066400000000000000000000021351223335133400300100ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Streaming handler. -module(toppage_handler). -export([init/3]). -export([rest_init/2]). -export([content_types_provided/2]). -export([streaming_csv/2]). init(_Transport, _Req, _Table) -> {upgrade, protocol, cowboy_rest}. rest_init(Req, Table) -> {ok, Req, Table}. content_types_provided(Req, State) -> {[ {{<<"text">>, <<"csv">>, []}, streaming_csv} ], Req, State}. streaming_csv(Req, Table) -> {N, Req1} = cowboy_req:binding(v1, Req, 1), MS = [{{'$1', '$2', '$3'}, [{'==', '$2', N}], ['$$']}], {{stream, result_streamer(Table, MS)}, Req1, Table}. result_streamer(Table, MS) -> fun (Socket, Transport) -> send_records(Socket, Transport, ets:select(Table, MS, 1)) end. send_records(Socket, Transport, {[Rec], Cont}) -> timer:sleep(500), send_line(Socket, Transport, Rec), send_records(Socket, Transport, ets:select(Cont)); send_records(_Socket, _Transport, '$end_of_table') -> ok. send_line(Socket, Transport, [Key, V1, V2]) -> Transport:send(Socket, [Key, $,, integer_to_list(V1), $,, integer_to_list(V2), $\r, $\n]). erlang-cowboy-0.8.6+dfsg1/examples/rest_stream_response/start.sh000077500000000000000000000002111223335133400250460ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s rest_stream_response \ -eval "io:format(\"Streaming results: curl -i http://localhost:8080~n\")." erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/000077500000000000000000000000001223335133400223055ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/README.md000066400000000000000000000011261223335133400235640ustar00rootroot00000000000000Cowboy Hello World ================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL. You will need to temporarily trust the root certificate authority in `priv/ssl/cowboy-ca.crt`. Example ------- ``` bash $ curl --cacert priv/ssl/cowboy-ca.crt -i https://localhost:8443 HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:10:25 GMT content-length: 12 Hello world! ``` erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/priv/000077500000000000000000000000001223335133400232655ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/priv/ssl/000077500000000000000000000000001223335133400240665ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/priv/ssl/cowboy-ca.crt000066400000000000000000000016241223335133400264660ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICeDCCAeGgAwIBAgIJAOvpU0y2e5J4MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczETMBEGA1UECgwKTmluZSBOaW5lczEPMA0G A1UECwwGQ293Ym95MRAwDgYDVQQDDAdST09UIENBMB4XDTEzMDIyODA1MTAwMVoX DTMzMDIyMzA1MTAwMVowVTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRMw EQYDVQQKDApOaW5lIE5pbmVzMQ8wDQYDVQQLDAZDb3dib3kxEDAOBgNVBAMMB1JP T1QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMzmY7Us06yjyUbpqwPx Iv+xh/g3V7we07ClC9GEYnvr3OQvdA1jFEHccMBUUjRoQ8DPd6uSyK5UkixABs08 Tt5B3VsnGKr0DIN+IO4SN2PkmBqIU/BN3KdcwN65YNr3iM0KsKWeFtAZdYx4CakX 7REbO0wjK20AH3xSBn3uFGiBAgMBAAGjUDBOMB0GA1UdDgQWBBRKfZ8KF2jlLBDm NL6IuEuGY0pdbzAfBgNVHSMEGDAWgBRKfZ8KF2jlLBDmNL6IuEuGY0pdbzAMBgNV HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAG1I0kBxXiLkM1b7rl2zPLizREYg 1m+ajb6rWzPOBg6TXjv58Be+H4tqoHIL/M/crixew5emftBkuAGjiKMhbIokjvan aPTCV8U6HHvNvz9c68HpESWbd+56cHqfsS5XCKp1OpW5tbL2UQYpFKMP4qmbv3Ea pBfPPmSFMBb1i2AI -----END CERTIFICATE----- erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/priv/ssl/server.crt000066400000000000000000000017211223335133400261070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICpTCCAg6gAwIBAgIJAOvpU0y2e5J5MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczETMBEGA1UECgwKTmluZSBOaW5lczEPMA0G A1UECwwGQ293Ym95MRAwDgYDVQQDDAdST09UIENBMB4XDTEzMDIyODA1MjMzNFoX DTMzMDIyMzA1MjMzNFowVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRMw EQYDVQQKDApOaW5lIE5pbmVzMQ8wDQYDVQQLDAZDb3dib3kxEjAQBgNVBAMMCWxv Y2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzbW1GjECzHUc/WST qLiAGqjCNccR5saVS+yoz2SPRhpoyf0/qBrX5BY0tzmgozoTiRfE4wCiVD99Cc+D rp/FM49r4EpZdocIovprmOmv/gwkoj95zaA6PKNn1OdmDp2hwJsX2Zm3kpbGUZTx jDkkccmgUb4EjL7qNHq7saQtivUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE FB6jTEIWI8T1ckORA4GezbyYxtbvMB8GA1UdIwQYMBaAFEp9nwoXaOUsEOY0voi4 S4ZjSl1vMA0GCSqGSIb3DQEBBQUAA4GBACMboVQjrx8u/fk3gl/sR0tbA0Wf/NcS 2Dzsy2czndgVUAG4Sqb+hfgn0dqAyUKghRrj3JDcYxYksGPIklDfPzZb7yJ39l16 6x5ZiIzhp8CAVdPvRxRznw5rZwaXesryXu1jVSZxTr3MYZdkG6KaAM0t90+YlGLZ UG8fAicx0Bf+ -----END CERTIFICATE----- erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/priv/ssl/server.key000066400000000000000000000015671223335133400261170ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDNtbUaMQLMdRz9ZJOouIAaqMI1xxHmxpVL7KjPZI9GGmjJ/T+o GtfkFjS3OaCjOhOJF8TjAKJUP30Jz4Oun8Uzj2vgSll2hwii+muY6a/+DCSiP3nN oDo8o2fU52YOnaHAmxfZmbeSlsZRlPGMOSRxyaBRvgSMvuo0eruxpC2K9QIDAQAB AoGAaD85c/h6bpq7Aj7CBbLaWKhFI3OqwsTITB22vsM7SE+B4zsP02UnG1OVi3UM zytTUxpUkKV1njQ+bYZYOVqGWF4Up8tTqUglHn0FTPok1AIemELWtz3sXvdSHC1T lqvFBAZ9kibn13qGyVOiyCFaMwfOM/05RvV7p3jfUMTWnNECQQDs7yCJZ8Ol8MyH TGZzvkjoN2zg1KwmTbSD1hkP6QAJtPdRuqFbjlEru0/PefgOXsWLRIa3/3v0qw2G xGkV6AXTAkEA3kNbFisqUydjPnZIYv/P6SvPdUimHJEjXbAbfNfzS9dzszrOVJd2 XqGH7z5yzjoH3IyaIMW8GnubVzGDSjrHFwJAKSU5vELlygpwKkrNO+pelN0TLlQg dSJnZ8GlZorq88SWcn37iX/EftivenNO7YftvEqxLoDSkOGnnrC7Iw/A+wJBAIEe L/QY72WPJCBNJpAce/PA96vyoE1II3txqwZDjZspdpVQPDz4IFOpEwbxCFC1dYuy Qnd3Z2cbF4r3wIWGz9ECQQCJGNhUNtY+Om1ELdqPcquxE2VRV/pucnvJSTKwyo2C Rvm6H7kFDwPDuN23YnTOlTiho0zzCkclcIukhIVJ+dKz -----END RSA PRIVATE KEY----- erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/rebar.config000066400000000000000000000001261223335133400245660ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/src/000077500000000000000000000000001223335133400230745ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/src/ssl_hello_world.app.src000066400000000000000000000004541223335133400275620ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, ssl_hello_world, [ {description, "Cowboy Hello World example with SSL."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {ssl_hello_world_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/src/ssl_hello_world.erl000066400000000000000000000004251223335133400267740ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ssl_hello_world). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(ssl_hello_world). erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/src/ssl_hello_world_app.erl000066400000000000000000000010641223335133400276340ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(ssl_hello_world_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []} ]} ]), {ok, _} = cowboy:start_https(https, 100, [ {port, 8443}, {cacertfile, "priv/ssl/cowboy-ca.crt"}, {certfile, "priv/ssl/server.crt"}, {keyfile, "priv/ssl/server.key"} ], [{env, [{dispatch, Dispatch}]}]), ssl_hello_world_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/src/ssl_hello_world_sup.erl000066400000000000000000000006111223335133400276600ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(ssl_hello_world_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/src/toppage_handler.erl000066400000000000000000000006011223335133400267310ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Hello world handler. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> {ok, Req2} = cowboy_req:reply(200, [], <<"Hello world!">>, Req), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/ssl_hello_world/start.sh000077500000000000000000000002001223335133400237710ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s ssl_hello_world \ -eval "io:format(\"Point your browser at https://localhost:8443~n\")." erlang-cowboy-0.8.6+dfsg1/examples/static_world/000077500000000000000000000000001223335133400216105ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/static_world/README.md000066400000000000000000000021011223335133400230610ustar00rootroot00000000000000Cowboy Static File Handler ========================== To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Cowboy will serve all the files you put in the priv/ directory. You can replace the filename given in the example URL with the one of a file you added to this directory to receive that file. Example ------- Show that the file is returned as an octet-stream ``` bash $ curl -i http://localhost:8080/test.txt HTTP/1.1 200 OK connection: keep-alive server: Cowboy date: Fri, 28 Sep 2012 04:19:40 GMT content-length: 52 Content-Type: application/octet-stream Last-Modified: Fri, 28 Sep 2012 04:01:20 GMT If you read this then the static file server works! ``` Finally download and cat the file to verify ``` bash $ curl -sLO http://localhost:8080/test.txt $ cat test.txt If you read this then the static file server works! ``` HTML5 Video Example ------------------- Open http://localhost:8080/video.html in your favorite browser. erlang-cowboy-0.8.6+dfsg1/examples/static_world/priv/000077500000000000000000000000001223335133400225705ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/static_world/priv/test.txt000066400000000000000000000000641223335133400243100ustar00rootroot00000000000000If you read this then the static file server works! erlang-cowboy-0.8.6+dfsg1/examples/static_world/priv/video.html000066400000000000000000000004731223335133400245700ustar00rootroot00000000000000

HTML5 Video Example

Videos taken from TechSlides

erlang-cowboy-0.8.6+dfsg1/examples/static_world/rebar.config000066400000000000000000000002511223335133400240700ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/static_world/src/000077500000000000000000000000001223335133400223775ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/static_world/src/static_world.app.src000066400000000000000000000004451223335133400263700ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, static_world, [ {description, "Cowboy static file handler example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {static_world_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/static_world/src/static_world.erl000066400000000000000000000004171223335133400256030ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(static_world). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(static_world). erlang-cowboy-0.8.6+dfsg1/examples/static_world/src/static_world_app.erl000066400000000000000000000010521223335133400264370ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(static_world_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/[...]", cowboy_static, [ {directory, {priv_dir, static_world, []}}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]} ]), static_world_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/static_world/src/static_world_sup.erl000066400000000000000000000006061223335133400264720ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(static_world_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/static_world/start.sh000077500000000000000000000003311223335133400233010ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s static_world \ -eval "io:format(\"Point your browser at http://localhost:8080/test.txt~n\")." \ -eval "io:format(\"Point your browser at http://localhost:8080/video.html~n\")." erlang-cowboy-0.8.6+dfsg1/examples/web_server/000077500000000000000000000000001223335133400212555ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/web_server/README.md000066400000000000000000000015421223335133400225360ustar00rootroot00000000000000Cowboy Static File Handler with Index Support ============================================= To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Cowboy will serve all the files you put in the priv/ directory. You can replace the filename given in the example URL with the one of a file you added to this directory to receive that file. A middleware has been added that will re-route the request to a different handler if the requested path is a directory. Example ------- Point your browser to http://localhost:8080 to see the contents of `priv/`. You can click on a link to see that file. If HTML is not preferred, the contents of a directory will be listed as a JSON array (e.g. with `curl http://localhost:8080`). erlang-cowboy-0.8.6+dfsg1/examples/web_server/priv/000077500000000000000000000000001223335133400222355ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/web_server/priv/test.txt000066400000000000000000000000641223335133400237550ustar00rootroot00000000000000If you read this then the static file server works! erlang-cowboy-0.8.6+dfsg1/examples/web_server/priv/video.html000066400000000000000000000004731223335133400242350ustar00rootroot00000000000000

HTML5 Video Example

Videos taken from TechSlides

erlang-cowboy-0.8.6+dfsg1/examples/web_server/rebar.config000066400000000000000000000003641223335133400235420ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", "master"}}, {jsx, ".*", {git, "git://github.com/talentdeficit/jsx.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/000077500000000000000000000000001223335133400220445ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/directory_handler.erl000066400000000000000000000025061223335133400262540ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @doc Directory handler. -module(directory_handler). %% REST Callbacks -export([init/3]). -export([rest_init/2]). -export([allowed_methods/2]). -export([resource_exists/2]). -export([content_types_provided/2]). %% Callback Callbacks -export([list_json/2]). -export([list_html/2]). init(_Transport, _Req, _Paths) -> {upgrade, protocol, cowboy_rest}. rest_init(Req, Paths) -> {ok, Req, Paths}. allowed_methods(Req, State) -> {[<<"GET">>], Req, State}. resource_exists(Req, {ReqPath, FilePath}) -> case file:list_dir(FilePath) of {ok, Fs} -> {true, Req, {ReqPath, lists:sort(Fs)}}; _Err -> {false, Req, {ReqPath, FilePath}} end. content_types_provided(Req, State) -> {[ {{<<"application">>, <<"json">>, []}, list_json}, {{<<"text">>, <<"html">>, []}, list_html} ], Req, State}. list_json(Req, {Path, Fs}) -> Files = [[ <<(list_to_binary(F))/binary>> || F <- Fs ]], {jsx:encode(Files), Req, Path}. list_html(Req, {Path, Fs}) -> Body = [[ links(Path, F) || F <- [".."|Fs] ]], HTML = [<<"Index", "">>, Body, <<"\n">>], {HTML, Req, Path}. links(<<>>, File) -> ["", File, "
\n"]; links(Prefix, File) -> ["", File, "
\n"]. erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/directory_lister.erl000066400000000000000000000022601223335133400261360ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(directory_lister). -behaviour(cowboy_middleware). -export([execute/2]). execute(Req, Env) -> case lists:keyfind(handler, 1, Env) of {handler, cowboy_static} -> redirect_directory(Req, Env); _H -> {ok, Req, Env} end. redirect_directory(Req, Env) -> {Path, Req1} = cowboy_req:path_info(Req), Path1 = << <> || S <- Path >>, {handler_opts, StaticOpts} = lists:keyfind(handler_opts, 1, Env), {dir_handler, DirHandler} = lists:keyfind(dir_handler, 1, StaticOpts), FullPath = resource_path(Path1), case valid_path(Path) and filelib:is_dir(FullPath) of true -> handle_directory(Req1, Env, Path1, FullPath, DirHandler); false -> {ok, Req1, Env} end. handle_directory(Req, Env, Prefix, Path, DirHandler) -> Env1 = lists:keydelete(handler, 1, lists:keydelete(handler_opts, 1, Env)), {ok, Req, [{handler, DirHandler}, {handler_opts, {Prefix, Path}} | Env1]}. valid_path([]) -> true; valid_path([<<"..">> | _T]) -> false; valid_path([<<"/", _/binary>> | _T]) -> false; valid_path([_H | Rest]) -> valid_path(Rest). resource_path(Path) -> {ok, Cwd} = file:get_cwd(), filename:join([Cwd, "priv", Path]). erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/web_server.app.src000066400000000000000000000004601223335133400254770ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, web_server, [ {description, "Cowboy static file handler with directory indexes."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {web_server_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/web_server.erl000066400000000000000000000004131223335133400247110ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(web_server). %% API. -export([start/0]). %% API. start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(web_server). erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/web_server_app.erl000066400000000000000000000012151223335133400255520ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(web_server_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/[...]", cowboy_static, [ {directory, {priv_dir, web_server, []}}, {dir_handler, directory_handler}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]}, {middlewares, [cowboy_router, directory_lister, cowboy_handler]} ]), web_server_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/web_server/src/web_server_sup.erl000066400000000000000000000006041223335133400256020ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(web_server_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/web_server/start.sh000077500000000000000000000001731223335133400227520ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s web_server \ -eval "io:format(\"Point your browser at http://localhost:8080/~n\")." erlang-cowboy-0.8.6+dfsg1/examples/websocket/000077500000000000000000000000001223335133400211005ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/websocket/README.md000066400000000000000000000005651223335133400223650ustar00rootroot00000000000000Cowboy websocket ================ To compile this example you need rebar in your PATH. Type the following command: ``` $ rebar get-deps compile ``` You can then start the Erlang node with the following command: ``` ./start.sh ``` Then point your browser to the indicated URL to open a websocket client. Not all browsers support websockets. It was tested with Chromium. erlang-cowboy-0.8.6+dfsg1/examples/websocket/priv/000077500000000000000000000000001223335133400220605ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/websocket/priv/html_ws_client.html000066400000000000000000000057631223335133400257740ustar00rootroot00000000000000 Websocket client erlang-cowboy-0.8.6+dfsg1/examples/websocket/rebar.config000066400000000000000000000002511223335133400233600ustar00rootroot00000000000000{deps, [ {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", "master"}} ]}. erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/000077500000000000000000000000001223335133400216675ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/toppage_handler.erl000066400000000000000000000010621223335133400255260ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(toppage_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init(_Transport, Req, []) -> {ok, Req, undefined}. handle(Req, State) -> Html = get_html(), {ok, Req2} = cowboy_req:reply(200, [{<<"content-type">>, <<"text/html">>}], Html, Req), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. get_html() -> {ok, Cwd} = file:get_cwd(), Filename =filename:join([Cwd, "priv", "html_ws_client.html"]), {ok, Binary} = file:read_file(Filename), Binary. erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/websocket.app.src000066400000000000000000000004251223335133400251460ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. {application, websocket, [ {description, "Cowboy websocket example."}, {vsn, "1"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib, cowboy ]}, {mod, {websocket_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/websocket.erl000066400000000000000000000004001223335133400243530ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(websocket). %% API. -export([start/0]). start() -> ok = application:start(crypto), ok = application:start(ranch), ok = application:start(cowboy), ok = application:start(websocket). erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/websocket_app.erl000066400000000000000000000011611223335133400252200ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(websocket_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> Dispatch = cowboy_router:compile([ {'_', [ {"/", toppage_handler, []}, {"/websocket", ws_handler, []}, {"/static/[...]", cowboy_static, [ {directory, {priv_dir, websocket, [<<"static">>]}}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]), {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [{env, [{dispatch, Dispatch}]}]), websocket_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/websocket_sup.erl000066400000000000000000000006031223335133400252470ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. %% @private -module(websocket_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% supervisor. init([]) -> Procs = [], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/examples/websocket/src/ws_handler.erl000066400000000000000000000015021223335133400245170ustar00rootroot00000000000000-module(ws_handler). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). init({tcp, http}, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> erlang:start_timer(1000, self(), <<"Hello!">>), {ok, Req, undefined_state}. websocket_handle({text, Msg}, Req, State) -> {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; websocket_handle(_Data, Req, State) -> {ok, Req, State}. websocket_info({timeout, _Ref, Msg}, Req, State) -> erlang:start_timer(1000, self(), <<"How' you doin'?">>), {reply, {text, Msg}, Req, State}; websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/examples/websocket/start.sh000077500000000000000000000002371223335133400225760ustar00rootroot00000000000000#!/bin/sh erl -pa ebin deps/*/ebin -s websocket \ -eval "io:format(\"Point your browser at http://localhost:8080/ to use a simple websocket client~n\")." erlang-cowboy-0.8.6+dfsg1/guide/000077500000000000000000000000001223335133400163715ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/guide/handlers.md000066400000000000000000000031771223335133400205230ustar00rootroot00000000000000Handlers ======== Purpose ------- Handlers are Erlang modules that represent a resource. Handlers must process the request and send a reply. The nature of the reply will vary between handlers. Different kinds of handlers can be combined in a single module. This allows a module to handle both websocket and long-polling code in a single place, for example. Protocol upgrades ----------------- Cowboy features many different handlers: HTTP handlers, loop handlers, websocket handlers, REST handlers and static handlers. All of them have a common entry point: the `init/3` function. By default, Cowboy considers your handler to be an HTTP handler. To switch to a different protocol, like, for example, Websocket, you must perform a protocol upgrade. This is done by returning a protocol upgrade tuple at the end of `init/3`. The following snippet upgrades the handler to `my_protocol`. ``` erlang init(_Any, _Req, _Opts) -> {upgrade, protocol, my_protocol}. ``` Cowboy comes with two protocol upgrades: `cowboy_rest` and `cowboy_websocket`. Use these values in place of `my_protocol` to use them. Custom protocol upgrades ------------------------ The `my_protocol` module above will be used for further processing of the request. It should use the `cowboy_sub_protocol` behaviour, which requires only one callback, `upgrade/4`. It receives the request object, the middleware environment, and the handler this request has been routed to along with its options. ``` erlang upgrade(Req, Env, Handler, HandlerOpts) -> %% ... ``` This callback is expected to behave like any middleware. Please see the corresponding chapter for more information. erlang-cowboy-0.8.6+dfsg1/guide/hooks.md000066400000000000000000000046161223335133400200450ustar00rootroot00000000000000Hooks ===== On request ---------- The `onrequest` hook is called as soon as Cowboy finishes fetching the request headers. It occurs before any other processing, including routing. It can be used to perform any modification needed on the request object before continuing with the processing. If a reply is sent inside this hook, then Cowboy will move on to the next request, skipping any subsequent handling. This hook is a function that takes a request object as argument, and returns a request object. This function MUST NOT crash. Cowboy will not send any reply if a crash occurs in this function. You can specify the `onrequest` hook when creating the listener, inside the request options. ``` erlang cowboy:start_http(my_http_listener, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]}, {onrequest, fun ?MODULE:debug_hook/1} ] ). ``` The following hook function prints the request object everytime a request is received. This can be useful for debugging, for example. ``` erlang debug_hook(Req) -> erlang:display(Req), Req. ``` Make sure to always return the last request object obtained. On response ----------- The `onresponse` hook is called right before sending the response to the socket. It can be used for the purposes of logging responses, or for modifying the response headers or body. The best example is providing custom error pages. Note that like the `onrequest` hook, this function MUST NOT crash. Cowboy may or may not send a reply if this function crashes. If a reply is sent, the hook MUST explicitly provide all headers that are needed. You can specify the `onresponse` hook when creating the listener also. ``` erlang cowboy:start_http(my_http_listener, 100, [{port, 8080}], [ {env, [{dispatch, Dispatch}]}, {onresponse, fun ?MODULE:custom_404_hook/4} ] ). ``` The following hook function will provide a custom body for 404 errors when it has not been provided before, and will let Cowboy proceed with the default response otherwise. ``` erlang custom_404_hook(404, Headers, <<>>, Req) -> Body = <<"404 Not Found.">>, Headers2 = lists:keyreplace(<<"content-length">>, 1, Headers, {<<"content-length">>, integer_to_list(byte_size(Body))}), {ok, Req2} = cowboy_req:reply(404, Headers2, Body, Req), Req2; custom_404_hook(_, _, _, Req) -> Req. ``` Again, make sure to always return the last request object obtained. erlang-cowboy-0.8.6+dfsg1/guide/http_handlers.md000066400000000000000000000027051223335133400215560ustar00rootroot00000000000000HTTP handlers ============= Purpose ------- HTTP handlers are the simplest Cowboy module to handle a request. Usage ----- You need to implement three callbacks for HTTP handlers. The first, `init/3`, is common to all handlers. It receives three arguments: a tuple containing the transport and protocol in use, the `Req` object and the handler options you defined in the routes. In the context of HTTP handlers this should be used for any initialization needs. For example you can initialize here the `State` variable that will be passed to the following functions. The second callback, `handle/2`, is where most of your code should be. It receives two arguments: the `Req` object and the `State` previously defined. As the name explains, this is where you handle the request. The last callback, `terminate/3`, will be empty most of the time. It receives three arguments: the `Reason` for termination, the `Req` object and the `State` previously defined. This callback should be used strictly for cleaning up. Replying using the `Req` is disallowed. If you used the process dictionary, timers, monitors then you most likely want to stop them in this callback, as Cowboy might end up reusing this process for subsequent requests. Please see the Internals chapter for more information. Of course the general advice is to not use the process dictionary, and that any operation requiring reception of messages should be done in a loop handler, documented in its own chapter. erlang-cowboy-0.8.6+dfsg1/guide/internals.md000066400000000000000000000056751223335133400207270ustar00rootroot00000000000000Internals ========= Architecture ------------ Cowboy is a lightweight HTTP server. It is built on top of Ranch. Please see the Ranch guide for more informations. It uses only one process per connection. The process where your code runs is the process controlling the socket. Using one process instead of two allows for lower memory usage. It uses binaries. Binaries are more efficient than lists for representing strings because they take less memory space. Processing performance can vary depending on the operation. Binaries are known for generally getting a great boost if the code is compiled natively. Please see the HiPE documentation for more details. Because querying for the current date and time can be expensive, Cowboy generates one `Date` header value every second, shares it to all other processes, which then simply copy it in the response. This allows compliance with HTTP/1.1 with no actual performance loss. One process for many requests ----------------------------- As previously mentioned, Cowboy only use one process per connection. Because there can be more than one request per connection with the keepalive feature of HTTP/1.1, that means the same process will be used to handle many requests. Because of this, you are expected to make sure your process cleans up before terminating the handling of the current request. This may include cleaning up the process dictionary, timers, monitoring and more. Lowercase header names ---------------------- For consistency reasons it has been chosen to convert all header names to lowercase binary strings. This prevents the programmer from making case mistakes, and is possible because header names are case insensitive. This works fine for the large majority of clients. However, some badly implemented clients, especially ones found in corporate code or closed source products, may not handle header names in a case insensitive manner. This means that when Cowboy returns lowercase header names, these clients will not find the headers they are looking for. A simple way to solve this is to create an `onresponse` hook that will format the header names with the expected case. ``` erlang capitalize_hook(Status, Headers, Body, Req) -> Headers2 = [{cowboy_bstr:capitalize_token(N), V} || {N, V} <- Headers], {ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req), Req2. ``` Improving performance --------------------- By default the maximum number of active connections is set to a generally accepted big enough number. This is meant to prevent having too many processes performing potentially heavy work and slowing everything else down, or taking up all the memory. Disabling this feature, by setting the `{max_connections, infinity}` protocol option, would give you greater performance when you are only processing short-lived requests. Another option is to define platform-specific socket options that are known to improve their efficiency. Please see the Ranch guide for more information. erlang-cowboy-0.8.6+dfsg1/guide/introduction.md000066400000000000000000000106651223335133400214440ustar00rootroot00000000000000Introduction ============ Purpose ------- Cowboy is a small, fast and modular HTTP server written in Erlang. Cowboy aims to provide a complete HTTP stack, including its derivatives SPDY, Websocket and REST. Cowboy currently supports HTTP/1.0, HTTP/1.1, Websocket (all implemented drafts + standard) and Webmachine-based REST. Cowboy is a high quality project. It has a small code base, is very efficient (both in latency and memory use) and can easily be embedded in another application. Cowboy is clean Erlang code. It bans the use of parameterized modules and the process dictionary. It includes documentation and typespecs for all public interfaces. Prerequisites ------------- It is assumed the developer already knows Erlang and has basic knowledge about the HTTP protocol. In order to run the examples available in this user guide, you will need Erlang and rebar installed and in your $PATH. Please see the [rebar repository](https://github.com/basho/rebar) for downloading and building instructions. Please look up the environment variables documentation of your system for details on how to update the $PATH information. Supported platforms ------------------- Cowboy is tested and supported on Linux. Cowboy has been reported to work on other platforms, but we make no guarantee that the experience will be safe and smooth. You are advised to perform the necessary testing and security audits prior to deploying on other platforms. Cowboy is developed for Erlang R15B+. Cowboy may be compiled on earlier Erlang versions with small source code modifications but there is no guarantee that it will work as expected. Conventions ----------- In the HTTP protocol, the method name is case sensitive. All standard method names are uppercase. Header names are case insensitive. Cowboy converts all the request header names to lowercase, and expects your application to provide lowercase header names in the response. Getting started --------------- Cowboy does nothing by default. Cowboy requires the `crypto` and `ranch` applications to be started. ``` erlang ok = application:start(crypto). ok = application:start(ranch). ok = application:start(cowboy). ``` Cowboy uses Ranch for handling the connections and provides convenience functions to start Ranch listeners. The `cowboy:start_http/4` function starts a listener for HTTP connections using the TCP transport. The `cowboy:start_https/4` function starts a listener for HTTPS connections using the SSL transport. Listeners are a group of processes that are used to accept and manage connections. The processes used specifically for accepting connections are called acceptors. The number of acceptor processes is unrelated to the maximum number of connections Cowboy can handle. Please refer to the Ranch guide for in-depth information. Listeners are named. They spawn a given number of acceptors, listen for connections using the given transport options and pass along the protocol options to the connection processes. The protocol options must include the dispatch list for routing requests to handlers. The dispatch list is explained in greater details in the Routing section of the guide. ``` erlang Dispatch = cowboy_router:compile([ %% {URIHost, list({URIPath, Handler, Opts})} {'_', [{'_', my_handler, []}]} ]), %% Name, NbAcceptors, TransOpts, ProtoOpts cowboy:start_http(my_http_listener, 100, [{port, 8080}], [{env, [{dispatch, Dispatch}]}] ). ``` Cowboy features many kinds of handlers. It has plain HTTP handlers, loop handlers, Websocket handlers, REST handlers and static handlers. Their usage is documented in the respective sections of the guide. Most applications use the plain HTTP handler, which has three callback functions: init/3, handle/2 and terminate/3. You can find more information about the arguments and possible return values of these callbacks in the HTTP handlers section of this guide. Following is an example of a simple HTTP handler module. ``` erlang -module(my_handler). -behaviour(cowboy_http_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). init({tcp, http}, Req, Opts) -> {ok, Req, undefined_state}. handle(Req, State) -> {ok, Req2} = cowboy_req:reply(200, [], <<"Hello World!">>, Req), {ok, Req2, State}. terminate(Reason, Req, State) -> ok. ``` The `Req` variable above is the Req object, which allows the developer to obtain information about the request and to perform a reply. Its usage is explained in its respective section of the guide. erlang-cowboy-0.8.6+dfsg1/guide/loop_handlers.md000066400000000000000000000036531223335133400215530ustar00rootroot00000000000000Loop handlers ============= Purpose ------- Loop handlers are a special kind of HTTP handlers used when the response can not be sent right away. The handler enters instead a receive loop waiting for the right message before it can send a response. They are most useful when performing long-polling operations or when using server-sent events. While the same can be accomplished using plain HTTP handlers, it is recommended to use loop handlers because they are well-tested and allow using built-in features like hibernation and timeouts. Usage ----- Loop handlers are used for requests where a response might not be immediately available, but where you would like to keep the connection open for a while in case the response arrives. The most known example of such practice is known as long-polling. Loop handlers can also be used for requests where a response is partially available and you need to stream the response body while the connection is open. The most known example of such practice is known as server-sent events. Loop handlers essentially wait for one or more Erlang messages and feed these messages to the `info/3` callback. It also features the `init/3` and `terminate/3` callbacks which work the same as for plain HTTP handlers. The following handler waits for a message `{reply, Body}` before sending a response. If this message doesn't arrive within 60 seconds, it gives up and a `204 No Content` will be replied. It also hibernates the process to save memory while waiting for this message. ``` erlang -module(my_loop_handler). -behaviour(cowboy_loop_handler). -export([init/3]). -export([info/3]). -export([terminate/3]). init({tcp, http}, Req, Opts) -> {loop, Req, undefined_state, 60000, hibernate}. info({reply, Body}, Req, State) -> {ok, Req2} = cowboy_req:reply(200, [], Body, Req), {ok, Req2, State}; info(Message, Req, State) -> {loop, Req, State, hibernate}. terminate(Reason, Req, State) -> ok. ``` erlang-cowboy-0.8.6+dfsg1/guide/middlewares.md000066400000000000000000000052411223335133400212150ustar00rootroot00000000000000Middlewares =========== Purpose ------- Cowboy delegates the request processing to middleware components. By default, two middlewares are defined, for the routing and handling of the request, as is detailed in most of this guide. Middlewares give you complete control over how requests are to be processed. You can add your own middlewares to the mix or completely change the chain of middlewares as needed. Cowboy will execute all middlewares in the given order, unless one of them decides to stop processing. Usage ----- Middlewares only need to implement a single callback: `execute/2`. It is defined in the `cowboy_middleware` behavior. This callback has two arguments. The first is the `Req` object. The second is the environment. Middlewares can return one of four different values: * `{ok, Req, Env}` to continue the request processing * `{suspend, Module, Function, Args}` to hibernate * `{halt, Req}` to stop processing and move on to the next request * `{error, StatusCode, Req}` to reply an error and close the socket Of note is that when hibernating, processing will resume on the given MFA, discarding all previous stacktrace. Make sure you keep the `Req` and `Env` in the arguments of this MFA for later use. If an error happens during middleware processing, Cowboy will not try to send an error back to the socket, the process will just crash. It is up to the middleware to make sure that a reply is sent if something goes wrong. Configuration ------------- The middleware environment is defined as the `env` protocol option. In the previous chapters we saw it briefly when we needed to pass the routing information. It is a list of tuples with the first element being an atom and the second any Erlang term. Two values in the environment are reserved: * `listener` contains the name of the listener * `result` contains the result of the processing The `listener` value is always defined. The `result` value can be set by any middleware. If set to anything other than `ok`, Cowboy will not process any subsequent requests on this connection. The middlewares that come with Cowboy may define or require other environment values to perform. You can update the environment by calling the `cowboy:set_env/3` convenience function, adding or replacing a value in the environment. Routing middleware ------------------ The routing middleware requires the `dispatch` value. If routing succeeds, it will put the handler name and options in the `handler` and `handler_opts` values of the environment, respectively. Handler middleware ------------------ The handler middleware requires the `handler` and `handler_opts` values. It puts the result of the request handling into `result`. erlang-cowboy-0.8.6+dfsg1/guide/req.md000066400000000000000000000175241223335133400175130ustar00rootroot00000000000000Request object ============== Purpose ------- The request object is a special variable that can be used to interact with a request, extracting information from it or modifying it, and sending a response. It's a special variable because it contains both immutable and mutable state. This means that some operations performed on the request object will always return the same result, while others will not. For example, obtaining request headers can be repeated safely. Obtaining the request body can only be done once, as it is read directly from the socket. With few exceptions, all calls to the `cowboy_req` module will return an updated request object. You MUST use the new request object instead of the old one for all subsequent operations. Request ------- Cowboy allows you to retrieve a lot of information about the request. All these calls return a `{Value, Req}` tuple, with `Value` the requested value and `Req` the updated request object. The following access functions are defined in `cowboy_req`: * `method/1`: the request method (`<<"GET">>`, `<<"POST">>`...) * `version/1`: the HTTP version (`'HTTP/1.0'` or `'HTTP/1.1'`) * `peer/1`: the peer address and port number * `host/1`: the hostname requested * `host_info/1`: the result of the `[...]` match on the host * `port/1`: the port number used for the connection * `path/1`: the path requested * `path_info/1`: the result of the `[...]` match on the path * `qs/1`: the entire query string unmodified * `qs_val/{2,3}`: the value for the requested query string key * `qs_vals/1`: all key/values found in the query string * `host_url/1`: the requested URL without the path and query string * `url/1`: the requested URL * `binding/{2,3}`: the value for the requested binding found during routing * `bindings/1`: all key/values found during routing * `header/{2,3}`: the value for the requested header name * `headers/1`: all headers name/value * `cookie/{2,3}`: the value for the requested cookie name * `cookies/1`: all cookies name/value * `meta/{2,3}`: the meta information for the requested key All the functions above that can take two or three arguments take an optional third argument for the default value if none is found. Otherwise it will return `undefined`. In addition, Cowboy allows you to parse headers using the `parse_header/{2,3}` function, which takes a header name as lowercase binary, the request object, and an optional default value. It returns `{ok, ParsedValue, Req}` if it could be parsed, `{undefined, RawValue, Req}` if Cowboy doesn't know this header, and `{error, badarg}` if Cowboy encountered an error while trying to parse it. Finally, Cowboy allows you to set request meta information using the `set_meta/3` function, which takes a name, a value and the request object and returns the latter modified. Request body ------------ Cowboy will not read the request body until you ask it to. If you don't, then Cowboy will simply discard it. It will not take extra memory space until you start reading it. Cowboy has a few utility functions for dealing with the request body. The function `has_body/1` will return whether the request contains a body. Note that some clients may not send the right headers while still sending a body, but as Cowboy has no way of detecting it this function will return `false`. The function `body_length/1` retrieves the size of the request body. If the body is compressed, the value returned here is the compressed size. If a `Transfer-Encoding` header was passed in the request, then Cowboy will return a size of `undefined`, as it has no way of knowing it. If you know the request contains a body, and that it is within 8MB (for `body/1`) or 16KB (for `body_qs/1`) bytes, then you can read it directly with either `body/1` or `body_qs/1`. If you want to override the default size limits of `body/1` or `body_qs/1`, you can pass the maximum body length byte size as first parameter to `body/2` and `body_qs/2` or pass atom `infinity` to ignore size limits. If the request contains bigger body than allowed default sizes or supplied maximum body length, `body/1`, `body/2`, `body_qs/1` and `body_qs/2` will return `{error, badlength}`. If the request contains chunked body, `body/1`, `body/2`, `body_qs/1` and `body_qs/2` will return `{error, chunked}`. If you get either of the above two errors, you will want to handle the body of the request using `stream_body/1`, `stream_body/2` and `skip_body/1`, with the streaming process optionally initialized using `init_stream/4`. Multipart request body ---------------------- Cowboy provides facilities for dealing with multipart bodies. They are typically used for uploading files. You can use two functions to process these bodies, `multipart_data/1` and `multipart_skip/1`. Response -------- You can send a response by calling the `reply/{2,3,4}` function. It takes the status code for the response (usually `200`), an optional list of headers, an optional body and the request object. The following snippet sends a simple response with no headers specified but with a body. ``` erlang {ok, Req2} = cowboy_req:reply(200, [], "Hello world!", Req). ``` If this is the only line in your handler then make sure to return the `Req2` variable to Cowboy so it can know you replied. If you want to send HTML you'll need to specify the `Content-Type` header so the client can properly interpret it. ``` erlang {ok, Req2} = cowboy_req:reply(200, [{<<"content-type">>, <<"text/html">>}], "Hello world!

Hats off!

", Req). ``` You only need to make sure to follow conventions and to use a lowercase header name. Chunked response ---------------- You can also send chunked responses using `chunked_reply/{2,3}`. Chunked responses allow you to send the body in chunks of various sizes. It is the recommended way of performing streaming if the client supports it. You must first initiate the response by calling the aforementioned function, then you can call `chunk/2` as many times as needed. The following snippet sends a body in three chunks. ``` erlang {ok, Req2} = cowboy_req:chunked_reply(200, Req), ok = cowboy_req:chunk("Hello...", Req2), ok = cowboy_req:chunk("chunked...", Req2), ok = cowboy_req:chunk("world!!", Req2). ``` As you can see the call to `chunk/2` does not return a modified request object. It may return an error, however, so you should make sure that you match the return value on `ok`. Response preconfiguration ------------------------- Cowboy allows you to set response cookies, headers or body in advance without having to send the response at the same time. Then, when you decide to send it, all these informations will be built into the resulting response. Some of the functions available for this purpose also give you additional functionality, like `set_resp_cookie/4` which will build the appropriate `Set-Cookie` header, or `set_resp_body_fun/{2,3}` which allows you to stream the response body. Note that any value given directly to `reply/{2,3,4}` will override all preset values. This means for example that you can set a default body and then override it when you decide to send a reply. Closing the connection ---------------------- HTTP/1.1 keep-alive allows clients to send more than one request on the same connection. This can be useful for speeding up the loading of webpages, but is not required. You can tell Cowboy explicitly that you want to close the connection by setting the `Connection` header to `close`. ``` erlang {ok, Req2} = cowboy_req:reply(200, [{<<"connection">>, <<"close">>}], Req). ``` Reducing the memory footprint ----------------------------- When you are done reading information from the request object and know you are not going to access it anymore, for example when using long-polling or Websocket, you can use the `compact/1` function to remove most of the data from the request object and free memory. ``` erlang Req2 = cowboy_req:compact(Req). ``` erlang-cowboy-0.8.6+dfsg1/guide/resources.md000066400000000000000000000003371223335133400207300ustar00rootroot00000000000000Resources ========= Frameworks ---------- * Please send a pull request! Helper libraries ---------------- * [eventsource](https://github.com/jdavisp3/eventsource) Articles -------- * Please send a pull request! erlang-cowboy-0.8.6+dfsg1/guide/rest_handlers.md000066400000000000000000000145551223335133400215620ustar00rootroot00000000000000REST handlers ============= Purpose ------- REST is a set of constraints that, when applied to HTTP, dictates how resources must behave. It is the recommended way to handle requests with Cowboy. REST is implemented in Cowboy as a protocol upgrade. Once upgraded, the request is handled as a state machine with many optional callbacks describing the resource and modifying the machine's behavior. As the REST handler is still subject to change, the documentation is still thin. This state of affair will be improved in the coming weeks. Usage ----- Like Websocket, REST is a sub-protocol of HTTP. It therefore requires a protocol upgrade. ``` erlang init({tcp, http}, Req, Opts) -> {upgrade, protocol, cowboy_rest}. ``` Cowboy will then switch to the REST protocol and start executing the flow diagram, starting from `rest_init/2` if it's defined, and ending with `rest_terminate/2` also if defined. Flow diagram ------------ Not done yet. Feel free to use the one that is currently being worked on. * https://github.com/extend/cowboy/pull/364 Methods ------- The REST component has code for handling the following HTTP methods: HEAD, GET, POST, PATCH, PUT, DELETE and OPTIONS. Other methods can be accepted, however they have no specific callback defined for them at this time. Callbacks --------- All callbacks are optional. Some may become mandatory depending on what other defined callbacks return. The flow diagram should be a pretty good resource to determine which callbacks you need. When the request starts being processed, Cowboy will call the `rest_init/2` function if it is defined, with the Req object and the handler options as arguments. This function must return `{ok, Req, State}` where `State` is the handler's state that all subsequent callbacks will receive. At the end of every request, the special callback `rest_terminate/2` will be called if it is defined. It cannot be used to send a reply, and must always return `ok`. All other callbacks are resource callbacks. They all take two arguments, the Req object and the State, and return a three-element tuple of the form `{Value, Req, State}`. The following table summarizes the callbacks and their default values. If the callback isn't defined, then the default value will be used. Please look at the flow diagram to find out the result of each return value. All callbacks can also return `{halt, Req, State}` to stop execution of the request, at which point `rest_terminate/2` will be called. In the following table, "skip" means the callback is entirely skipped if it is undefined, moving directly to the next step. Similarly, an empty column means there is no default value for this callback. | Callback name | Default value | | ---------------------- | ------------------------- | | allowed_methods | `[<<"GET">>, <<"HEAD">>, <<"OPTIONS">>]` | | allow_missing_post | `true` | | charsets_provided | skip | | content_types_accepted | | | content_types_provided | `[{{<<"text">>, <<"html">>, '*'}, to_html}] ` | | delete_completed | `true` | | delete_resource | `false` | | expires | `undefined` | | forbidden | `false` | | generate_etag | `undefined` | | is_authorized | `true` | | is_conflict | `false` | | known_content_type | `true` | | known_methods | `[<<"GET">>, <<"HEAD">>, <<"POST">>, <<"PUT">>, <<"PATCH">>, <<"DELETE">>, <<"OPTIONS">>]` | | languages_provided | skip | | last_modified | `undefined` | | malformed_request | `false` | | moved_permanently | `false` | | moved_temporarily | `false` | | multiple_choices | `false` | | options | `ok` | | previously_existed | `false` | | resource_exists | `true` | | service_available | `true` | | uri_too_long | `false` | | valid_content_headers | `true` | | valid_entity_length | `true` | | variances | `[]` | As you can see, Cowboy tries to move on with the request whenever possible by using well thought out default values. In addition to these, there can be any number of user-defined callbacks that are specified through `content_types_accepted/2` and `content_types_provided/2`. They can take any name, however it is recommended to use a separate prefix for the callbacks of each function. For example, `from_html` and `to_html` indicate in the first case that we're accepting a resource given as HTML, and in the second case that we send one as HTML. Meta data --------- Cowboy will set informative meta values at various points of the execution. You can retrieve them using `cowboy_req:meta/{2,3}`. The values are defined in the following table. | Meta key | Details | | -----------| ---------------------------------------------------- | | media_type | The content-type negotiated for the response entity. | | language | The language negotiated for the response entity. | | charset | The charset negotiated for the response entity. | They can be used to reply a response entity to a request with an idempotent method (`POST`, `PUT`, `PATCH`, `DELETE`). Response headers ---------------- Cowboy will set response headers automatically over the execution of the REST code. They are listed in the following table. | Header name | Details | | ---------------- | -------------------------------------------------- | | content-language | Language used in the response body | | content-type | Media type and charset of the response body | | etag | Etag of the resource | | expires | Expiration date of the resource | | last-modified | Last modification date for the resource | | location | Relative or absolute URI to the requested resource | | vary | List of headers that may change the representation of the resource | erlang-cowboy-0.8.6+dfsg1/guide/routing.md000066400000000000000000000167351223335133400204160ustar00rootroot00000000000000Routing ======= Purpose ------- Cowboy does nothing by default. To make Cowboy useful, you need to map URLs to Erlang modules that will handle the requests. This is called routing. When Cowboy receives a request, it tries to match the requested host and path to the resources given in the dispatch rules. If it matches, then the associated Erlang code will be executed. Routing rules are given per host. Cowboy will first match on the host, and then try to find a matching path. Routes need to be compiled before they can be used by Cowboy. Structure --------- The general structure for the routes is defined as follow. ``` erlang Routes = [Host1, Host2, ... HostN]. ``` Each host contains matching rules for the host along with optional constraints, and a list of routes for the path component. ``` erlang Host1 = {HostMatch, PathsList}. Host2 = {HostMatch, Constraints, PathsList}. ``` The list of routes for the path component is defined similar to the list of hosts. ``` erlang PathsList = [Path1, Path2, ... PathN]. ``` Finally, each path contains matching rules for the path along with optional constraints, and gives us the handler module to be used along with options that will be given to it on initialization. ``` erlang Path1 = {PathMatch, Handler, Opts}. Path2 = {PathMatch, Constraints, Handler, Opts}. ``` Continue reading to learn more about the match syntax and the optional constraints. Match syntax ------------ The match syntax is used to associate host names and paths with their respective handlers. The match syntax is the same for host and path with a few subtleties. Indeed, the segments separator is different, and the host is matched starting from the last segment going to the first. All examples will feature both host and path match rules and explain the differences when encountered. Excluding special values that we will explain at the end of this section, the simplest match value is a host or a path. It can be given as either a `string()` or a `binary()`. ``` erlang PathMatch1 = "/". PathMatch2 = "/path/to/resource". HostMatch1 = "cowboy.example.org". ``` As you can see, all paths defined this way must start with a slash character. Note that these two paths are identical as far as routing is concerned. ``` erlang PathMatch2 = "/path/to/resource". PathMatch3 = "/path/to/resource/". ``` Hosts with and without a trailing dot are equivalent for routing. Similarly, hosts with and without a leading dot are also equivalent. ``` erlang HostMatch1 = "cowboy.example.org". HostMatch2 = "cowboy.example.org.". HostMatch3 = ".cowboy.example.org". ``` It is possible to extract segments of the host and path and to store the values in the `Req` object for later use. We call these kind of values bindings. The syntax for bindings is very simple. A segment that begins with the `:` character means that what follows until the end of the segment is the name of the binding in which the segment value will be stored. ``` erlang PathMatch = "/hats/:name/prices". HostMatch = ":subdomain.example.org". ``` If these two end up matching when routing, you will end up with two bindings defined, `subdomain` and `name`, each containing the segment value where they were defined. For example, the URL `http://test.example.org/hats/wild_cowboy_legendary/prices` will result in having the value `test` bound to the name `subdomain` and the value `wild_cowboy_legendary` bound to the name `name`. They can later be retrieved using `cowboy_req:binding/{2,3}`. The binding name must be given as an atom. There is a special binding name you can use to mimic the underscore variable in Erlang. Any match against the `_` binding will succeed but the data will be discarded. This is especially useful for matching against many domain names in one go. ``` erlang HostMatch = "ninenines.:_". ``` Similarly, it is possible to have optional segments. Anything between brackets is optional. ``` erlang PathMatch = "/hats/[page/:number]". HostMatch = "[www.]ninenines.eu". ``` You can also have imbricated optional segments. ``` erlang PathMatch = "/hats/[page/[:number]]". ``` You can retrieve the rest of the host or path using `[...]`. In the case of hosts it will match anything before, in the case of paths anything after the previously matched segments. It is a special case of optional segments, in that it can have zero, one or many segments. You can then find the segments using `cowboy_req:host_info/1` and `cowboy_req:path_info/1` respectively. They will be represented as a list of segments. ``` erlang PathMatch = "/hats/[...]". HostMatch = "[...]ninenines.eu". ``` If a binding appears twice in the routing rules, then the match will succeed only if they share the same value. This copies the Erlang pattern matching behavior. ``` erlang PathMatch = "/hats/:name/:name". ``` This is also true when an optional segment is present. In this case the two values must be identical only if the segment is available. ``` erlang PathMatch = "/hats/:name/[:name]". ``` If a binding is defined in both the host and path, then they must also share the same value. ``` erlang PathMatch = "/:user/[...]". HostMatch = ":user.github.com". ``` Finally, there are two special match values that can be used. The first is the atom `'_'` which will match any host or path. ``` erlang PathMatch = '_'. HostMatch = '_'. ``` The second is the special host match `"*"` which will match the wildcard path, generally used alongside the `OPTIONS` method. ``` erlang HostMatch = "*". ``` Constraints ----------- After the matching has completed, the resulting bindings can be tested against a set of constraints. Constraints are only tested when the binding is defined. They run in the order you defined them. The match will succeed only if they all succeed. They are always given as a two or three elements tuple, where the first element is the name of the binding, the second element is the constraint's name, and the optional third element is the constraint's arguments. The following constraints are currently defined: * {Name, int} * {Name, function, fun ((Value) -> true | {true, NewValue} | false)} The `int` constraint will check if the binding is a binary string representing an integer, and if it is, will convert the value to integer. The `function` constraint will pass the binding value to a user specified function that receives the binary value as its only argument and must return whether it fulfills the constraint, optionally modifying the value. The value thus returned can be of any type. Note that constraint functions SHOULD be pure and MUST NOT crash. Compilation ----------- The structure defined in this chapter needs to be compiled before it is passed to Cowboy. This allows Cowboy to efficiently lookup the correct handler to run instead of having to parse the routes repeatedly. This can be done with a simple call to `cowboy_router:compile/1`. ``` erlang Dispatch = cowboy_router:compile([ %% {HostMatch, list({PathMatch, Handler, Opts})} {'_', [{'_', my_handler, []}]} ]), %% Name, NbAcceptors, TransOpts, ProtoOpts cowboy:start_http(my_http_listener, 100, [{port, 8080}], [{env, [{dispatch, Dispatch}]}] ). ``` Note that this function will return `{error, badarg}` if the structure given is incorrect. Live update ----------- You can use the `cowboy:set_env/3` function for updating the dispatch list used by routing. This will apply to all new connections accepted by the listener. ``` erlang cowboy:set_env(my_http_listener, dispatch, cowboy_router:compile(Dispatch)). ``` Note that you need to compile the routes before updating. erlang-cowboy-0.8.6+dfsg1/guide/static_handlers.md000066400000000000000000000032551223335133400220670ustar00rootroot00000000000000Static handlers =============== Purpose ------- Static handlers are a built-in REST handler for serving files. They are available as a convenience and provide fast file serving with proper cache handling. It is recommended to use a Content Distribution Network (CDN) or at least a dedicated file server running on a dedicated cookie-less hostname for serving your application's static files in production. Usage ----- Static handlers are pre-written REST handlers. They only need to be specified in the routing information with the proper options. The following example routing serves all files found in the `priv_dir/static/` directory of the application `my_app`. ``` erlang Dispatch = [ {'_', [ {"/[...]", cowboy_static, [ {directory, {priv_dir, my_app, [<<"static">>]}}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]. ``` You can also serve a single file specifically. A common example would be an `index.html` file to be served when the path `/` is requested. The following example will serve the `priv/index.html` file from the application `my_app`. ``` erlang Dispatch = [ {'_', [ {"/", cowboy_static, [ {directory, {priv_dir, my_app, []}}, {file, "index.html"}, {mimetypes, {fun mimetypes:path_to_mimes/2, default}} ]} ]} ]. ``` MIME type --------- Cowboy does not provide any default for MIME types. This means that unless you specify the `mimetypes` option, all files will be sent as `application/octet-stream`, which the browser will not try to interpret, instead trying to make you download it. In the examples above we used the [mimetypes application](https://github.com/spawngrid/mimetypes) to find the MIME type from the file's extension. erlang-cowboy-0.8.6+dfsg1/guide/toc.md000066400000000000000000000026621223335133400175060ustar00rootroot00000000000000Cowboy User Guide ================= * [Introduction](introduction.md) * Purpose * Prerequisites * Conventions * Getting started * [Routing](routing.md) * Purpose * Structure * Match syntax * Constraints * Compilation * Live update * [Handlers](handlers.md) * Purpose * Protocol upgrades * Custom protocol upgrades * [HTTP handlers](http_handlers.md) * Purpose * Usage * [Loop handlers](loop_handlers.md) * Purpose * Usage * [Websocket handlers](ws_handlers.md) * Purpose * Usage * [REST handlers](rest_handlers.md) * Purpose * Usage * Flow diagram * Methods * Callbacks * Meta data * Response headers * [Static handlers](static_handlers.md) * Purpose * Usage * MIME type * [Request object](req.md) * Purpose * Request * Request body * Multipart request body * Response * Chunked response * Response preconfiguration * Closing the connection * Reducing the memory footprint * [Hooks](hooks.md) * On request * On response * [Middlewares](middlewares.md) * Purpose * Usage * Configuration * Routing middleware * Handler middleware * [Internals](internals.md) * Architecture * One process for many requests * Lowercase header names * Improving performance * [Resources](resources.md) * Frameworks * Helper libraries * Articles erlang-cowboy-0.8.6+dfsg1/guide/ws_handlers.md000066400000000000000000000044331223335133400212300ustar00rootroot00000000000000Websocket handlers ================== Purpose ------- Websocket is an extension to HTTP to emulate plain TCP connections between the user's browser and the server. Requests that are upgraded are then handled by websocket handlers. Both sides of the socket can send data at any time asynchronously. Websocket is an IETF standard. Cowboy supports the standard and all the drafts that were previously implemented by browsers. Websocket is implemented by most browsers today, although for backward compatibility reasons a solution like [Bullet](https://github.com/extend/bullet) might be preferred. Usage ----- Websocket handlers are a bridge between the client and your system. They can receive data from the client, through `websocket_handle/3`, or from the system, through `websocket_info/3`. It is up to the handler to decide to process this data, and optionally send a reply to the client. The first thing to do to be able to handle websockets is to tell Cowboy that it should upgrade the connection to use the Websocket protocol, as follow. ``` erlang init({tcp, http}, Req, Opts) -> {upgrade, protocol, cowboy_websocket}. ``` Cowboy will then switch the protocol and call `websocket_init`, followed by zero or more calls to `websocket_handle` and `websocket_info`. Then, when the connection is shutting down, `websocket_terminate` will be called. The following handler sends a message every second. It also echoes back what it receives. ``` erlang -module(my_ws_handler). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). init({tcp, http}, Req, Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(TransportName, Req, _Opts) -> erlang:start_timer(1000, self(), <<"Hello!">>), {ok, Req, undefined_state}. websocket_handle({text, Msg}, Req, State) -> {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; websocket_handle(_Data, Req, State) -> {ok, Req, State}. websocket_info({timeout, _Ref, Msg}, Req, State) -> erlang:start_timer(1000, self(), <<"How' you doin'?">>), {reply, {text, Msg}, Req, State}; websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. ``` erlang-cowboy-0.8.6+dfsg1/manual/000077500000000000000000000000001223335133400165515ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/manual/cowboy.md000066400000000000000000000042551223335133400204030ustar00rootroot00000000000000cowboy ====== The `cowboy` module provides convenience functions for manipulating Ranch listeners. Types ----- ### http_headers() = [{binary(), iodata()}] > HTTP headers as a list of key/values. ### http_status() = non_neg_integer() | binary() > HTTP status. > > A binary status can be used to set a custom message. ### http_version() = 'HTTP/1.1' | 'HTTP/1.0' > HTTP version. ### onrequest_fun() = fun((cowboy_req:req()) -> cowboy_req:req()) > Fun called immediately after receiving a request. > > It can perform any operation on the `Req` object, including > reading the request body or replying. If a reply is sent, > the processing of the request ends here, before any middleware > is executed. ### onresponse_fun() = fun((http_status(), http_headers(), iodata(), cowboy_req:req()) -> cowboy_req:req()) > Fun called immediately before sending the response. > > It can perform any operation on the `Req` object, including > reading the request body or replying. If a reply is sent, it > overrides the reply initially sent. The callback will not be > called again for the new reply. Exports ------- ### start_http(Ref, NbAcceptors, TransOpts, ProtoOpts) -> {ok, pid()} > Types: > * Ref = ranch:ref() > * NbAcceptors = non_neg_integer() > * TransOpts = ranch_tcp:opts() > * ProtoOpts = cowboy_protocol:opts() > > Start listening for HTTP connections. Returns the pid for this > listener's supervisor. ### start_https(Ref, NbAcceptors, TransOpts, ProtoOpts) -> {ok, pid()} > Types: > * Ref = ranch:ref() > * NbAcceptors = non_neg_integer() > * TransOpts = ranch_ssl:opts() > * ProtoOpts = cowboy_protocol:opts() > > Start listening for HTTPS connections. Returns the pid for this > listener's supervisor. ### stop_listener(Ref) -> ok > Types: > * Ref = ranch:ref() > > Stop a previously started listener. ### set_env(Ref, Name, Value) -> ok > Types: > * Ref = ranch:ref() > * Name = atom() > * Value = any() > > Set or update an environment value for an already running listener. > This will take effect on all subsequent connections. See also -------- The [Ranch guide](http://ninenines.eu/docs/en/ranch/HEAD/guide) provides detailed information about how listeners work. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_app.md000066400000000000000000000014421223335133400212360ustar00rootroot00000000000000The Cowboy Application ====================== Small, fast, modular HTTP server. Dependencies ------------ The `cowboy` application uses the Erlang applications `ranch` for listening and accepting TCP connections, and `crypto` for establishing Websocket connections. These dependencies must be loaded for the `cowboy` application to work. In an embedded environment this means that they need to be started with the `application:start/{1,2}` function before the `cowboy` application is started. The `cowboy` application also uses the Erlang applications `asn1`, `public_key` and `ssl` when listening for HTTPS connections. These are started automatically if they weren't before. Environment ----------- The `cowboy` application does not define any application environment configuration parameters. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_handler.md000066400000000000000000000007021223335133400220710ustar00rootroot00000000000000cowboy_handler ============== The `cowboy_handler` middleware executes the handler passed through the environment values `handler` and `handler_opts`, and add the result of this execution to the environment as the value `result`, indicating that the request has been handled and received a response. Environment input: * handler = module() * handler_opts = any() Environment output: * result = ok Types ----- None. Exports ------- None. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_http_handler.md000066400000000000000000000025431223335133400231350ustar00rootroot00000000000000cowboy_http_handler =================== The `cowboy_http_handler` behaviour defines the interface used by plain HTTP handlers. Unless noted otherwise, the callbacks will be executed sequentially. Types ----- None. Callbacks --------- ### init({TransportName, ProtocolName}, Req, Opts) -> {ok, Req, State} | {shutdown, Req, State} > Types: > * TransportName = tcp | ssl | atom() > * ProtocolName = http | atom() > * Req = cowboy_req:req() > * Opts = any() > * State = any() > > Initialize the state for this request. > > The `shutdown` return value can be used to skip the `handle/2` > call entirely. ### handle(Req, State) -> {ok, Req, State} > Types: > * Req = cowboy_req:req() > * State = any() > > Handle the request. > > This callback is where the request is handled and a response > should be sent. If a response is not sent, Cowboy will send > a `204 No Content` response automatically. ### terminate(Reason, Req, State) -> ok > Types: > * Reason = {normal, shutdown} | {error, atom()} > * Req = cowboy_req:req() > * State = any() > > Perform any necessary cleanup of the state. > > This callback should release any resource currently in use, > clear any active timer and reset the process to its original > state, as it might be reused for future requests sent on the > same connection. Typical plain HTTP handlers rarely need to > use it. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_loop_handler.md000066400000000000000000000052721223335133400231310ustar00rootroot00000000000000cowboy_loop_handler =================== The `cowboy_loop_handler` behaviour defines the interface used by HTTP handlers that do not send a response directly, instead requiring a receive loop to process Erlang messages. This interface is best fit for long-polling types of requests. The `init/3` callback will always be called, followed by zero or more calls to `info/3`. The `terminate/3` will always be called last. Types ----- None. Callbacks --------- ### init({TransportName, ProtocolName}, Req, Opts) -> {loop, Req, State} | {loop, Req, State, hibernate} | {loop, Req, State, Timeout} | {loop, Req, State, Timeout, hibernate} | {shutdown, Req, State} > Types: > * TransportName = tcp | ssl | atom() > * ProtocolName = http | atom() > * Req = cowboy_req:req() > * Opts = any() > * State = any() > * Timeout = timeout() > > Initialize the state for this request. > > This callback will typically be used to register this process > to an event manager or a message queue in order to receive > the messages the handler wants to process. > > The receive loop will run for a duration of up to `Timeout` > milliseconds after it last received data from the socket, > at which point it will stop and send a `204 No Content` reply. > By default this value is set to `infinity`. It is recommended > to either set this value or ensure by any other mechanism > that the handler will be closed after a certain period of > inactivity. > > The `hibernate` option will hibernate the process until it > starts receiving messages. > > The `shutdown` return value can be used to skip the receive > loop entirely. ### info(Info, Req, State) -> {ok, Req, State} | {loop, Req, State} | {loop, Req, State, hibernate} > Types: > * Info = any() > * Req = cowboy_req:req() > * State = any() > > Handle the Erlang message received. > > This function will be called every time an Erlang message > has been received. The message can be any Erlang term. > > The `ok` return value can be used to stop the receive loop, > typically because a response has been sent. > > The `hibernate` option will hibernate the process until > it receives another message. ### terminate(Reason, Req, State) -> ok > Types: > * Reason = {normal, shutdown} | {normal, timeout} | {error, closed} | {error, overflow} | {error, atom()} > * Req = cowboy_req:req() > * State = any() > > Perform any necessary cleanup of the state. > > This callback will typically unregister from any event manager > or message queue it registered to in `init/3`. > > This callback should release any resource currently in use, > clear any active timer and reset the process to its original > state, as it might be reused for future requests sent on the > same connection. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_middleware.md000066400000000000000000000030111223335133400225650ustar00rootroot00000000000000cowboy_middleware ================= The `cowboy_middleware` behaviour defines the interface used by Cowboy middleware modules. Middlewares process the request sequentially in the order they are configured. Types ----- ### env() = [{atom(), any()}] > The environment variable. > > One is created for every request. It is passed to each > middleware module executed and subsequently returned, > optionally with its contents modified. Callbacks --------- ### execute(Req, Env) -> {ok, Req, Env} | {suspend, Module, Function, Args} | {halt, Req} | {error, StatusCode, Req} > Types: > * Req = cowboy_req:req() > * Env = env() > * Module = module() > * Function = atom() > * Args = [any()] > * StatusCode = cowboy:http_status() > > Execute the middleware. > > The `ok` return value indicates that everything went well > and that Cowboy should continue processing the request. A > response may or may not have been sent. > > The `suspend` return value will hibernate the process until > an Erlang message is received. Note that when resuming, any > previous stacktrace information will be gone. > > The `halt` return value stops Cowboy from doing any further > processing of the request, even if there are middlewares > that haven't been executed yet. The connection may be left > open to receive more requests from the client. > > The `error` return value sends an error response identified > by the `StatusCode` and then proceeds to terminate the > connection. Middlewares that haven't been executed yet > will not be called. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_protocol.md000066400000000000000000000037231223335133400223230ustar00rootroot00000000000000cowboy_protocol =============== The `cowboy_protocol` module implements HTTP/1.1 and HTTP/1.0 as a Ranch protocol. Types ----- ### opts() = [{compress, boolean()} | {env, cowboy_middleware:env()} | {max_empty_lines, non_neg_integer()} | {max_header_name_length, non_neg_integer()} | {max_header_value_length, non_neg_integer()} | {max_headers, non_neg_integer()} | {max_keepalive, non_neg_integer()} | {max_request_line_length, non_neg_integer()} | {middlewares, [module()]} | {onrequest, cowboy:onrequest_fun()} | {onresponse, cowboy:onresponse_fun()} | {timeout, timeout()}] > Configuration for the HTTP protocol handler. > > This configuration is passed to Cowboy when starting listeners > using `cowboy:start_http/4` or `cowboy:start_https/4` functions. > > It can be updated without restarting listeners using the > Ranch functions `ranch:get_protocol_options/1` and > `ranch:set_protocol_options/2`. Option descriptions ------------------- The default value is given next to the option name. - compress (false) - When enabled, Cowboy will attempt to compress the response body. - env ([{listener, Ref}]) - Initial middleware environment. - max_empty_lines (5) - Maximum number of empty lines before a request. - max_header_name_length (64) - Maximum length of header names. - max_header_value_length (4096) - Maximum length of header values. - max_headers (100) - Maximum number of headers allowed per request. - max_keepalive (100) - Maximum number of requests allowed per connection. - max_request_line_length (4096) - Maximum length of the request line. - middlewares ([cowboy_router, cowboy_handler]) - List of middlewares to execute for every requests. - onrequest (undefined) - Fun called every time a request is received. - onresponse (undefined) - Fun called every time a response is sent. - timeout (5000) - Time in ms with no requests before Cowboy closes the connection. Exports ------- None. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_req.md000066400000000000000000000424701223335133400212530ustar00rootroot00000000000000cowboy_req ========== The `cowboy_req` module provides functions to access, manipulate and respond to requests. The functions in this module follow patterns for their return types, based on the kind of function. * access: `{Value, Req}` * action: `{Result, Req} | {Result, Value, Req} | {error, atom()}` * modification: `Req` * question: `boolean()` The only exception is the `chunk/2` function which may return `ok`. Whenever `Req` is returned, you must use this returned value and ignore any previous you may have had. This value contains various state informations which are necessary for Cowboy to do some lazy evaluation or cache results where appropriate. Types ----- ### cookie_opts() = [{max_age, non_neg_integer()} | {domain, binary()} | {path, binary()} | {secure, boolean()} | {http_only, boolean()}] > Cookie options. ### req() - opaque to the user > The `Req` object. > > All functions in this module receive a `Req` as argument, > and most of them return a new object labelled `Req2` in > the function descriptions below. Request related exports ----------------------- ### binding(Name, Req) -> binding(Name, Req, undefined) ### binding(Name, Req, Default) -> {Value, Req2} > Types: > * Name = atom() > * Default = any() > * Value = binary() | Default > > Return the value for the given binding. ### bindings(Req) -> {[{Name, Value}], Req2} > Types: > * Name = atom() > * Value = binary() > > Return all bindings. ### cookie(Name, Req) -> cookie(Name, Req, undefined) ### cookie(Name, Req, Default) -> {Value, Req2} > Types: > * Name = binary() > * Default = any() > * Value = binary() | Default > > Return the value for the given cookie. > > Cookie names are case sensitive. ### cookies(Req) -> {[{Name, Value}], Req2} > Types: > * Name = binary() > * Value = binary() > > Return all cookies. ### header(Name, Req) -> header(Name, Req, undefined) ### header(Name, Req, Default) -> {Value, Req2} > Types: > * Name = binary() > * Default = any() > * Value = binary() | Default > > Return the value for the given header. > > While header names are case insensitive, this function expects > the name to be a lowercase binary. ### headers(Req) -> {Headers, Req2} > Types: > * Headers = cowboy:http_headers() > > Return all headers. ### host(Req) -> {Host, Req2} > Types: > * Host = binary() > > Return the requested host. ### host_info(Req) -> {HostInfo, Req2} > Types: > * HostInfo = cowboy_router:tokens() | undefined > > Return the extra tokens from matching against `...` during routing. ### host_url(Req) -> {HostURL, Req2} > Types: > * HostURL = binary() | undefined > > Return the requested URL excluding the path component. > > This function will always return `undefined` until the > `cowboy_router` middleware has been executed. This includes > the `onrequest` hook. ### meta(Name, Req) -> meta(Name, Req, undefined) ### meta(Name, Req, Default) -> {Value, Req2} > Types: > * Name = atom() > * Default = any() > * Value = any() > > Return metadata about the request. ### method(Req) -> {Method, Req2} > Types: > * Method = binary() > > Return the method. > > Methods are case sensitive. Standard methods are always uppercase. ### parse_header(Name, Req) -> ### parse_header(Name, Req, Default) -> {ok, ParsedValue, Req2} | {undefined, Value, Req2} | {error, badarg} > Types: > * Name = binary() > * Default = any() > * ParsedValue - see below > * Value = any() > > Parse the given header. > > While header names are case insensitive, this function expects > the name to be a lowercase binary. > > The `parse_header/2` function will call `parser_header/3` with a > different default value depending on the header being parsed. The > following table summarizes the default values used. > > | Header name | Default value | > | ----------------- | ------------------ | > | transfer-encoding | `[<<"identity">>]` | > | Any other header | `undefined` | > > The parsed value differs depending on the header being parsed. The > following table summarizes the different types returned. > > | Header name | Type | > | ---------------------- | ------------------------------------------------- | > | accept | `[{{Type, SubType, Params}, Quality, AcceptExt}]` | > | accept-charset | `[{Charset, Quality}]` | > | accept-encoding | `[{Encoding, Quality}]` | > | accept-language | `[{LanguageTag, Quality}]` | > | authorization | `{AuthType, Credentials}` | > | content-length | `non_neg_integer()` | > | content-type | `{Type, SubType, ContentTypeParams}` | > | cookie | `[{binary(), binary()}]` | > | expect | `[Expect | {Expect, ExpectValue, Params}]` | > | if-match | `'*' | [{weak | strong, OpaqueTag}]` | > | if-modified-since | `calendar:datetime()` | > | if-none-match | `'*' | [{weak | strong, OpaqueTag}]` | > | if-unmodified-since | `calendar:datetime()` | > | range | `{Unit, [Range]}` | > | sec-websocket-protocol | `[binary()]` | > | transfer-encoding | `[binary()]` | > | upgrade | `[binary()]` | > | x-forwarded-for | `[binary()]` | > > Types for the above table: > * Type = SubType = Charset = Encoding = LanguageTag = binary() > * AuthType = Expect = OpaqueTag = Unit = binary() > * Params = ContentTypeParams = [{binary(), binary()}] > * Quality = 0..1000 > * AcceptExt = [{binary(), binary()} | binary()] > * Credentials - see below > * Range = {non_neg_integer(), non_neg_integer() | infinity} | neg_integer() > > The cookie names and values, the values of the sec-websocket-protocol > and x-forwarded-for headers, the values in `AcceptExt` and `Params`, > the authorization `Credentials`, the `ExpectValue` and `OpaqueTag` > are case sensitive. All values in `ContentTypeParams` are case sensitive > except the value of the charset parameter, which is case insensitive. > All other values are case insensitive and will be returned as lowercase. > > The headers accept, accept-encoding and cookie headers can return > an empty list. Others will return `{error, badarg}` if the header > value is empty. > > The authorization header parsing code currently only supports basic > HTTP authentication. The `Credentials` type is thus `{Username, Password}` > with `Username` and `Password` being `binary()`. > > The range header value `Range` can take three forms: > * `{From, To}`: from `From` to `To` units > * `{From, infinity}`: everything after `From` units > * `-Final`: the final `Final` units > > An `undefined` tuple will be returned if Cowboy doesn't know how > to parse the requested header. ### path(Req) -> {Path, Req2} > Types: > * Path = binary() > > Return the requested path. ### path_info(Req) -> {PathInfo, Req2} > Types: > * PathInfo = cowboy_router:tokens() | undefined > > Return the extra tokens from matching against `...` during routing. ### peer(Req) -> {Peer, Req2} > Types: > * Peer = {inet:ip_address(), inet:port_number()} > > Return the client's IP address and port number. ### port(Req) -> {Port, Req2} > Types: > * Port = inet:port_number() > > Return the request's port. > > The port returned by this function is obtained by parsing > the host header. It may be different than the actual port > the client used to connect to the Cowboy server. ### qs(Req) -> {QueryString, Req2} > Types: > * QueryString = binary() > > Return the request's query string. ### qs_val(Name, Req) -> qs_val(Name, Req, undefined) ### qs_val(Name, Req, Default) -> {Value, Req2} > Types: > * Name = binary() > * Default = any() > * Value = binary() | true > > Return a value from the request's query string. > > The value `true` will be returned when the name was found > in the query string without an associated value. ### qs_vals(Req) -> {[{Name, Value}], Req2} > Types: > * Name = binary() > * Value = binary() | true > > Return the request's query string as a list of tuples. > > The value `true` will be returned when the name was found > in the query string without an associated value. ### set_meta(Name, Value, Req) -> Req2 > Types: > * Name = atom() > * Value = any() > > Set metadata about the request. > > An existing value will be overwritten. ### url(Req) -> {URL, Req2} > Types: > * URL = binary() | undefined > > Return the requested URL. > > This function will always return `undefined` until the > `cowboy_router` middleware has been executed. This includes > the `onrequest` hook. ### version(Req) -> {Version, Req2} > Types: > * Version = cowboy:http_version() > > Return the HTTP version used for this request. Request body related exports ---------------------------- ### body(Req) -> body(8000000, Req) ### body(MaxLength, Req) -> {ok, Data, Req2} | {error, Reason} > Types: > * MaxLength = non_neg_integer() | infinity > * Data = binary() > * Reason = chunked | badlength | atom() > > Return the request body. > > This function will return `{error, chunked}` if the request > body was sent using the chunked transfer-encoding. It will > also return `{error, badlength}` if the length of the body > exceeds the given `MaxLength`, which is 8MB by default. ### body_length(Req) -> {Length, Req2} > Types: > * Length = non_neg_integer() | undefined > > Return the length of the request body. > > The length will only be returned if the request does not > use any transfer-encoding and if the content-length header > is present. ### body_qs(Req) -> body_qs(16000, Req) ### body_qs(MaxLength, Req) -> {ok, [{Name, Value}], Req2} | {error, Reason} > Types: > * MaxLength = non_neg_integer() | infinity > * Name = binary() > * Value = binary() | true > * Reason = chunked | badlength | atom() > > Return the request body as a list of tuples. > > This function will parse the body assuming the content-type > application/x-www-form-urlencoded, commonly used for the > query string. > > This function will return `{error, chunked}` if the request > body was sent using the chunked transfer-encoding. It will > also return `{error, badlength}` if the length of the body > exceeds the given `MaxLength`, which is 16KB by default. ### has_body(Req) -> boolean() > Return whether the request has a body. ### init_stream(TransferDecode, TransferState, ContentDecode, Req) -> {ok, Req2} > Types: > * TransferDecode = fun((Encoded, TransferState) -> OK | More | Done | {error, Reason}) > * Encoded = Decoded = Rest = binary() > * TransferState = any() > * OK = {ok, Decoded, Rest, TransferState} > * More = more | {more, Length, Decoded, TransferState} > * Done = {done, TotalLength, Rest} | {done, Decoded, TotalLength, Rest} > * Length = TotalLength = non_neg_integer() > * ContentDecode = fun((Encoded) -> {ok, Decoded} | {error, Reason}) > * Reason = atom() > > Initialize streaming of the request body. > > This function can be used to specify what function to use > for decoding the request body, generally specified in the > transfer-encoding and content-encoding request headers. > > Cowboy will properly handle chunked transfer-encoding by > default. You do not need to call this function if you do > not need to decode other encodings, `stream_body/{1,2}` > will perform all the required initialization when it is > called the first time. ### skip_body(Req) -> {ok, Req2} | {error, Reason} > Types: > * Reason = atom() > > Skip the request body. > > This function will skip the body even if it was partially > read before. ### stream_body(Req) -> stream_body(1000000, Req) ### stream_body(MaxSegmentSize, Req) -> {ok, Data, Req2} | {done, Req2} | {error, Reason} > Types: > * MaxSegmentSize = non_neg_integer() > * Data = binary() > * Reason = atom() > > Stream the request body. > > This function will return a segment of the request body > with a size of up to `MaxSegmentSize`, or 1MB by default. > This function can be called repeatedly until a `done` tuple > is returned, indicating the body has been fully received. > > Cowboy will properly handle chunked transfer-encoding by > default. If any other transfer-encoding or content-encoding > has been used for the request, custom decoding functions > can be used. They must be specified using `init_stream/4`. > > After the body has been streamed fully, Cowboy will remove > the transfer-encoding header from the `Req` object, and add > the content-length header if it wasn't already there. Response related exports ------------------------ ### chunk(Data, Req) -> ok | {error, Reason} > Types: > * Data = iodata() > * Reason = atom() > > Send a chunk of data. > > This function should be called as many times as needed > to send data chunks after calling `chunked_reply/{2,3}`. > > When the method is HEAD, no data will actually be sent. > > If the request uses HTTP/1.0, the data is sent directly > without wrapping it in an HTTP/1.1 chunk, providing > compatibility with older clients. ### chunked_reply(StatusCode, Req) -> chunked_reply(StatusCode, [], Req) ### chunked_reply(StatusCode, Headers, Req) -> {ok, Req2} > Types: > * StatusCode = cowboy:http_status() > * Headers = cowboy:http_headers() > > Send a response using chunked transfer-encoding. > > This function effectively sends the response status line > and headers to the client. > > This function will not send any body set previously. After > this call the handler must use the `chunk/2` function > repeatedly to send the body in as many chunks as needed. > > If the request uses HTTP/1.0, the data is sent directly > without wrapping it in an HTTP/1.1 chunk, providing > compatibility with older clients. ### delete_resp_header(Name, Req) -> Req2 > Types: > * Name = binary() > > Delete the given response header. > > While header names are case insensitive, this function expects > the name to be a lowercase binary. ### has_resp_body(Req) -> boolean() > Return whether a response body has been set. > > This function will return false if a response body has > been set with a length of 0. ### has_resp_header(Name, Req) -> boolean() > Types: > * Name = binary() > > Return whether the given response header has been set. > > While header names are case insensitive, this function expects > the name to be a lowercase binary. ### reply(StatusCode, Req) -> reply(StatusCode, [], Req) ### reply(StatusCode, Headers, Req) - see below ### reply(StatusCode, Headers, Body, Req) -> {ok, Req2} > Types: > * StatusCode = cowboy:http_status() > * Headers = cowboy:http_headers() > * Body = iodata() > > Send a response. > > This function effectively sends the response status line, > headers and body to the client, in a single send function > call. > > The `reply/2` and `reply/3` functions will send the body > set previously, if any. The `reply/4` function overrides > any body set previously and sends `Body` instead. > > If a body function was set, and `reply/2` or `reply/3` was > used, it will be called before returning. > > No more data can be sent to the client after this function > returns. ### set_resp_body(Body, Req) -> Req2 > Types: > * Body = iodata() > > Set a response body. > > This body will not be sent if `chunked_reply/{2,3}` or > `reply/4` is used, as they override it. ### set_resp_body_fun(Fun, Req) -> Req2 ### set_resp_body_fun(Length, Fun, Req) -> Req2 > Types: > * Fun = fun((Socket, Transport) -> ok) > * Socket = inet:socket() > * Transport = module() > * Length = non_neg_integer() > > Set a fun for sending the response body. > > If a `Length` is provided, it will be sent in the > content-length header in the response. It is recommended > to set the length if it can be known in advance. > > This function will only be called if the response is sent > using the `reply/2` or `reply/3` function. > > The fun will receive the Ranch `Socket` and `Transport` as > arguments. Only send and sendfile operations are supported. ### set_resp_body_fun(chunked, Fun, Req) -> Req2 > Types: > * Fun = fun((ChunkFun) -> ok) > * ChunkFun = fun((iodata()) -> ok | {error, atom()}) > > Set a fun for sending the response body using chunked transfer-encoding. > > This function will only be called if the response is sent > using the `reply/2` or `reply/3` function. > > The fun will receive another fun as argument. This fun is to > be used to send chunks in a similar way to the `chunk/2` function, > except the fun only takes one argument, the data to be sent in > the chunk. ### set_resp_cookie(Name, Value, Opts, Req) -> Req2 > Types: > * Name = iodata() > * Value = iodata() > * Opts = cookie_opts() > > Set a cookie in the response. > > Cookie names are case sensitive. ### set_resp_header(Name, Value, Req) -> Req2 > Types: > * Name = binary() > * Value = iodata() > > Set a response header. > > You should use `set_resp_cookie/4` instead of this function > to set cookies. Misc. exports ------------- ### compact(Req) -> Req2 > Remove any non-essential data from the `Req` object. > > Long-lived connections usually only need to manipulate the > `Req` object at initialization. Compacting allows saving up > memory by discarding extraneous information. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_rest.md000066400000000000000000000374231223335133400214430ustar00rootroot00000000000000cowboy_rest =========== The `cowboy_rest` module implements REST semantics on top of the HTTP protocol. This module cannot be described as a behaviour due to most of the callbacks it defines being optional. It has the same semantics as a behaviour otherwise. The only mandatory callback is `init/3`, needed to perform the protocol upgrade. Types ----- None. Meta values ----------- ### charset > Type: binary() > > Negotiated charset. > > This value may not be defined if no charset was negotiated. ### language > Type: binary() > > Negotiated language. > > This value may not be defined if no language was negotiated. ### media_type > Type: {binary(), binary(), '*' | [{binary(), binary()}]} > > Negotiated media-type. > > The media-type is the content-type, excluding the charset. > > This value is always defined after the call to > `content_types_provided/2`. Callbacks --------- ### init({TransportName, ProtocolName}, Req, Opts) -> {upgrade, protocol, cowboy_rest} | {upgrade, protocol, cowboy_rest, Req, Opts} > Types: > * TransportName = tcp | ssl | atom() > * ProtocolName = http | atom() > * Req = cowboy_req:req() > * Opts = any() > > Upgrade the protocol to `cowboy_rest`. > > This is the only mandatory callback. ### rest_init(Req, Opts) -> {ok, Req, State} > Types: > * Req = cowboy_req:req() > * Opts = any() > * State = any() > > Initialize the state for this request. ### rest_terminate(Req, State) -> ok > Types: > * Req = cowboy_req:req() > * State = any() > > Perform any necessary cleanup of the state. > > This callback should release any resource currently in use, > clear any active timer and reset the process to its original > state, as it might be reused for future requests sent on the > same connection. ### Callback(Req, State) -> {Value, Req, State} | {halt, Req, State} > Types: > * Callback - one of the REST callbacks described below > * Req = cowboy_req:req() > * State = any() > * Value - see the REST callbacks description below > > Please see the REST callbacks description below for details > on the `Value` type, the default value if the callback is > not defined, and more general information on when the > callback is called and what its intended use is. > > The `halt` tuple can be returned to stop REST processing. > It is up to the resource code to send a reply before that, > otherwise a `204 No Content` will be sent. REST callbacks description -------------------------- ### allowed_methods > * Methods: all > * Value type: [binary()] > * Default value: [<<"GET">>, <<"HEAD">>, <<"OPTIONS">>] > > Return the list of allowed methods. > > Methods are case sensitive. Standard methods are always uppercase. ### allow_missing_post > * Methods: POST > * Value type: boolean() > * Default value: true > > Return whether POST is allowed when the resource doesn't exist. > > Returning `true` here means that a new resource will be > created. The URL to the created resource should also be > returned from the `AcceptResource` callback. ### charsets_provided > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: [binary()] > * Skip to the next step if undefined > > Return the list of charsets the resource provides. > > The list must be ordered in order of preference. > > If the accept-charset header was not sent, the first charset > in the list will be selected. Otherwise Cowboy will select > the most appropriate charset from the list. > > The chosen charset will be set in the `Req` object as the meta > value `charset`. > > While charsets are case insensitive, this callback is expected > to return them as lowercase binary. ### content_types_accepted > * Methods: POST, PUT, PATCH > * No default > > Types: > * Value = [{binary() | {Type, SubType, Params}, AcceptResource}] > * Type = SubType = binary() > * Params = '*' | [{binary(), binary()}] > * AcceptResource = atom() > > Return the list of content-types the resource accepts. > > The list must be ordered in order of preference. > > Each content-type can be given either as a binary string or as > a tuple containing the type, subtype and parameters. > > Cowboy will select the most appropriate content-type from the list. > If any parameter is acceptable, then the tuple form should be used > with parameters set to `'*'`. If the parameters value is set to `[]` > only content-type values with no parameters will be accepted. All > parameter values are treated in a case sensitive manner except the > `charset` parameter, if present, which is case insensitive. > > This function will be called for POST, PUT and PATCH requests. > It is entirely possible to define different callbacks for different > methods if the handling of the request differs. Simply verify > what the method is with `cowboy_req:method/1` and return a > different list for each methods. > > The `AcceptResource` value is the name of the callback that will > be called if the content-type matches. It is defined as follow. > > * Value type: true | {true, URL} | false > * No default > > Process the request body. > > This function should create or update the resource with the > information contained in the request body. This information > may be full or partial depending on the request method. > > If the request body was processed successfully, `true` or > `{true, URL}` may be returned. If an URL is provided, the > response will redirect the client to the location of the > resource. > > If a response body must be sent, the appropriate media-type, charset > and language can be retrieved using the `cowboy_req:meta/{2,3}` > functions. The respective keys are `media_type`, `charset` > and `language`. The body can be set using `cowboy_req:set_resp_body/2`. ### content_types_provided > * Methods: GET, HEAD > * Default value: [{{<<"text">>, <<"html">>, '*'}, to_html}] > > Types: > * Value = [{binary() | {Type, SubType, Params}, ProvideResource}] > * Type = SubType = binary() > * Params = '*' | [{binary(), binary()}] > * ProvideResource = atom() > > Return the list of content-types the resource provides. > > The list must be ordered in order of preference. > > Each content-type can be given either as a binary string or as > a tuple containing the type, subtype and parameters. > > Cowboy will select the most appropriate content-type from the list. > If any parameter is acceptable, then the tuple form should be used > with parameters set to `'*'`. If the parameters value is set to `[]` > only content-type values with no parameters will be accepted. All > parameter values are treated in a case sensitive manner except the > `charset` parameter, if present, which is case insensitive. > > The `ProvideResource` value is the name of the callback that will > be called if the content-type matches. It is defined as follow. > > * Value type: iodata() | {stream, Fun} | {stream, Len, Fun} | {chunked, ChunkedFun} > * No default > > Return the response body. > > The response body may be provided directly or through a fun. > If a fun tuple is returned, the appropriate `set_resp_body_fun` > function will be called. Please refer to the documentation for > these functions for more information about the types. > > The call to this callback happens a good time after the call to > `content_types_provided/2`, when it is time to start rendering > the response body. ### delete_completed > * Methods: DELETE > * Value type: boolean() > * Default value: true > > Return whether the delete action has been completed. > > This function should return `false` if there is no guarantee > that the resource gets deleted immediately from the system, > including from any internal cache. > > When this function returns `false`, a `202 Accepted` > response will be sent instead of a `200 OK` or `204 No Content`. ### delete_resource > * Methods: DELETE > * Value type: boolean() > * Default value: false > > Delete the resource. > > The value returned indicates if the action was successful, > regardless of whether the resource is immediately deleted > from the system. ### expires > * Methods: GET, HEAD > * Value type: calendar:datetime() | undefined > * Default value: undefined > > Return the date of expiration of the resource. > > This date will be sent as the value of the expires header. ### forbidden > * Methods: all > * Value type: boolean() > * Default value: false > > Return whether access to the resource is forbidden. > > A `403 Forbidden` response will be sent if this > function returns `true`. This status code means that > access is forbidden regardless of authentication, > and that the request shouldn't be repeated. ### generate_etag > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: binary() | {weak | strong, binary()} > * Default value: undefined > > Return the entity tag of the resource. > > This value will be sent as the value of the etag header. > > If a binary is returned, then the value will be parsed > to the tuple form automatically. The value must be in > the same format as the etag header, including quotes. ### is_authorized > * Methods: all > * Value type: true | {false, AuthHeader} > * Default value: true > > Return whether the user is authorized to perform the action. > > This function should be used to perform any necessary > authentication of the user before attempting to perform > any action on the resource. > > If the authentication fails, the value returned will be sent > as the value for the www-authenticate header in the > `401 Unauthorized` response. ### is_conflict > * Methods: PUT > * Value type: boolean() > * Default value: false > > Return whether the put action results in a conflict. > > A `409 Conflict` response will be sent if this function > returns `true`. ### known_content_type > * Methods: all > * Value type: boolean() > * Default value: true > > Return whether the content-type is known. > > This function determines if the server understands the > content-type, regardless of its use by the resource. ### known_methods > * Methods: all > * Value type: [binary()] > * Default value: [<<"GET">>, <<"HEAD">>, <<"POST">>, <<"PUT">>, <<"PATCH">>, <<"DELETE">>, <<"OPTIONS">>] > > Return the list of known methods. > > The full list of methods known by the server should be > returned, regardless of their use in the resource. > > The default value lists the methods Cowboy knows and > implement in `cowboy_rest`. > > Methods are case sensitive. Standard methods are always uppercase. ### languages_provided > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: [binary()] > * Skip to the next step if undefined > > Return the list of languages the resource provides. > > The list must be ordered in order of preference. > > If the accept-language header was not sent, the first language > in the list will be selected. Otherwise Cowboy will select > the most appropriate language from the list. > > The chosen language will be set in the `Req` object as the meta > value `language`. > > While languages are case insensitive, this callback is expected > to return them as lowercase binary. ### last_modified > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: calendar:datetime() > * Default value: undefined > > Return the date of last modification of the resource. > > This date will be used to test against the if-modified-since > and if-unmodified-since headers, and sent as the last-modified > header in the response of GET and HEAD requests. ### malformed_request > * Methods: all > * Value type: boolean() > * Default value: false > > Return whether the request is malformed. > > Cowboy has already performed all the necessary checks > by the time this function is called, so few resources > are expected to implement it. > > The check is to be done on the request itself, not on > the request body, which is processed later. ### moved_permanently > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: {true, URL} | false > * Default value: false > > Return whether the resource was permanently moved. > > If it was, its new URL is also returned and sent in the > location header in the response. ### moved_temporarily > * Methods: GET, HEAD, POST, PATCH, DELETE > * Value type: {true, URL} | false > * Default value: false > > Return whether the resource was temporarily moved. > > If it was, its new URL is also returned and sent in the > location header in the response. ### multiple_choices > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: boolean() > * Default value: false > > Return whether there are multiple representations of the resource. > > This function should be used to inform the client if there > are different representations of the resource, for example > different content-type. If this function returns `true`, > the response body should include information about these > different representations using `cowboy_req:set_resp_body/2`. > The content-type of the response should be the one previously > negociated and that can be obtained by calling > `cowboy_req:meta(media_type, Req)`. ### options > * Methods: OPTIONS > * Value type: ok > * Default value: ok > > Handle a request for information. > > The response should inform the client the communication > options available for this resource. > > By default, Cowboy will send a `200 OK` response with the > allow header set. ### previously_existed > * Methods: GET, HEAD, POST, PATCH, DELETE > * Value type: boolean() > * Default value: false > > Return whether the resource existed previously. ### resource_exists > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: boolean() > * Default value: true > > Return whether the resource exists. > > If it exists, conditional headers will be tested before > attempting to perform the action. Otherwise, Cowboy will > check if the resource previously existed first. ### service_available > * Methods: all > * Value type: boolean() > * Default value: true > > Return whether the service is available. > > This function can be used to test that all relevant backend > systems are up and able to handle requests. > > A `503 Service Unavailable` response will be sent if this > function returns `false`. ### uri_too_long > * Methods: all > * Value type: boolean() > * Default value: false > > Return whether the requested URI is too long. > > Cowboy has already performed all the necessary checks > by the time this function is called, so few resources > are expected to implement it. > > A `414 Request-URI Too Long` response will be sent if this > function returns `true`. ### valid_content_headers > * Methods: all > * Value type: boolean() > * Default value: true > > Return whether the content-* headers are valid. > > This also applies to the transfer-encoding header. This > function must return `false` for any unknown content-* > headers, or if the headers can't be understood. The > function `cowboy_req:parse_header/2` can be used to > quickly check the headers can be parsed. > > A `501 Not Implemented` response will be sent if this > function returns `false`. ### valid_entity_length > * Methods: all > * Value type: boolean() > * Default value: true > > Return whether the request body length is within acceptable boundaries. > > A `413 Request Entity Too Large` response will be sent if this > function returns `false`. ### variances > * Methods: GET, HEAD, POST, PUT, PATCH, DELETE > * Value type: [binary()] > * Default value: [] > > Return the list of headers that affect the representation of the resource. > > These request headers return the same resource but with different > parameters, like another language or a different content-type. > > Cowboy will automatically add the accept, accept-language and > accept-charset headers to the list if the respective functions > were defined in the resource. > > This operation is performed right before the `resource_exists/2` > callback. All responses past that point will contain the vary > header which holds this list. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_router.md000066400000000000000000000033461223335133400220030ustar00rootroot00000000000000cowboy_router ============= The `cowboy_router` middleware maps the requested host and path to the handler to be used for processing the request. It uses the dispatch rules compiled from the routes given to the `compile/1` function for this purpose. It adds the handler name and options to the environment as the values `handler` and `handler_opts` respectively. Environment input: * dispatch = dispatch_rules() Environment output: * handler = module() * handler_opts = any() Types ----- ### bindings() = [{atom(), binary()}] > List of bindings found during routing. ### constraints() = [IntConstraint | FunConstraint] > Types: > * IntConstraint = {atom(), int} > * FunConstraint = {atom(), function, Fun} > * Fun = fun((binary()) -> true | {true, any()} | false) > > List of constraints to apply to the bindings. > > The int constraint will convert the binding to an integer. > The fun constraint allows writing custom code for checking > the bindings. Returning a new value from that fun allows > replacing the current binding with a new value. ### dispatch_rules() - opaque to the user > Rules for dispatching request used by Cowboy. ### routes() = [{Host, Paths} | {Host, constraints(), Paths}] > Types: > * Host = Path = '_' | iodata() > * Paths = [{Path, Handler, Opts} | {Path, constraints(), Handler, Opts}] > * Handler = module() > * Opts = any() > > Human readable list of routes mapping hosts and paths to handlers. > > The syntax for routes is defined in the user guide. ### tokens() = [binary()] > List of host_info and path_info tokens found during routing. Exports ------- ### compile(Routes) -> Dispatch > Types: > * Routes = routes() > * Dispatch = dispatch_rules() > > Compile the routes for use by Cowboy. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_sub_protocol.md000066400000000000000000000012301223335133400231630ustar00rootroot00000000000000cowboy_sub_protocol =================== The `cowboy_sub_protocol` behaviour defines the interface used by modules that implement a protocol on top of HTTP. Types ----- None. Callbacks --------- ### upgrade(Req, Env, Handler, Opts) -> {ok, Req, Env} | {suspend, Module, Function, Args} | {halt, Req} | {error, StatusCode, Req} > Types: > * Req = cowboy_req:req() > * Env = env() > * Handler = module() > * Opts = any() > * Module = module() > * Function = atom() > * Args = [any()] > * StatusCode = cowboy:http_status() > > Upgrade the protocol. > > Please refer to the `cowboy_middleware` manual for a > description of the return values. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_websocket.md000066400000000000000000000011311223335133400224370ustar00rootroot00000000000000cowboy_websocket ================ The `cowboy_websocket` module implements the Websocket protocol. The callbacks for websocket handlers are defined in the manual for the `cowboy_websocket_handler` behaviour. Types ----- ### close_code() = 1000..4999 > Reason for closing the connection. ### frame() = close | ping | pong | {text | binary | close | ping | pong, iodata()} | {close, close_code(), iodata()} > Frames that can be sent to the client. Meta values ----------- ### websocket_version > Type: 7 | 8 | 13 > > The version of the Websocket protocol being used. Exports ------- None. erlang-cowboy-0.8.6+dfsg1/manual/cowboy_websocket_handler.md000066400000000000000000000076461223335133400241550ustar00rootroot00000000000000cowboy_websocket_handler ======================== The `cowboy_websocket_handler` behaviour defines the interface used by Websocket handlers. The `init/3` and `websocket_init/3` callbacks will always be called, followed by zero or more calls to `websocket_handle/3` and `websocket_info/3`. The `websocket_terminate/3` will always be called last. Types ----- None. Callbacks --------- ### init({TransportName, ProtocolName}, Req, Opts) -> {upgrade, protocol, cowboy_websocket} | {upgrade, protocol, cowboy_websocket, Req, Opts} > Types: > * TransportName = tcp | ssl | atom() > * ProtocolName = http | atom() > * Req = cowboy_req:req() > * Opts = any() > > Upgrade the protocol to `cowboy_websocket`. ### websocket_init(TransportName, Req, Opts) -> {ok, Req, State} | {ok, Req, State, hibernate} | {ok, Req, State, Timeout} | {ok, Req, State, Timeout, hibernate} | {shutdown, Req} > Types: > * TransportName = tcp | ssl | atom() > * Req = cowboy_req:req() > * Opts = any() > * State = any() > * Timeout = timeout() > > Initialize the state for this session. > > This function is called before the upgrade to Websocket occurs. > It can be used to negotiate Websocket protocol extensions > with the client. It will typically be used to register this process > to an event manager or a message queue in order to receive > the messages the handler wants to process. > > The connection will stay up for a duration of up to `Timeout` > milliseconds after it last received data from the socket, > at which point it will stop and close the connection. > By default this value is set to `infinity`. It is recommended > to either set this value or ensure by any other mechanism > that the handler will be closed after a certain period of > inactivity. > > The `hibernate` option will hibernate the process until it > starts receiving either data from the Websocket connection > or Erlang messages. > > The `shutdown` return value can be used to close the connection > before upgrading to Websocket. ### websocket_handle(InFrame, Req, State) -> {ok, Req, State} | {ok, Req, State, hibernate} | {reply, OutFrame | [OutFrame], Req, State} | {reply, OutFrame | [OutFrame], Req, State, hibernate} | {shutdown, Req, State} > Types: > * InFrame = {text | binary | ping | pong, binary()} > * Req = cowboy_req:req() > * State = any() > * OutFrame = cowboy_websocket:frame() > > Handle the data received from the Websocket connection. > > This function will be called every time data is received > from the Websocket connection. > > The `shutdown` return value can be used to close the > connection. A close reply will also result in the connection > being closed. > > The `hibernate` option will hibernate the process until > it receives new data from the Websocket connection or an > Erlang message. ### websocket_info(Info, Req, State) -> {ok, Req, State} | {ok, Req, State, hibernate} | {reply, OutFrame | [OutFrame], Req, State} | {reply, OutFrame | [OutFrame], Req, State, hibernate} | {shutdown, Req, State} > Types: > * Info = any() > * Req = cowboy_req:req() > * State = any() > * OutFrame = cowboy_websocket:frame() > > Handle the Erlang message received. > > This function will be called every time an Erlang message > has been received. The message can be any Erlang term. > > The `shutdown` return value can be used to close the > connection. A close reply will also result in the connection > being closed. > > The `hibernate` option will hibernate the process until > it receives another message or new data from the Websocket > connection. ### websocket_terminate(Reason, Req, State) -> ok > Types: > * Reason = {normal, shutdown | timeout} | {remote, closed} | {remote, cowboy_websocket:close_code(), binary()} | {error, badencoding | badframe | closed | atom()} > * Req = cowboy_req:req() > * State = any() > > Perform any necessary cleanup of the state. > > The connection will be closed and the process stopped right > after this call. erlang-cowboy-0.8.6+dfsg1/manual/toc.md000066400000000000000000000012411223335133400176560ustar00rootroot00000000000000Cowboy Function Reference ========================= The function reference documents the public interface of Cowboy. * [The Cowboy Application](cowboy_app.md) * [cowboy](cowboy.md) * [cowboy_handler](cowboy_handler.md) * [cowboy_http_handler](cowboy_http_handler.md) * [cowboy_loop_handler](cowboy_loop_handler.md) * [cowboy_middleware](cowboy_middleware.md) * [cowboy_protocol](cowboy_protocol.md) * [cowboy_req](cowboy_req.md) * [cowboy_rest](cowboy_rest.md) * [cowboy_router](cowboy_router.md) * [cowboy_sub_protocol](cowboy_sub_protocol.md) * [cowboy_websocket](cowboy_websocket.md) * [cowboy_websocket_handler](cowboy_websocket_handler.md) erlang-cowboy-0.8.6+dfsg1/rebar.config000066400000000000000000000001211223335133400175500ustar00rootroot00000000000000{deps, [ {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.4"}} ]}. erlang-cowboy-0.8.6+dfsg1/src/000077500000000000000000000000001223335133400160635ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/src/cowboy.app.src000066400000000000000000000022471223335133400206620ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. {application, cowboy, [ {id, "Cowboy"}, {description, "Small, fast, modular HTTP server."}, {sub_description, "Cowboy is also a socket acceptor pool, " "able to accept connections for any kind of TCP protocol."}, {vsn, "0.8.6"}, {modules, []}, {registered, [cowboy_clock, cowboy_sup]}, {applications, [ kernel, stdlib, ranch, crypto ]}, {mod, {cowboy_app, []}}, {env, []} ]}. erlang-cowboy-0.8.6+dfsg1/src/cowboy.erl000066400000000000000000000063641223335133400201020ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Convenience API to start and stop HTTP/HTTPS listeners. -module(cowboy). -export([start_http/4]). -export([start_https/4]). -export([start_spdy/4]). -export([stop_listener/1]). -export([set_env/3]). -type http_headers() :: [{binary(), iodata()}]. -export_type([http_headers/0]). -type http_status() :: non_neg_integer() | binary(). -export_type([http_status/0]). -type http_version() :: 'HTTP/1.1' | 'HTTP/1.0'. -export_type([http_version/0]). -type onrequest_fun() :: fun((Req) -> Req). -export_type([onrequest_fun/0]). -type onresponse_fun() :: fun((http_status(), http_headers(), iodata(), Req) -> Req). -export_type([onresponse_fun/0]). %% @doc Start an HTTP listener. -spec start_http(ranch:ref(), non_neg_integer(), ranch_tcp:opts(), cowboy_protocol:opts()) -> {ok, pid()} | {error, any()}. start_http(Ref, NbAcceptors, TransOpts, ProtoOpts) when is_integer(NbAcceptors), NbAcceptors > 0 -> ranch:start_listener(Ref, NbAcceptors, ranch_tcp, TransOpts, cowboy_protocol, ProtoOpts). %% @doc Start an HTTPS listener. -spec start_https(ranch:ref(), non_neg_integer(), ranch_ssl:opts(), cowboy_protocol:opts()) -> {ok, pid()} | {error, any()}. start_https(Ref, NbAcceptors, TransOpts, ProtoOpts) when is_integer(NbAcceptors), NbAcceptors > 0 -> ranch:start_listener(Ref, NbAcceptors, ranch_ssl, TransOpts, cowboy_protocol, ProtoOpts). %% @doc Start a SPDY listener. -spec start_spdy(ranch:ref(), non_neg_integer(), ranch_ssl:opts(), cowboy_spdy:opts()) -> {ok, pid()} | {error, any()}. start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts) when is_integer(NbAcceptors), NbAcceptors > 0 -> TransOpts2 = [ {connection_type, supervisor}, {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>, <<"http/1.0">>]} |TransOpts], ranch:start_listener(Ref, NbAcceptors, ranch_ssl, TransOpts2, cowboy_spdy, ProtoOpts). %% @doc Stop a listener. -spec stop_listener(ranch:ref()) -> ok. stop_listener(Ref) -> ranch:stop_listener(Ref). %% @doc Convenience function for setting an environment value. %% %% Allows you to update live an environment value used by middlewares. %% This function is primarily intended to simplify updating the dispatch %% list used for routing. -spec set_env(ranch:ref(), atom(), any()) -> ok. set_env(Ref, Name, Value) -> Opts = ranch:get_protocol_options(Ref), {_, Env} = lists:keyfind(env, 1, Opts), Env2 = [{Name, Value}|lists:keydelete(Name, 1, Env)], Opts2 = lists:keyreplace(env, 1, Opts, {env, Env2}), ok = ranch:set_protocol_options(Ref, Opts2). erlang-cowboy-0.8.6+dfsg1/src/cowboy_app.erl000066400000000000000000000017261223335133400207370ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @private -module(cowboy_app). -behaviour(application). %% API. -export([start/2]). -export([stop/1]). %% API. start(_Type, _Args) -> cowboy_sup:start_link(). stop(_State) -> ok. erlang-cowboy-0.8.6+dfsg1/src/cowboy_bstr.erl000066400000000000000000000103471223335133400211300ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Binary string manipulation. -module(cowboy_bstr). %% Binary strings. -export([capitalize_token/1]). -export([to_lower/1]). -export([to_upper/1]). %% Characters. -export([char_to_lower/1]). -export([char_to_upper/1]). %% @doc Capitalize a token. %% %% The first letter and all letters after a dash are capitalized. %% This is the form seen for header names in the HTTP/1.1 RFC and %% others. Note that using this form isn't required, as header name %% are case insensitive, and it is only provided for use with eventual %% badly implemented clients. -spec capitalize_token(B) -> B when B::binary(). capitalize_token(B) -> capitalize_token(B, true, <<>>). capitalize_token(<<>>, _, Acc) -> Acc; capitalize_token(<< $-, Rest/bits >>, _, Acc) -> capitalize_token(Rest, true, << Acc/binary, $- >>); capitalize_token(<< C, Rest/bits >>, true, Acc) -> capitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>); capitalize_token(<< C, Rest/bits >>, false, Acc) -> capitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>). %% @doc Convert a binary string to lowercase. -spec to_lower(B) -> B when B::binary(). to_lower(B) -> << << (char_to_lower(C)) >> || << C >> <= B >>. %% @doc Convert a binary string to uppercase. -spec to_upper(B) -> B when B::binary(). to_upper(B) -> << << (char_to_upper(C)) >> || << C >> <= B >>. %% @doc Convert [A-Z] characters to lowercase. %% @end %% We gain noticeable speed by matching each value directly. -spec char_to_lower(char()) -> char(). char_to_lower($A) -> $a; char_to_lower($B) -> $b; char_to_lower($C) -> $c; char_to_lower($D) -> $d; char_to_lower($E) -> $e; char_to_lower($F) -> $f; char_to_lower($G) -> $g; char_to_lower($H) -> $h; char_to_lower($I) -> $i; char_to_lower($J) -> $j; char_to_lower($K) -> $k; char_to_lower($L) -> $l; char_to_lower($M) -> $m; char_to_lower($N) -> $n; char_to_lower($O) -> $o; char_to_lower($P) -> $p; char_to_lower($Q) -> $q; char_to_lower($R) -> $r; char_to_lower($S) -> $s; char_to_lower($T) -> $t; char_to_lower($U) -> $u; char_to_lower($V) -> $v; char_to_lower($W) -> $w; char_to_lower($X) -> $x; char_to_lower($Y) -> $y; char_to_lower($Z) -> $z; char_to_lower(Ch) -> Ch. %% @doc Convert [a-z] characters to uppercase. -spec char_to_upper(char()) -> char(). char_to_upper($a) -> $A; char_to_upper($b) -> $B; char_to_upper($c) -> $C; char_to_upper($d) -> $D; char_to_upper($e) -> $E; char_to_upper($f) -> $F; char_to_upper($g) -> $G; char_to_upper($h) -> $H; char_to_upper($i) -> $I; char_to_upper($j) -> $J; char_to_upper($k) -> $K; char_to_upper($l) -> $L; char_to_upper($m) -> $M; char_to_upper($n) -> $N; char_to_upper($o) -> $O; char_to_upper($p) -> $P; char_to_upper($q) -> $Q; char_to_upper($r) -> $R; char_to_upper($s) -> $S; char_to_upper($t) -> $T; char_to_upper($u) -> $U; char_to_upper($v) -> $V; char_to_upper($w) -> $W; char_to_upper($x) -> $X; char_to_upper($y) -> $Y; char_to_upper($z) -> $Z; char_to_upper(Ch) -> Ch. %% Tests. -ifdef(TEST). capitalize_token_test_() -> %% {Header, Result} Tests = [ {<<"heLLo-woRld">>, <<"Hello-World">>}, {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>}, {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>}, {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>}, {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>}, {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--Version">>}, {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>} ], [{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests]. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_client.erl000066400000000000000000000207771223335133400214440ustar00rootroot00000000000000%% Copyright (c) 2012-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @private -module(cowboy_client). -export([init/1]). -export([state/1]). -export([transport/1]). -export([connect/4]). -export([raw_request/2]). -export([request/3]). -export([request/4]). -export([request/5]). -export([response/1]). -export([response_body/1]). -export([skip_body/1]). -export([stream_status/1]). -export([stream_headers/1]). -export([stream_header/1]). -export([stream_body/1]). -record(client, { state = wait :: wait | request | response | response_body, opts = [] :: [any()], socket = undefined :: undefined | inet:socket(), transport = undefined :: module(), timeout = 5000 :: timeout(), %% @todo Configurable. buffer = <<>> :: binary(), connection = keepalive :: keepalive | close, version = 'HTTP/1.1' :: cowboy:http_version(), response_body = undefined :: undefined | non_neg_integer() }). init(Opts) -> {ok, #client{opts=Opts}}. state(#client{state=State}) -> State. transport(#client{socket=undefined}) -> {error, notconnected}; transport(#client{transport=Transport, socket=Socket}) -> {ok, Transport, Socket}. connect(Transport, Host, Port, Client) when is_binary(Host) -> connect(Transport, binary_to_list(Host), Port, Client); connect(Transport, Host, Port, Client=#client{state=State, opts=Opts}) when is_atom(Transport), is_list(Host), is_integer(Port), is_record(Client, client), State =:= wait -> {ok, Socket} = Transport:connect(Host, Port, Opts), {ok, Client#client{state=request, socket=Socket, transport=Transport}}. raw_request(Data, Client=#client{state=response_body}) -> {done, Client2} = skip_body(Client), raw_request(Data, Client2); raw_request(Data, Client=#client{ state=State, socket=Socket, transport=Transport}) when State =:= request -> ok = Transport:send(Socket, Data), {ok, Client}. request(Method, URL, Client) -> request(Method, URL, [], <<>>, Client). request(Method, URL, Headers, Client) -> request(Method, URL, Headers, <<>>, Client). request(Method, URL, Headers, Body, Client=#client{state=response_body}) -> {done, Client2} = skip_body(Client), request(Method, URL, Headers, Body, Client2); request(Method, URL, Headers, Body, Client=#client{ state=State, version=Version}) when State =:= wait; State =:= request -> {Transport, FullHost, Host, Port, Path} = parse_url(URL), {ok, Client2} = case State of wait -> connect(Transport, Host, Port, Client); request -> {ok, Client} end, VersionBin = atom_to_binary(Version, latin1), %% @todo do keepalive too, allow override... Headers2 = [ {<<"host">>, FullHost}, {<<"user-agent">>, <<"Cow">>} |Headers], Headers3 = case iolist_size(Body) of 0 -> Headers2; Length -> [{<<"content-length">>, integer_to_list(Length)}|Headers2] end, HeadersData = [[Name, <<": ">>, Value, <<"\r\n">>] || {Name, Value} <- Headers3], Data = [Method, <<" ">>, Path, <<" ">>, VersionBin, <<"\r\n">>, HeadersData, <<"\r\n">>, Body], raw_request(Data, Client2). parse_url(<< "https://", Rest/binary >>) -> parse_url(Rest, ranch_ssl); parse_url(<< "http://", Rest/binary >>) -> parse_url(Rest, ranch_tcp); parse_url(URL) -> parse_url(URL, ranch_tcp). parse_url(URL, Transport) -> case binary:split(URL, <<"/">>) of [Peer] -> {Host, Port} = parse_peer(Peer, Transport), {Transport, Peer, Host, Port, <<"/">>}; [Peer, Path] -> {Host, Port} = parse_peer(Peer, Transport), {Transport, Peer, Host, Port, [<<"/">>, Path]} end. parse_peer(Peer, Transport) -> case binary:split(Peer, <<":">>) of [Host] when Transport =:= ranch_tcp -> {binary_to_list(Host), 80}; [Host] when Transport =:= ranch_ssl -> {binary_to_list(Host), 443}; [Host, Port] -> {binary_to_list(Host), list_to_integer(binary_to_list(Port))} end. response(Client=#client{state=response_body}) -> {done, Client2} = skip_body(Client), response(Client2); response(Client=#client{state=request}) -> case stream_status(Client) of {ok, Status, _, Client2} -> case stream_headers(Client2) of {ok, Headers, Client3} -> {ok, Status, Headers, Client3}; {error, Reason} -> {error, Reason} end; {error, Reason} -> {error, Reason} end. response_body(Client=#client{state=response_body}) -> response_body_loop(Client, <<>>). response_body_loop(Client, Acc) -> case stream_body(Client) of {ok, Data, Client2} -> response_body_loop(Client2, << Acc/binary, Data/binary >>); {done, Client2} -> {ok, Acc, Client2}; {error, Reason} -> {error, Reason} end. skip_body(Client=#client{state=response_body}) -> case stream_body(Client) of {ok, _, Client2} -> skip_body(Client2); Done -> Done end. stream_status(Client=#client{state=State, buffer=Buffer}) when State =:= request -> case binary:split(Buffer, <<"\r\n">>) of [Line, Rest] -> parse_version(Client#client{state=response, buffer=Rest}, Line); _ -> case recv(Client) of {ok, Data} -> Buffer2 = << Buffer/binary, Data/binary >>, stream_status(Client#client{buffer=Buffer2}); {error, Reason} -> {error, Reason} end end. parse_version(Client, << "HTTP/1.1 ", Rest/binary >>) -> parse_status(Client, Rest, 'HTTP/1.1'); parse_version(Client, << "HTTP/1.0 ", Rest/binary >>) -> parse_status(Client, Rest, 'HTTP/1.0'). parse_status(Client, << S3, S2, S1, " ", StatusStr/binary >>, Version) when S3 >= $0, S3 =< $9, S2 >= $0, S2 =< $9, S1 >= $0, S1 =< $9 -> Status = (S3 - $0) * 100 + (S2 - $0) * 10 + S1 - $0, {ok, Status, StatusStr, Client#client{version=Version}}. stream_headers(Client=#client{state=State}) when State =:= response -> stream_headers(Client, []). stream_headers(Client, Acc) -> case stream_header(Client) of {ok, Name, Value, Client2} -> stream_headers(Client2, [{Name, Value}|Acc]); {done, Client2} -> {ok, Acc, Client2}; {error, Reason} -> {error, Reason} end. stream_header(Client=#client{state=State, buffer=Buffer, response_body=RespBody}) when State =:= response -> case binary:split(Buffer, <<"\r\n">>) of [<<>>, Rest] -> %% If we have a body, set response_body. Client2 = case RespBody of undefined -> Client#client{state=request}; 0 -> Client#client{state=request}; _ -> Client#client{state=response_body} end, {done, Client2#client{buffer=Rest}}; [Line, Rest] -> %% @todo Do a better parsing later on. [Name, Value] = binary:split(Line, <<": ">>), Name2 = cowboy_bstr:to_lower(Name), Client2 = case Name2 of <<"content-length">> -> Length = list_to_integer(binary_to_list(Value)), if Length >= 0 -> ok end, Client#client{response_body=Length}; _ -> Client end, {ok, Name2, Value, Client2#client{buffer=Rest}}; _ -> case recv(Client) of {ok, Data} -> Buffer2 = << Buffer/binary, Data/binary >>, stream_header(Client#client{buffer=Buffer2}); {error, Reason} -> {error, Reason} end end. stream_body(Client=#client{state=response_body, response_body=RespBody}) when RespBody =:= undefined; RespBody =:= 0 -> {done, Client#client{state=request, response_body=undefined}}; stream_body(Client=#client{state=response_body, buffer=Buffer, response_body=Length}) when is_integer(Length) -> case byte_size(Buffer) of 0 -> case recv(Client) of {ok, Body} when byte_size(Body) =< Length -> Length2 = Length - byte_size(Body), {ok, Body, Client#client{response_body=Length2}}; {ok, Data} -> << Body:Length/binary, Rest/binary >> = Data, {ok, Body, Client#client{buffer=Rest, response_body=undefined}}; {error, Reason} -> {error, Reason} end; N when N =< Length -> Length2 = Length - N, {ok, Buffer, Client#client{buffer= <<>>, response_body=Length2}}; _ -> << Body:Length/binary, Rest/binary >> = Buffer, {ok, Body, Client#client{buffer=Rest, response_body=undefined}} end. recv(#client{socket=Socket, transport=Transport, timeout=Timeout}) -> Transport:recv(Socket, 0, Timeout). erlang-cowboy-0.8.6+dfsg1/src/cowboy_clock.erl000066400000000000000000000205361223335133400212520ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Date and time related functions. %% %% While a gen_server process runs in the background to update %% the cache of formatted dates every second, all API calls are %% local and directly read from the ETS cache table, providing %% fast time and date computations. -module(cowboy_clock). -behaviour(gen_server). %% API. -export([start_link/0]). -export([stop/0]). -export([rfc1123/0]). -export([rfc1123/1]). -export([rfc2109/1]). %% gen_server. -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). -export([code_change/3]). -record(state, { universaltime = undefined :: undefined | calendar:datetime(), rfc1123 = <<>> :: binary(), tref = undefined :: undefined | timer:tref() }). -define(SERVER, ?MODULE). -define(TABLE, ?MODULE). %% API. %% @private -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %% @private -spec stop() -> stopped. stop() -> gen_server:call(?SERVER, stop). %% @doc Return the current date and time formatted according to RFC-1123. -spec rfc1123() -> binary(). rfc1123() -> ets:lookup_element(?TABLE, rfc1123, 2). %% @doc Return the given date and time formatted according to RFC-1123. -spec rfc1123(calendar:datetime()) -> binary(). rfc1123(DateTime) -> update_rfc1123(<<>>, undefined, DateTime). %% @doc Return the given date and time formatted according to RFC-2109. %% %% This format is used in the set-cookie header sent with %% HTTP responses. -spec rfc2109(calendar:datetime()) -> binary(). rfc2109({Date = {Y, Mo, D}, {H, Mi, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, "-", (month(Mo))/binary, "-", (list_to_binary(integer_to_list(Y)))/binary, " ", (pad_int(H))/binary, $:, (pad_int(Mi))/binary, $:, (pad_int(S))/binary, " GMT" >>. %% gen_server. %% @private init([]) -> ?TABLE = ets:new(?TABLE, [set, protected, named_table, {read_concurrency, true}]), T = erlang:universaltime(), B = update_rfc1123(<<>>, undefined, T), {ok, TRef} = timer:send_interval(1000, update), ets:insert(?TABLE, {rfc1123, B}), {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}. %% @private handle_call(stop, _From, State=#state{tref=TRef}) -> {ok, cancel} = timer:cancel(TRef), {stop, normal, stopped, State}; handle_call(_Request, _From, State) -> {reply, ignored, State}. %% @private handle_cast(_Msg, State) -> {noreply, State}. %% @private handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) -> T = erlang:universaltime(), B2 = update_rfc1123(B1, Prev, T), ets:insert(?TABLE, {rfc1123, B2}), {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}}; handle_info(_Info, State) -> {noreply, State}. %% @private terminate(_Reason, _State) -> ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. %% Internal. -spec update_rfc1123(binary(), undefined | calendar:datetime(), calendar:datetime()) -> binary(). update_rfc1123(Bin, Now, Now) -> Bin; update_rfc1123(<< Keep:23/binary, _/bits >>, {Date, {H, M, _}}, {Date, {H, M, S}}) -> << Keep/binary, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< Keep:20/binary, _/bits >>, {Date, {H, _, _}}, {Date, {H, M, S}}) -> << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) -> << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>, {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>, {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", (month(Mo))/binary, Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>; update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) -> Wday = calendar:day_of_the_week(Date), << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ", (month(Mo))/binary, " ", (list_to_binary(integer_to_list(Y)))/binary, " ", (pad_int(H))/binary, $:, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>. %% Following suggestion by MononcQc on #erlounge. -spec pad_int(0..59) -> binary(). pad_int(X) when X < 10 -> << $0, ($0 + X) >>; pad_int(X) -> list_to_binary(integer_to_list(X)). -spec weekday(1..7) -> <<_:24>>. weekday(1) -> <<"Mon">>; weekday(2) -> <<"Tue">>; weekday(3) -> <<"Wed">>; weekday(4) -> <<"Thu">>; weekday(5) -> <<"Fri">>; weekday(6) -> <<"Sat">>; weekday(7) -> <<"Sun">>. -spec month(1..12) -> <<_:24>>. month( 1) -> <<"Jan">>; month( 2) -> <<"Feb">>; month( 3) -> <<"Mar">>; month( 4) -> <<"Apr">>; month( 5) -> <<"May">>; month( 6) -> <<"Jun">>; month( 7) -> <<"Jul">>; month( 8) -> <<"Aug">>; month( 9) -> <<"Sep">>; month(10) -> <<"Oct">>; month(11) -> <<"Nov">>; month(12) -> <<"Dec">>. %% Tests. -ifdef(TEST). rfc2109_test_() -> Tests = [ {<<"Sat, 14-May-2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}}, {<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}} ], [{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests]. update_rfc1123_test_() -> Tests = [ {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined, {{2011, 5, 14}, {14, 25, 33}}, <<>>}, {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}}, {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>}, {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}}, {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>}, {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}}, {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>}, {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}}, {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>}, {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>}, {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}}, {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>} ], [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests]. pad_int_test_() -> Tests = [ { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>}, { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>}, { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>}, {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>}, {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>}, {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>}, {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>}, {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>}, {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>}, {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>}, {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>}, {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>}, {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>}, {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>}, {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>} ], [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests]. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_handler.erl000066400000000000000000000303611223335133400215710ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Handler middleware. %% %% Execute the handler given by the handler and handler_opts %% environment values. The result of this execution is added to the %% environment under the result value. %% %% When using loop handlers, we are receiving data from the socket because we %% want to know when the socket gets closed. This is generally not an issue %% because these kinds of requests are generally not pipelined, and don't have %% a body. If they do have a body, this body is often read in the %% init/3 callback and this is no problem. Otherwise, this data %% accumulates in a buffer until we reach a certain threshold of 5000 bytes %% by default. This can be configured through the loop_max_buffer %% environment value. The request will be terminated with an %% {error, overflow} reason if this threshold is reached. %% %% @see cowboy_http_handler -module(cowboy_handler). -behaviour(cowboy_middleware). -export([execute/2]). -export([handler_loop/4]). -record(state, { env :: cowboy_middleware:env(), hibernate = false :: boolean(), loop_buffer_size = 0 :: non_neg_integer(), loop_max_buffer = 5000 :: non_neg_integer() | infinity, loop_timeout = infinity :: timeout(), loop_timeout_ref = undefined :: undefined | reference(), resp_sent = false :: boolean() }). %% @private -spec execute(Req, Env) -> {ok, Req, Env} | {error, 500, Req} | {suspend, ?MODULE, handler_loop, [any()]} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). execute(Req, Env) -> {_, Handler} = lists:keyfind(handler, 1, Env), {_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env), MaxBuffer = case lists:keyfind(loop_max_buffer, 1, Env) of false -> 5000; {_, MaxBuffer0} -> MaxBuffer0 end, handler_init(Req, #state{env=Env, loop_max_buffer=MaxBuffer}, Handler, HandlerOpts). -spec handler_init(Req, #state{}, module(), any()) -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_init(Req, State, Handler, HandlerOpts) -> Transport = cowboy_req:get(transport, Req), try Handler:init({Transport:name(), http}, Req, HandlerOpts) of {ok, Req2, HandlerState} -> handler_handle(Req2, State, Handler, HandlerState); {loop, Req2, HandlerState} -> handler_after_callback(Req2, State, Handler, HandlerState); {loop, Req2, HandlerState, hibernate} -> handler_after_callback(Req2, State#state{hibernate=true}, Handler, HandlerState); {loop, Req2, HandlerState, Timeout} -> State2 = handler_loop_timeout(State#state{loop_timeout=Timeout}), handler_after_callback(Req2, State2, Handler, HandlerState); {loop, Req2, HandlerState, Timeout, hibernate} -> State2 = handler_loop_timeout(State#state{ hibernate=true, loop_timeout=Timeout}), handler_after_callback(Req2, State2, Handler, HandlerState); {shutdown, Req2, HandlerState} -> terminate_request(Req2, State, Handler, HandlerState, {normal, shutdown}); %% @todo {upgrade, transport, Module} {upgrade, protocol, Module} -> upgrade_protocol(Req, State, Handler, HandlerOpts, Module); {upgrade, protocol, Module, Req2, HandlerOpts2} -> upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n" "** Options were ~p~n" "** Request was ~p~n" "** Stacktrace: ~p~n~n", [Handler, init, 3, Class, Reason, HandlerOpts, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. -spec upgrade_protocol(Req, #state{}, module(), any(), module()) -> {ok, Req, Env} | {suspend, module(), atom(), any()} | {halt, Req} | {error, cowboy:http_status(), Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). upgrade_protocol(Req, #state{env=Env}, Handler, HandlerOpts, Module) -> Module:upgrade(Req, Env, Handler, HandlerOpts). -spec handler_handle(Req, #state{}, module(), any()) -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} when Req::cowboy_req:req(). handler_handle(Req, State, Handler, HandlerState) -> try Handler:handle(Req, HandlerState) of {ok, Req2, HandlerState2} -> terminate_request(Req2, State, Handler, HandlerState2, {normal, shutdown}) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n" "** Handler state was ~p~n" "** Request was ~p~n" "** Stacktrace: ~p~n~n", [Handler, handle, 2, Class, Reason, HandlerState, cowboy_req:to_list(Req), erlang:get_stacktrace()]), handler_terminate(Req, Handler, HandlerState, Reason), error_terminate(Req, State) end. %% Update the state if the response was sent in the callback. -spec handler_after_callback(Req, #state{}, module(), any()) -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_after_callback(Req, State=#state{resp_sent=false}, Handler, HandlerState) -> receive {cowboy_req, resp_sent} -> handler_before_loop(Req, State#state{resp_sent=true}, Handler, HandlerState) after 0 -> handler_before_loop(Req, State, Handler, HandlerState) end; handler_after_callback(Req, State, Handler, HandlerState) -> handler_before_loop(Req, State, Handler, HandlerState). %% We don't listen for Transport closes because that would force us %% to receive data and buffer it indefinitely. -spec handler_before_loop(Req, #state{}, module(), any()) -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) -> [Socket, Transport] = cowboy_req:get([socket, transport], Req), Transport:setopts(Socket, [{active, once}]), {suspend, ?MODULE, handler_loop, [Req, State#state{hibernate=false}, Handler, HandlerState]}; handler_before_loop(Req, State, Handler, HandlerState) -> [Socket, Transport] = cowboy_req:get([socket, transport], Req), Transport:setopts(Socket, [{active, once}]), handler_loop(Req, State, Handler, HandlerState). %% Almost the same code can be found in cowboy_websocket. -spec handler_loop_timeout(#state{}) -> #state{}. handler_loop_timeout(State=#state{loop_timeout=infinity}) -> State#state{loop_timeout_ref=undefined}; handler_loop_timeout(State=#state{loop_timeout=Timeout, loop_timeout_ref=PrevRef}) -> _ = case PrevRef of undefined -> ignore; PrevRef -> erlang:cancel_timer(PrevRef) end, TRef = erlang:start_timer(Timeout, self(), ?MODULE), State#state{loop_timeout_ref=TRef}. %% @private -spec handler_loop(Req, #state{}, module(), any()) -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_loop(Req, State=#state{loop_buffer_size=NbBytes, loop_max_buffer=Threshold, loop_timeout_ref=TRef}, Handler, HandlerState) -> [Socket, Transport] = cowboy_req:get([socket, transport], Req), {OK, Closed, Error} = Transport:messages(), receive {OK, Socket, Data} -> NbBytes2 = NbBytes + byte_size(Data), if NbBytes2 > Threshold -> _ = handler_terminate(Req, Handler, HandlerState, {error, overflow}), error_terminate(Req, State); true -> Req2 = cowboy_req:append_buffer(Data, Req), State2 = handler_loop_timeout(State#state{ loop_buffer_size=NbBytes2}), handler_before_loop(Req2, State2, Handler, HandlerState) end; {Closed, Socket} -> terminate_request(Req, State, Handler, HandlerState, {error, closed}); {Error, Socket, Reason} -> terminate_request(Req, State, Handler, HandlerState, {error, Reason}); {timeout, TRef, ?MODULE} -> handler_after_loop(Req, State, Handler, HandlerState, {normal, timeout}); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> handler_before_loop(Req, State, Handler, HandlerState); Message -> %% We set the socket back to {active, false} mode in case %% the handler is going to call recv. We also flush any %% data received after that and put it into the buffer. %% We do not check the size here, if data keeps coming %% we'll error out on the next packet received. Transport:setopts(Socket, [{active, false}]), Req2 = receive {OK, Socket, Data} -> cowboy_req:append_buffer(Data, Req) after 0 -> Req end, handler_call(Req2, State, Handler, HandlerState, Message) end. -spec handler_call(Req, #state{}, module(), any(), any()) -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_call(Req, State, Handler, HandlerState, Message) -> try Handler:info(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> handler_after_loop(Req2, State, Handler, HandlerState2, {normal, shutdown}); {loop, Req2, HandlerState2} -> handler_after_callback(Req2, State, Handler, HandlerState2); {loop, Req2, HandlerState2, hibernate} -> handler_after_callback(Req2, State#state{hibernate=true}, Handler, HandlerState2) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n" "** Handler state was ~p~n" "** Request was ~p~n" "** Stacktrace: ~p~n~n", [Handler, info, 3, Class, Reason, HandlerState, cowboy_req:to_list(Req), erlang:get_stacktrace()]), handler_terminate(Req, Handler, HandlerState, Reason), error_terminate(Req, State) end. %% It is sometimes important to make a socket passive as it was initially %% and as it is expected to be by cowboy_protocol, right after we're done %% with loop handling. The browser may freely pipeline a bunch of requests %% if previous one was, say, a JSONP long-polling request. -spec handler_after_loop(Req, #state{}, module(), any(), {normal, timeout | shutdown} | {error, atom()}) -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). handler_after_loop(Req, State, Handler, HandlerState, Reason) -> [Socket, Transport] = cowboy_req:get([socket, transport], Req), Transport:setopts(Socket, [{active, false}]), {OK, _Closed, _Error} = Transport:messages(), Req2 = receive {OK, Socket, Data} -> cowboy_req:append_buffer(Data, Req) after 0 -> Req end, terminate_request(Req2, State, Handler, HandlerState, Reason). -spec terminate_request(Req, #state{}, module(), any(), {normal, timeout | shutdown} | {error, atom()}) -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). terminate_request(Req, #state{env=Env}, Handler, HandlerState, Reason) -> HandlerRes = handler_terminate(Req, Handler, HandlerState, Reason), {ok, Req, [{result, HandlerRes}|Env]}. -spec handler_terminate(cowboy_req:req(), module(), any(), {normal, timeout | shutdown} | {error, atom()}) -> ok. handler_terminate(Req, Handler, HandlerState, Reason) -> try Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState) catch Class:Reason2 -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n" "** Handler state was ~p~n" "** Request was ~p~n" "** Stacktrace: ~p~n~n", [Handler, terminate, 3, Class, Reason2, HandlerState, cowboy_req:to_list(Req), erlang:get_stacktrace()]) end. %% Only send an error reply if there is no resp_sent message. -spec error_terminate(Req, #state{}) -> {error, 500, Req} | {halt, Req} when Req::cowboy_req:req(). error_terminate(Req, #state{resp_sent=true}) -> %% Close the connection, but do not attempt sending a reply. {halt, cowboy_req:set([{connection, close}, {resp_state, done}], Req)}; error_terminate(Req, _) -> {error, 500, Req}. erlang-cowboy-0.8.6+dfsg1/src/cowboy_http.erl000066400000000000000000001316001223335133400211310ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% Copyright (c) 2011, Anthony Ramine %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Core HTTP parsing API. -module(cowboy_http). %% Parsing. -export([list/2]). -export([nonempty_list/2]). -export([cookie_list/1]). -export([content_type/1]). -export([media_range/2]). -export([conneg/2]). -export([language_range/2]). -export([entity_tag_match/1]). -export([expectation/2]). -export([params/2]). -export([http_date/1]). -export([rfc1123_date/1]). -export([rfc850_date/1]). -export([asctime_date/1]). -export([whitespace/2]). -export([digits/1]). -export([token/2]). -export([token_ci/2]). -export([quoted_string/2]). -export([authorization/2]). -export([range/1]). -export([parameterized_tokens/1]). %% Decoding. -export([te_chunked/2]). -export([te_identity/2]). -export([ce_identity/1]). %% Interpretation. -export([cookie_to_iodata/3]). -export([urldecode/1]). -export([urldecode/2]). -export([urlencode/1]). -export([urlencode/2]). -export([x_www_form_urlencoded/1]). %% Parsing. %% @doc Parse a non-empty list of the given type. -spec nonempty_list(binary(), fun()) -> [any(), ...] | {error, badarg}. nonempty_list(Data, Fun) -> case list(Data, Fun, []) of {error, badarg} -> {error, badarg}; [] -> {error, badarg}; L -> lists:reverse(L) end. %% @doc Parse a list of the given type. -spec list(binary(), fun()) -> list() | {error, badarg}. list(Data, Fun) -> case list(Data, Fun, []) of {error, badarg} -> {error, badarg}; L -> lists:reverse(L) end. -spec list(binary(), fun(), [binary()]) -> [any()] | {error, badarg}. %% From the RFC: %%
Wherever this construct is used, null elements are allowed, %% but do not contribute to the count of elements present. %% That is, "(element), , (element) " is permitted, but counts %% as only two elements. Therefore, where at least one element is required, %% at least one non-null element MUST be present.
list(Data, Fun, Acc) -> whitespace(Data, fun (<<>>) -> Acc; (<< $,, Rest/binary >>) -> list(Rest, Fun, Acc); (Rest) -> Fun(Rest, fun (D, I) -> whitespace(D, fun (<<>>) -> [I|Acc]; (<< $,, R/binary >>) -> list(R, Fun, [I|Acc]); (_Any) -> {error, badarg} end) end) end). %% @doc Parse a list of cookies. %% %% We need a special function for this because we need to support both %% $; and $, as separators as per RFC2109. -spec cookie_list(binary()) -> [{binary(), binary()}] | {error, badarg}. cookie_list(Data) -> case cookie_list(Data, []) of {error, badarg} -> {error, badarg}; [] -> {error, badarg}; L -> lists:reverse(L) end. -spec cookie_list(binary(), Acc) -> Acc | {error, badarg} when Acc::[{binary(), binary()}]. cookie_list(Data, Acc) -> whitespace(Data, fun (<<>>) -> Acc; (<< $,, Rest/binary >>) -> cookie_list(Rest, Acc); (<< $;, Rest/binary >>) -> cookie_list(Rest, Acc); (Rest) -> cookie(Rest, fun (Rest2, << $$, _/binary >>, _) -> cookie_list(Rest2, Acc); (Rest2, Name, Value) -> cookie_list(Rest2, [{Name, Value}|Acc]) end) end). -spec cookie(binary(), fun()) -> any(). cookie(Data, Fun) -> whitespace(Data, fun (Rest) -> cookie_name(Rest, fun (_Rest2, <<>>) -> {error, badarg}; (<< $=, Rest2/binary >>, Name) -> cookie_value(Rest2, fun (Rest3, Value) -> Fun(Rest3, Name, Value) end); (_Rest2, _Attr) -> {error, badarg} end) end). -spec cookie_name(binary(), fun()) -> any(). cookie_name(Data, Fun) -> cookie_name(Data, Fun, <<>>). -spec cookie_name(binary(), fun(), binary()) -> any(). cookie_name(<<>>, Fun, Acc) -> Fun(<<>>, Acc); cookie_name(Data = << C, _Rest/binary >>, Fun, Acc) when C =:= $=; C =:= $,; C =:= $;; C =:= $\s; C =:= $\t; C =:= $\r; C =:= $\n; C =:= $\013; C =:= $\014 -> Fun(Data, Acc); cookie_name(<< C, Rest/binary >>, Fun, Acc) -> cookie_name(Rest, Fun, << Acc/binary, C >>). -spec cookie_value(binary(), fun()) -> any(). cookie_value(Data, Fun) -> cookie_value(Data, Fun, <<>>). -spec cookie_value(binary(), fun(), binary()) -> any(). cookie_value(<<>>, Fun, Acc) -> Fun(<<>>, Acc); cookie_value(Data = << C, _Rest/binary >>, Fun, Acc) when C =:= $,; C =:= $;; C =:= $\s; C =:= $\t; C =:= $\r; C =:= $\n; C =:= $\013; C =:= $\014 -> Fun(Data, Acc); cookie_value(<< C, Rest/binary >>, Fun, Acc) -> cookie_value(Rest, Fun, << Acc/binary, C >>). %% @doc Parse a content type. %% %% We lowercase the charset header as we know it's case insensitive. -spec content_type(binary()) -> any(). content_type(Data) -> media_type(Data, fun (Rest, Type, SubType) -> params(Rest, fun (<<>>, Params) -> case lists:keyfind(<<"charset">>, 1, Params) of false -> {Type, SubType, Params}; {_, Charset} -> Charset2 = cowboy_bstr:to_lower(Charset), Params2 = lists:keyreplace(<<"charset">>, 1, Params, {<<"charset">>, Charset2}), {Type, SubType, Params2} end; (_Rest2, _) -> {error, badarg} end) end). %% @doc Parse a media range. -spec media_range(binary(), fun()) -> any(). media_range(Data, Fun) -> media_type(Data, fun (Rest, Type, SubType) -> media_range_params(Rest, Fun, Type, SubType, []) end). -spec media_range_params(binary(), fun(), binary(), binary(), [{binary(), binary()}]) -> any(). media_range_params(Data, Fun, Type, SubType, Acc) -> whitespace(Data, fun (<< $;, Rest/binary >>) -> whitespace(Rest, fun (Rest2) -> media_range_param_attr(Rest2, Fun, Type, SubType, Acc) end); (Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000, []}) end). -spec media_range_param_attr(binary(), fun(), binary(), binary(), [{binary(), binary()}]) -> any(). media_range_param_attr(Data, Fun, Type, SubType, Acc) -> token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (<< $=, Rest/binary >>, Attr) -> media_range_param_value(Rest, Fun, Type, SubType, Acc, Attr) end). -spec media_range_param_value(binary(), fun(), binary(), binary(), [{binary(), binary()}], binary()) -> any(). media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) -> qvalue(Data, fun (Rest, Quality) -> accept_ext(Rest, Fun, Type, SubType, Acc, Quality, []) end); media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) -> word(Data, fun (Rest, Value) -> media_range_params(Rest, Fun, Type, SubType, [{Attr, Value}|Acc]) end). %% @doc Parse a media type. -spec media_type(binary(), fun()) -> any(). media_type(Data, Fun) -> token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (<< $/, Rest/binary >>, Type) -> token_ci(Rest, fun (_Rest2, <<>>) -> {error, badarg}; (Rest2, SubType) -> Fun(Rest2, Type, SubType) end); %% This is a non-strict parsing clause required by some user agents %% that use * instead of */* in the list of media types. (Rest, <<"*">> = Type) -> token_ci(<<"*", Rest/binary>>, fun (_Rest2, <<>>) -> {error, badarg}; (Rest2, SubType) -> Fun(Rest2, Type, SubType) end); (_Rest, _Type) -> {error, badarg} end). -spec accept_ext(binary(), fun(), binary(), binary(), [{binary(), binary()}], 0..1000, [{binary(), binary()} | binary()]) -> any(). accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) -> whitespace(Data, fun (<< $;, Rest/binary >>) -> whitespace(Rest, fun (Rest2) -> accept_ext_attr(Rest2, Fun, Type, SubType, Params, Quality, Acc) end); (Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Params)}, Quality, lists:reverse(Acc)}) end). -spec accept_ext_attr(binary(), fun(), binary(), binary(), [{binary(), binary()}], 0..1000, [{binary(), binary()} | binary()]) -> any(). accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) -> token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (<< $=, Rest/binary >>, Attr) -> accept_ext_value(Rest, Fun, Type, SubType, Params, Quality, Acc, Attr); (Rest, Attr) -> accept_ext(Rest, Fun, Type, SubType, Params, Quality, [Attr|Acc]) end). -spec accept_ext_value(binary(), fun(), binary(), binary(), [{binary(), binary()}], 0..1000, [{binary(), binary()} | binary()], binary()) -> any(). accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) -> word(Data, fun (Rest, Value) -> accept_ext(Rest, Fun, Type, SubType, Params, Quality, [{Attr, Value}|Acc]) end). %% @doc Parse a conneg header (Accept-Charset, Accept-Encoding), %% followed by an optional quality value. -spec conneg(binary(), fun()) -> any(). conneg(Data, Fun) -> token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (Rest, Conneg) -> maybe_qparam(Rest, fun (Rest2, Quality) -> Fun(Rest2, {Conneg, Quality}) end) end). %% @doc Parse a language range, followed by an optional quality value. -spec language_range(binary(), fun()) -> any(). language_range(<< $*, Rest/binary >>, Fun) -> language_range_ret(Rest, Fun, '*'); language_range(Data, Fun) -> language_tag(Data, fun (Rest, LanguageTag) -> language_range_ret(Rest, Fun, LanguageTag) end). -spec language_range_ret(binary(), fun(), '*' | {binary(), [binary()]}) -> any(). language_range_ret(Data, Fun, LanguageTag) -> maybe_qparam(Data, fun (Rest, Quality) -> Fun(Rest, {LanguageTag, Quality}) end). -spec language_tag(binary(), fun()) -> any(). language_tag(Data, Fun) -> alpha(Data, fun (_Rest, Tag) when byte_size(Tag) =:= 0; byte_size(Tag) > 8 -> {error, badarg}; (<< $-, Rest/binary >>, Tag) -> language_subtag(Rest, Fun, Tag, []); (Rest, Tag) -> Fun(Rest, Tag) end). -spec language_subtag(binary(), fun(), binary(), [binary()]) -> any(). language_subtag(Data, Fun, Tag, Acc) -> alpha(Data, fun (_Rest, SubTag) when byte_size(SubTag) =:= 0; byte_size(SubTag) > 8 -> {error, badarg}; (<< $-, Rest/binary >>, SubTag) -> language_subtag(Rest, Fun, Tag, [SubTag|Acc]); (Rest, SubTag) -> %% Rebuild the full tag now that we know it's correct Sub = << << $-, S/binary >> || S <- lists:reverse([SubTag|Acc]) >>, Fun(Rest, << Tag/binary, Sub/binary >>) end). -spec maybe_qparam(binary(), fun()) -> any(). maybe_qparam(Data, Fun) -> whitespace(Data, fun (<< $;, Rest/binary >>) -> whitespace(Rest, fun (Rest2) -> %% This is a non-strict parsing clause required by some user agents %% that use the wrong delimiter putting a charset where a qparam is %% expected. try qparam(Rest2, Fun) of Result -> Result catch error:function_clause -> Fun(<<",", Rest2/binary>>, 1000) end end); (Rest) -> Fun(Rest, 1000) end). %% @doc Parse a quality parameter string (for example q=0.500). -spec qparam(binary(), fun()) -> any(). qparam(<< Q, $=, Data/binary >>, Fun) when Q =:= $q; Q =:= $Q -> qvalue(Data, Fun). %% @doc Parse either a list of entity tags or a "*". -spec entity_tag_match(binary()) -> any(). entity_tag_match(<< $*, Rest/binary >>) -> whitespace(Rest, fun (<<>>) -> '*'; (_Any) -> {error, badarg} end); entity_tag_match(Data) -> nonempty_list(Data, fun entity_tag/2). %% @doc Parse an entity-tag. -spec entity_tag(binary(), fun()) -> any(). entity_tag(<< "W/", Rest/binary >>, Fun) -> opaque_tag(Rest, Fun, weak); entity_tag(Data, Fun) -> opaque_tag(Data, Fun, strong). -spec opaque_tag(binary(), fun(), weak | strong) -> any(). opaque_tag(Data, Fun, Strength) -> quoted_string(Data, fun (_Rest, <<>>) -> {error, badarg}; (Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag}) end). %% @doc Parse an expectation. -spec expectation(binary(), fun()) -> any(). expectation(Data, Fun) -> token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (<< $=, Rest/binary >>, Expectation) -> word(Rest, fun (Rest2, ExtValue) -> params(Rest2, fun (Rest3, ExtParams) -> Fun(Rest3, {Expectation, ExtValue, ExtParams}) end) end); (Rest, Expectation) -> Fun(Rest, Expectation) end). %% @doc Parse a list of parameters (a=b;c=d). -spec params(binary(), fun()) -> any(). params(Data, Fun) -> params(Data, Fun, []). -spec params(binary(), fun(), [{binary(), binary()}]) -> any(). params(Data, Fun, Acc) -> whitespace(Data, fun (<< $;, Rest/binary >>) -> param(Rest, fun (Rest2, Attr, Value) -> params(Rest2, Fun, [{Attr, Value}|Acc]) end); (Rest) -> Fun(Rest, lists:reverse(Acc)) end). -spec param(binary(), fun()) -> any(). param(Data, Fun) -> whitespace(Data, fun (Rest) -> token_ci(Rest, fun (_Rest2, <<>>) -> {error, badarg}; (<< $=, Rest2/binary >>, Attr) -> word(Rest2, fun (Rest3, Value) -> Fun(Rest3, Attr, Value) end); (_Rest2, _Attr) -> {error, badarg} end) end). %% @doc Parse an HTTP date (RFC1123, RFC850 or asctime date). %% @end %% %% While this may not be the most efficient date parsing we can do, %% it should work fine for our purposes because all HTTP dates should %% be sent as RFC1123 dates in HTTP/1.1. -spec http_date(binary()) -> any(). http_date(Data) -> case rfc1123_date(Data) of {error, badarg} -> case rfc850_date(Data) of {error, badarg} -> case asctime_date(Data) of {error, badarg} -> {error, badarg}; HTTPDate -> HTTPDate end; HTTPDate -> HTTPDate end; HTTPDate -> HTTPDate end. %% @doc Parse an RFC1123 date. -spec rfc1123_date(binary()) -> any(). rfc1123_date(Data) -> wkday(Data, fun (<< ", ", Rest/binary >>, _WkDay) -> date1(Rest, fun (<< " ", Rest2/binary >>, Date) -> time(Rest2, fun (<< " GMT", Rest3/binary >>, Time) -> http_date_ret(Rest3, {Date, Time}); (_Any, _Time) -> {error, badarg} end); (_Any, _Date) -> {error, badarg} end); (_Any, _WkDay) -> {error, badarg} end). %% @doc Parse an RFC850 date. -spec rfc850_date(binary()) -> any(). %% From the RFC: %% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date %% which appears to be more than 50 years in the future is in fact %% in the past (this helps solve the "year 2000" problem). rfc850_date(Data) -> weekday(Data, fun (<< ", ", Rest/binary >>, _WeekDay) -> date2(Rest, fun (<< " ", Rest2/binary >>, Date) -> time(Rest2, fun (<< " GMT", Rest3/binary >>, Time) -> http_date_ret(Rest3, {Date, Time}); (_Any, _Time) -> {error, badarg} end); (_Any, _Date) -> {error, badarg} end); (_Any, _WeekDay) -> {error, badarg} end). %% @doc Parse an asctime date. -spec asctime_date(binary()) -> any(). asctime_date(Data) -> wkday(Data, fun (<< " ", Rest/binary >>, _WkDay) -> date3(Rest, fun (<< " ", Rest2/binary >>, PartialDate) -> time(Rest2, fun (<< " ", Rest3/binary >>, Time) -> asctime_year(Rest3, PartialDate, Time); (_Any, _Time) -> {error, badarg} end); (_Any, _PartialDate) -> {error, badarg} end); (_Any, _WkDay) -> {error, badarg1} end). -spec asctime_year(binary(), tuple(), tuple()) -> any(). asctime_year(<< Y1, Y2, Y3, Y4, Rest/binary >>, {Month, Day}, Time) when Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9, Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 -> Year = (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0), http_date_ret(Rest, {{Year, Month, Day}, Time}). -spec http_date_ret(binary(), tuple()) -> any(). http_date_ret(Data, DateTime = {Date, _Time}) -> whitespace(Data, fun (<<>>) -> case calendar:valid_date(Date) of true -> DateTime; false -> {error, badarg} end; (_Any) -> {error, badarg} end). %% We never use it, pretty much just checks the wkday is right. -spec wkday(binary(), fun()) -> any(). wkday(<< WkDay:3/binary, Rest/binary >>, Fun) when WkDay =:= <<"Mon">>; WkDay =:= <<"Tue">>; WkDay =:= <<"Wed">>; WkDay =:= <<"Thu">>; WkDay =:= <<"Fri">>; WkDay =:= <<"Sat">>; WkDay =:= <<"Sun">> -> Fun(Rest, WkDay); wkday(_Any, _Fun) -> {error, badarg}. %% We never use it, pretty much just checks the weekday is right. -spec weekday(binary(), fun()) -> any(). weekday(<< "Monday", Rest/binary >>, Fun) -> Fun(Rest, <<"Monday">>); weekday(<< "Tuesday", Rest/binary >>, Fun) -> Fun(Rest, <<"Tuesday">>); weekday(<< "Wednesday", Rest/binary >>, Fun) -> Fun(Rest, <<"Wednesday">>); weekday(<< "Thursday", Rest/binary >>, Fun) -> Fun(Rest, <<"Thursday">>); weekday(<< "Friday", Rest/binary >>, Fun) -> Fun(Rest, <<"Friday">>); weekday(<< "Saturday", Rest/binary >>, Fun) -> Fun(Rest, <<"Saturday">>); weekday(<< "Sunday", Rest/binary >>, Fun) -> Fun(Rest, <<"Sunday">>); weekday(_Any, _Fun) -> {error, badarg}. -spec date1(binary(), fun()) -> any(). date1(<< D1, D2, " ", M:3/binary, " ", Y1, Y2, Y3, Y4, Rest/binary >>, Fun) when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9, Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9, Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 -> case month(M) of {error, badarg} -> {error, badarg}; Month -> Fun(Rest, { (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0), Month, (D1 - $0) * 10 + (D2 - $0) }) end; date1(_Data, _Fun) -> {error, badarg}. -spec date2(binary(), fun()) -> any(). date2(<< D1, D2, "-", M:3/binary, "-", Y1, Y2, Rest/binary >>, Fun) when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9, Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9 -> case month(M) of {error, badarg} -> {error, badarg}; Month -> Year = (Y1 - $0) * 10 + (Y2 - $0), Year2 = case Year > 50 of true -> Year + 1900; false -> Year + 2000 end, Fun(Rest, { Year2, Month, (D1 - $0) * 10 + (D2 - $0) }) end; date2(_Data, _Fun) -> {error, badarg}. -spec date3(binary(), fun()) -> any(). date3(<< M:3/binary, " ", D1, D2, Rest/binary >>, Fun) when (D1 >= $0 andalso D1 =< $3) orelse D1 =:= $\s, D2 >= $0, D2 =< $9 -> case month(M) of {error, badarg} -> {error, badarg}; Month -> Day = case D1 of $\s -> D2 - $0; D1 -> (D1 - $0) * 10 + (D2 - $0) end, Fun(Rest, {Month, Day}) end; date3(_Data, _Fun) -> {error, badarg}. -spec month(<< _:24 >>) -> 1..12 | {error, badarg}. month(<<"Jan">>) -> 1; month(<<"Feb">>) -> 2; month(<<"Mar">>) -> 3; month(<<"Apr">>) -> 4; month(<<"May">>) -> 5; month(<<"Jun">>) -> 6; month(<<"Jul">>) -> 7; month(<<"Aug">>) -> 8; month(<<"Sep">>) -> 9; month(<<"Oct">>) -> 10; month(<<"Nov">>) -> 11; month(<<"Dec">>) -> 12; month(_Any) -> {error, badarg}. -spec time(binary(), fun()) -> any(). time(<< H1, H2, ":", M1, M2, ":", S1, S2, Rest/binary >>, Fun) when H1 >= $0, H1 =< $2, H2 >= $0, H2 =< $9, M1 >= $0, M1 =< $5, M2 >= $0, M2 =< $9, S1 >= $0, S1 =< $5, S2 >= $0, S2 =< $9 -> Hour = (H1 - $0) * 10 + (H2 - $0), case Hour < 24 of true -> Time = { Hour, (M1 - $0) * 10 + (M2 - $0), (S1 - $0) * 10 + (S2 - $0) }, Fun(Rest, Time); false -> {error, badarg} end. %% @doc Skip whitespace. -spec whitespace(binary(), fun()) -> any(). whitespace(<< C, Rest/binary >>, Fun) when C =:= $\s; C =:= $\t -> whitespace(Rest, Fun); whitespace(Data, Fun) -> Fun(Data). %% @doc Parse a list of digits as a non negative integer. -spec digits(binary()) -> non_neg_integer() | {error, badarg}. digits(Data) -> digits(Data, fun (Rest, I) -> whitespace(Rest, fun (<<>>) -> I; (_Rest2) -> {error, badarg} end) end). -spec digits(binary(), fun()) -> any(). digits(<< C, Rest/binary >>, Fun) when C >= $0, C =< $9 -> digits(Rest, Fun, C - $0); digits(_Data, _Fun) -> {error, badarg}. -spec digits(binary(), fun(), non_neg_integer()) -> any(). digits(<< C, Rest/binary >>, Fun, Acc) when C >= $0, C =< $9 -> digits(Rest, Fun, Acc * 10 + (C - $0)); digits(Data, Fun, Acc) -> Fun(Data, Acc). %% @doc Parse a list of case-insensitive alpha characters. %% %% Changes all characters to lowercase. -spec alpha(binary(), fun()) -> any(). alpha(Data, Fun) -> alpha(Data, Fun, <<>>). -spec alpha(binary(), fun(), binary()) -> any(). alpha(<<>>, Fun, Acc) -> Fun(<<>>, Acc); alpha(<< C, Rest/binary >>, Fun, Acc) when C >= $a andalso C =< $z; C >= $A andalso C =< $Z -> C2 = cowboy_bstr:char_to_lower(C), alpha(Rest, Fun, << Acc/binary, C2 >>); alpha(Data, Fun, Acc) -> Fun(Data, Acc). %% @doc Parse either a token or a quoted string. -spec word(binary(), fun()) -> any(). word(Data = << $", _/binary >>, Fun) -> quoted_string(Data, Fun); word(Data, Fun) -> token(Data, fun (_Rest, <<>>) -> {error, badarg}; (Rest, Token) -> Fun(Rest, Token) end). %% @doc Parse a case-insensitive token. %% %% Changes all characters to lowercase. -spec token_ci(binary(), fun()) -> any(). token_ci(Data, Fun) -> token(Data, Fun, ci, <<>>). %% @doc Parse a token. -spec token(binary(), fun()) -> any(). token(Data, Fun) -> token(Data, Fun, cs, <<>>). -spec token(binary(), fun(), ci | cs, binary()) -> any(). token(<<>>, Fun, _Case, Acc) -> Fun(<<>>, Acc); token(Data = << C, _Rest/binary >>, Fun, _Case, Acc) when C =:= $(; C =:= $); C =:= $<; C =:= $>; C =:= $@; C =:= $,; C =:= $;; C =:= $:; C =:= $\\; C =:= $"; C =:= $/; C =:= $[; C =:= $]; C =:= $?; C =:= $=; C =:= ${; C =:= $}; C =:= $\s; C =:= $\t; C < 32; C =:= 127 -> Fun(Data, Acc); token(<< C, Rest/binary >>, Fun, Case = ci, Acc) -> C2 = cowboy_bstr:char_to_lower(C), token(Rest, Fun, Case, << Acc/binary, C2 >>); token(<< C, Rest/binary >>, Fun, Case, Acc) -> token(Rest, Fun, Case, << Acc/binary, C >>). %% @doc Parse a quoted string. -spec quoted_string(binary(), fun()) -> any(). quoted_string(<< $", Rest/binary >>, Fun) -> quoted_string(Rest, Fun, <<>>). -spec quoted_string(binary(), fun(), binary()) -> any(). quoted_string(<<>>, _Fun, _Acc) -> {error, badarg}; quoted_string(<< $", Rest/binary >>, Fun, Acc) -> Fun(Rest, Acc); quoted_string(<< $\\, C, Rest/binary >>, Fun, Acc) -> quoted_string(Rest, Fun, << Acc/binary, C >>); quoted_string(<< C, Rest/binary >>, Fun, Acc) -> quoted_string(Rest, Fun, << Acc/binary, C >>). %% @doc Parse a quality value. -spec qvalue(binary(), fun()) -> any(). qvalue(<< $0, $., Rest/binary >>, Fun) -> qvalue(Rest, Fun, 0, 100); %% Some user agents use q=.x instead of q=0.x qvalue(<< $., Rest/binary >>, Fun) -> qvalue(Rest, Fun, 0, 100); qvalue(<< $0, Rest/binary >>, Fun) -> Fun(Rest, 0); qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) -> Fun(Rest, 1000); qvalue(<< $1, $., $0, $0, Rest/binary >>, Fun) -> Fun(Rest, 1000); qvalue(<< $1, $., $0, Rest/binary >>, Fun) -> Fun(Rest, 1000); qvalue(<< $1, Rest/binary >>, Fun) -> Fun(Rest, 1000); qvalue(_Data, _Fun) -> {error, badarg}. -spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any(). qvalue(Data, Fun, Q, 0) -> Fun(Data, Q); qvalue(<< C, Rest/binary >>, Fun, Q, M) when C >= $0, C =< $9 -> qvalue(Rest, Fun, Q + (C - $0) * M, M div 10); qvalue(Data, Fun, Q, _M) -> Fun(Data, Q). %% @doc Parse authorization value according rfc 2617. %% Only Basic authorization is supported so far. -spec authorization(binary(), binary()) -> {binary(), any()} | {error, badarg}. authorization(UserPass, Type = <<"basic">>) -> whitespace(UserPass, fun(D) -> authorization_basic_userid(base64:mime_decode(D), fun(Rest, Userid) -> authorization_basic_password(Rest, fun(Password) -> {Type, {Userid, Password}} end) end) end); authorization(String, Type) -> whitespace(String, fun(Rest) -> {Type, Rest} end). %% @doc Parse user credentials. -spec authorization_basic_userid(binary(), fun()) -> any(). authorization_basic_userid(Data, Fun) -> authorization_basic_userid(Data, Fun, <<>>). authorization_basic_userid(<<>>, _Fun, _Acc) -> {error, badarg}; authorization_basic_userid(<>, _Fun, Acc) when C < 32; C =:= 127; (C =:=$: andalso Acc =:= <<>>) -> {error, badarg}; authorization_basic_userid(<<$:, Rest/binary>>, Fun, Acc) -> Fun(Rest, Acc); authorization_basic_userid(<>, Fun, Acc) -> authorization_basic_userid(Rest, Fun, <>). -spec authorization_basic_password(binary(), fun()) -> any(). authorization_basic_password(Data, Fun) -> authorization_basic_password(Data, Fun, <<>>). authorization_basic_password(<<>>, _Fun, <<>>) -> {error, badarg}; authorization_basic_password(<>, _Fun, _Acc) when C < 32; C=:= 127 -> {error, badarg}; authorization_basic_password(<<>>, Fun, Acc) -> Fun(Acc); authorization_basic_password(<>, Fun, Acc) -> authorization_basic_password(Rest, Fun, <>). %% @doc Parse range header according rfc 2616. -spec range(binary()) -> {Unit, [Range]} | {error, badarg} when Unit :: binary(), Range :: {non_neg_integer(), non_neg_integer() | infinity} | neg_integer(). range(Data) -> token_ci(Data, fun range/2). range(Data, Token) -> whitespace(Data, fun(<<"=", Rest/binary>>) -> case list(Rest, fun range_beginning/2) of {error, badarg} -> {error, badarg}; Ranges -> {Token, Ranges} end; (_) -> {error, badarg} end). range_beginning(Data, Fun) -> range_digits(Data, suffix, fun(D, RangeBeginning) -> range_ending(D, Fun, RangeBeginning) end). range_ending(Data, Fun, RangeBeginning) -> whitespace(Data, fun(<<"-", R/binary>>) -> case RangeBeginning of suffix -> range_digits(R, fun(D, RangeEnding) -> Fun(D, -RangeEnding) end); _ -> range_digits(R, infinity, fun(D, RangeEnding) -> Fun(D, {RangeBeginning, RangeEnding}) end) end; (_) -> {error, badarg} end). -spec range_digits(binary(), fun()) -> any(). range_digits(Data, Fun) -> whitespace(Data, fun(D) -> digits(D, Fun) end). -spec range_digits(binary(), any(), fun()) -> any(). range_digits(Data, Default, Fun) -> whitespace(Data, fun(<< C, Rest/binary >>) when C >= $0, C =< $9 -> digits(Rest, Fun, C - $0); (_) -> Fun(Data, Default) end). %% @doc Parse a non empty list of tokens followed with optional parameters. -spec parameterized_tokens(binary()) -> any(). parameterized_tokens(Data) -> nonempty_list(Data, fun (D, Fun) -> token(D, fun (_Rest, <<>>) -> {error, badarg}; (Rest, Token) -> parameterized_tokens_params(Rest, fun (Rest2, Params) -> Fun(Rest2, {Token, Params}) end, []) end) end). -spec parameterized_tokens_params(binary(), fun(), [binary() | {binary(), binary()}]) -> any(). parameterized_tokens_params(Data, Fun, Acc) -> whitespace(Data, fun (<< $;, Rest/binary >>) -> parameterized_tokens_param(Rest, fun (Rest2, Param) -> parameterized_tokens_params(Rest2, Fun, [Param|Acc]) end); (Rest) -> Fun(Rest, lists:reverse(Acc)) end). -spec parameterized_tokens_param(binary(), fun()) -> any(). parameterized_tokens_param(Data, Fun) -> whitespace(Data, fun (Rest) -> token(Rest, fun (_Rest2, <<>>) -> {error, badarg}; (<< $=, Rest2/binary >>, Attr) -> word(Rest2, fun (Rest3, Value) -> Fun(Rest3, {Attr, Value}) end); (Rest2, Attr) -> Fun(Rest2, Attr) end) end). %% Decoding. %% @doc Decode a stream of chunks. -spec te_chunked(Bin, TransferState) -> more | {more, non_neg_integer(), Bin, TransferState} | {ok, Bin, Bin, TransferState} | {done, non_neg_integer(), Bin} | {error, badarg} when Bin::binary(), TransferState::{non_neg_integer(), non_neg_integer()}. te_chunked(<< "0\r\n\r\n", Rest/binary >>, {0, Streamed}) -> {done, Streamed, Rest}; te_chunked(Data, {0, Streamed}) -> %% @todo We are expecting an hex size, not a general token. token(Data, fun (<< "\r\n", Rest/binary >>, BinLen) -> Len = list_to_integer(binary_to_list(BinLen), 16), te_chunked(Rest, {Len, Streamed}); %% Chunk size shouldn't take too many bytes, %% don't try to stream forever. (Rest, _) when byte_size(Rest) < 16 -> more; (_, _) -> {error, badarg} end); te_chunked(Data, {ChunkRem, Streamed}) when byte_size(Data) >= ChunkRem + 2 -> << Chunk:ChunkRem/binary, "\r\n", Rest/binary >> = Data, {ok, Chunk, Rest, {0, Streamed + byte_size(Chunk)}}; te_chunked(Data, {ChunkRem, Streamed}) -> {more, ChunkRem + 2, Data, {ChunkRem, Streamed}}. %% @doc Decode an identity stream. -spec te_identity(Bin, TransferState) -> {more, non_neg_integer(), Bin, TransferState} | {done, Bin, non_neg_integer(), Bin} when Bin::binary(), TransferState::{non_neg_integer(), non_neg_integer()}. te_identity(Data, {Streamed, Total}) when Streamed + byte_size(Data) < Total -> Streamed2 = Streamed + byte_size(Data), {more, Total - Streamed2, Data, {Streamed2, Total}}; te_identity(Data, {Streamed, Total}) -> Size = Total - Streamed, << Data2:Size/binary, Rest/binary >> = Data, {done, Data2, Total, Rest}. %% @doc Decode an identity content. -spec ce_identity(binary()) -> {ok, binary()}. ce_identity(Data) -> {ok, Data}. %% Interpretation. %% @doc Convert a cookie name, value and options to its iodata form. %% @end %% %% Initially from Mochiweb: %% * Copyright 2007 Mochi Media, Inc. %% Initial binary implementation: %% * Copyright 2011 Thomas Burdick -spec cookie_to_iodata(iodata(), iodata(), cowboy_req:cookie_opts()) -> iodata(). cookie_to_iodata(Name, Value, Opts) -> case binary:match(iolist_to_binary(Name), [<<$=>>, <<$,>>, <<$;>>, <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of nomatch -> ok end, case binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>, <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of nomatch -> ok end, MaxAgeBin = case lists:keyfind(max_age, 1, Opts) of false -> <<>>; {_, 0} -> %% MSIE requires an Expires date in the past to delete a cookie. <<"; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0">>; {_, MaxAge} when is_integer(MaxAge), MaxAge > 0 -> UTC = calendar:universal_time(), Secs = calendar:datetime_to_gregorian_seconds(UTC), Expires = calendar:gregorian_seconds_to_datetime(Secs + MaxAge), [<<"; Expires=">>, cowboy_clock:rfc2109(Expires), <<"; Max-Age=">>, integer_to_list(MaxAge)] end, DomainBin = case lists:keyfind(domain, 1, Opts) of false -> <<>>; {_, Domain} -> [<<"; Domain=">>, Domain] end, PathBin = case lists:keyfind(path, 1, Opts) of false -> <<>>; {_, Path} -> [<<"; Path=">>, Path] end, SecureBin = case lists:keyfind(secure, 1, Opts) of false -> <<>>; {_, true} -> <<"; Secure">> end, HttpOnlyBin = case lists:keyfind(http_only, 1, Opts) of false -> <<>>; {_, true} -> <<"; HttpOnly">> end, [Name, <<"=">>, Value, <<"; Version=1">>, MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin]. %% @doc Decode a URL encoded binary. %% @equiv urldecode(Bin, crash) -spec urldecode(binary()) -> binary(). urldecode(Bin) when is_binary(Bin) -> urldecode(Bin, <<>>, crash). %% @doc Decode a URL encoded binary. %% The second argument specifies how to handle percent characters that are not %% followed by two valid hex characters. Use `skip' to ignore such errors, %% if `crash' is used the function will fail with the reason `badarg'. -spec urldecode(binary(), crash | skip) -> binary(). urldecode(Bin, OnError) when is_binary(Bin) -> urldecode(Bin, <<>>, OnError). -spec urldecode(binary(), binary(), crash | skip) -> binary(). urldecode(<<$%, H, L, Rest/binary>>, Acc, OnError) -> G = unhex(H), M = unhex(L), if G =:= error; M =:= error -> case OnError of skip -> ok; crash -> erlang:error(badarg) end, urldecode(<>, <>, OnError); true -> urldecode(Rest, <>, OnError) end; urldecode(<<$%, Rest/binary>>, Acc, OnError) -> case OnError of skip -> ok; crash -> erlang:error(badarg) end, urldecode(Rest, <>, OnError); urldecode(<<$+, Rest/binary>>, Acc, OnError) -> urldecode(Rest, <>, OnError); urldecode(<>, Acc, OnError) -> urldecode(Rest, <>, OnError); urldecode(<<>>, Acc, _OnError) -> Acc. -spec unhex(byte()) -> byte() | error. unhex(C) when C >= $0, C =< $9 -> C - $0; unhex(C) when C >= $A, C =< $F -> C - $A + 10; unhex(C) when C >= $a, C =< $f -> C - $a + 10; unhex(_) -> error. %% @doc URL encode a string binary. %% @equiv urlencode(Bin, []) -spec urlencode(binary()) -> binary(). urlencode(Bin) -> urlencode(Bin, []). %% @doc URL encode a string binary. %% The `noplus' option disables the default behaviour of quoting space %% characters, `\s', as `+'. The `upper' option overrides the default behaviour %% of writing hex numbers using lowecase letters to using uppercase letters %% instead. -spec urlencode(binary(), [noplus|upper]) -> binary(). urlencode(Bin, Opts) -> Plus = not lists:member(noplus, Opts), Upper = lists:member(upper, Opts), urlencode(Bin, <<>>, Plus, Upper). -spec urlencode(binary(), binary(), boolean(), boolean()) -> binary(). urlencode(<>, Acc, P=Plus, U=Upper) -> if C >= $0, C =< $9 -> urlencode(Rest, <>, P, U); C >= $A, C =< $Z -> urlencode(Rest, <>, P, U); C >= $a, C =< $z -> urlencode(Rest, <>, P, U); C =:= $.; C =:= $-; C =:= $~; C =:= $_ -> urlencode(Rest, <>, P, U); C =:= $ , Plus -> urlencode(Rest, <>, P, U); true -> H = C band 16#F0 bsr 4, L = C band 16#0F, H1 = if Upper -> tohexu(H); true -> tohexl(H) end, L1 = if Upper -> tohexu(L); true -> tohexl(L) end, urlencode(Rest, <>, P, U) end; urlencode(<<>>, Acc, _Plus, _Upper) -> Acc. -spec tohexu(byte()) -> byte(). tohexu(C) when C < 10 -> $0 + C; tohexu(C) when C < 17 -> $A + C - 10. -spec tohexl(byte()) -> byte(). tohexl(C) when C < 10 -> $0 + C; tohexl(C) when C < 17 -> $a + C - 10. -spec x_www_form_urlencoded(binary()) -> list({binary(), binary() | true}). x_www_form_urlencoded(<<>>) -> []; x_www_form_urlencoded(Qs) -> Tokens = binary:split(Qs, <<"&">>, [global, trim]), [case binary:split(Token, <<"=">>) of [Token] -> {urldecode(Token), true}; [Name, Value] -> {urldecode(Name), urldecode(Value)} end || Token <- Tokens]. %% Tests. -ifdef(TEST). nonempty_charset_list_test_() -> %% {Value, Result} Tests = [ {<<>>, {error, badarg}}, {<<"iso-8859-5, unicode-1-1;q=0.8">>, [ {<<"iso-8859-5">>, 1000}, {<<"unicode-1-1">>, 800} ]}, %% Some user agents send this invalid value for the Accept-Charset header {<<"ISO-8859-1;utf-8;q=0.7,*;q=0.7">>, [ {<<"iso-8859-1">>, 1000}, {<<"utf-8">>, 700}, {<<"*">>, 700} ]} ], [{V, fun() -> R = nonempty_list(V, fun conneg/2) end} || {V, R} <- Tests]. nonempty_language_range_list_test_() -> %% {Value, Result} Tests = [ {<<"da, en-gb;q=0.8, en;q=0.7">>, [ {<<"da">>, 1000}, {<<"en-gb">>, 800}, {<<"en">>, 700} ]}, {<<"en, en-US, en-cockney, i-cherokee, x-pig-latin">>, [ {<<"en">>, 1000}, {<<"en-us">>, 1000}, {<<"en-cockney">>, 1000}, {<<"i-cherokee">>, 1000}, {<<"x-pig-latin">>, 1000} ]} ], [{V, fun() -> R = nonempty_list(V, fun language_range/2) end} || {V, R} <- Tests]. nonempty_token_list_test_() -> %% {Value, Result} Tests = [ {<<>>, {error, badarg}}, {<<" ">>, {error, badarg}}, {<<" , ">>, {error, badarg}}, {<<",,,">>, {error, badarg}}, {<<"a b">>, {error, badarg}}, {<<"a , , , ">>, [<<"a">>]}, {<<" , , , a">>, [<<"a">>]}, {<<"a, , b">>, [<<"a">>, <<"b">>]}, {<<"close">>, [<<"close">>]}, {<<"keep-alive, upgrade">>, [<<"keep-alive">>, <<"upgrade">>]} ], [{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests]. cookie_list_test_() -> %% {Value, Result}. Tests = [ {<<"name=value; name2=value2">>, [ {<<"name">>, <<"value">>}, {<<"name2">>, <<"value2">>} ]}, {<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme">>, [ {<<"Customer">>, <<"WILE_E_COYOTE">>} ]}, {<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme; " "Part_Number=Rocket_Launcher_0001; $Path=/acme; " "Shipping=FedEx; $Path=/acme">>, [ {<<"Customer">>, <<"WILE_E_COYOTE">>}, {<<"Part_Number">>, <<"Rocket_Launcher_0001">>}, {<<"Shipping">>, <<"FedEx">>} ]}, %% Potential edge cases (initially from Mochiweb). {<<"foo=\\x">>, [{<<"foo">>, <<"\\x">>}]}, {<<"=">>, {error, badarg}}, {<<" foo ; bar ">>, {error, badarg}}, {<<"foo=;bar=">>, [{<<"foo">>, <<>>}, {<<"bar">>, <<>>}]}, {<<"foo=\\\";;bar ">>, {error, badarg}}, {<<"foo=\\\";;bar=good ">>, [{<<"foo">>, <<"\\\"">>}, {<<"bar">>, <<"good">>}]}, {<<"foo=\"\\\";bar">>, {error, badarg}}, {<<"">>, {error, badarg}}, {<<"foo=bar , baz=wibble ">>, [{<<"foo">>, <<"bar">>}, {<<"baz">>, <<"wibble">>}]} ], [{V, fun() -> R = cookie_list(V) end} || {V, R} <- Tests]. media_range_list_test_() -> %% {Tokens, Result} Tests = [ {<<"audio/*; q=0.2, audio/basic">>, [ {{<<"audio">>, <<"*">>, []}, 200, []}, {{<<"audio">>, <<"basic">>, []}, 1000, []} ]}, {<<"text/plain; q=0.5, text/html, " "text/x-dvi; q=0.8, text/x-c">>, [ {{<<"text">>, <<"plain">>, []}, 500, []}, {{<<"text">>, <<"html">>, []}, 1000, []}, {{<<"text">>, <<"x-dvi">>, []}, 800, []}, {{<<"text">>, <<"x-c">>, []}, 1000, []} ]}, {<<"text/*, text/html, text/html;level=1, */*">>, [ {{<<"text">>, <<"*">>, []}, 1000, []}, {{<<"text">>, <<"html">>, []}, 1000, []}, {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []}, {{<<"*">>, <<"*">>, []}, 1000, []} ]}, {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, " "text/html;level=2;q=0.4, */*;q=0.5">>, [ {{<<"text">>, <<"*">>, []}, 300, []}, {{<<"text">>, <<"html">>, []}, 700, []}, {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []}, {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []}, {{<<"*">>, <<"*">>, []}, 500, []} ]}, {<<"text/html;level=1;quoted=\"hi hi hi\";" "q=0.123;standalone;complex=gits, text/plain">>, [ {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123, [<<"standalone">>, {<<"complex">>, <<"gits">>}]}, {{<<"text">>, <<"plain">>, []}, 1000, []} ]}, {<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [ {{<<"text">>, <<"html">>, []}, 1000, []}, {{<<"image">>, <<"gif">>, []}, 1000, []}, {{<<"image">>, <<"jpeg">>, []}, 1000, []}, {{<<"*">>, <<"*">>, []}, 200, []}, {{<<"*">>, <<"*">>, []}, 200, []} ]} ], [{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests]. entity_tag_match_test_() -> %% {Tokens, Result} Tests = [ {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]}, {<<"\"xyzzy\", W/\"r2d2xxxx\", \"c3piozzzz\"">>, [{strong, <<"xyzzy">>}, {weak, <<"r2d2xxxx">>}, {strong, <<"c3piozzzz">>}]}, {<<"*">>, '*'} ], [{V, fun() -> R = entity_tag_match(V) end} || {V, R} <- Tests]. http_date_test_() -> %% {Tokens, Result} Tests = [ {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}, {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}, {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}} ], [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests]. rfc1123_date_test_() -> %% {Tokens, Result} Tests = [ {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}} ], [{V, fun() -> R = rfc1123_date(V) end} || {V, R} <- Tests]. rfc850_date_test_() -> %% {Tokens, Result} Tests = [ {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}} ], [{V, fun() -> R = rfc850_date(V) end} || {V, R} <- Tests]. asctime_date_test_() -> %% {Tokens, Result} Tests = [ {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}} ], [{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests]. content_type_test_() -> %% {ContentType, Result} Tests = [ {<<"text/plain; charset=iso-8859-4">>, {<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}}, {<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>, {<<"multipart">>, <<"form-data">>, [ {<<"boundary">>, <<"MultipartIsUgly">>} ]}}, {<<"foo/bar; one=FirstParam; two=SecondParam">>, {<<"foo">>, <<"bar">>, [ {<<"one">>, <<"FirstParam">>}, {<<"two">>, <<"SecondParam">>} ]}} ], [{V, fun () -> R = content_type(V) end} || {V, R} <- Tests]. parameterized_tokens_test_() -> %% {ParameterizedTokens, Result} Tests = [ {<<"foo">>, [{<<"foo">>, []}]}, {<<"bar; baz=2">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}]}]}, {<<"bar; baz=2;bat">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}, <<"bat">>]}]}, {<<"bar; baz=2;bat=\"z=1,2;3\"">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}, {<<"bat">>, <<"z=1,2;3">>}]}]}, {<<"foo, bar; baz=2">>, [{<<"foo">>, []}, {<<"bar">>, [{<<"baz">>, <<"2">>}]}]} ], [{V, fun () -> R = parameterized_tokens(V) end} || {V, R} <- Tests]. digits_test_() -> %% {Digits, Result} Tests = [ {<<"42 ">>, 42}, {<<"69\t">>, 69}, {<<"1337">>, 1337} ], [{V, fun() -> R = digits(V) end} || {V, R} <- Tests]. cookie_to_iodata_test_() -> %% {Name, Value, Opts, Result} Tests = [ {<<"Customer">>, <<"WILE_E_COYOTE">>, [{http_only, true}, {domain, <<"acme.com">>}], <<"Customer=WILE_E_COYOTE; Version=1; " "Domain=acme.com; HttpOnly">>}, {<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}], <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>}, {<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}, {badoption, <<"negatory">>}], <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>} ], [{R, fun() -> R = iolist_to_binary(cookie_to_iodata(N, V, O)) end} || {N, V, O, R} <- Tests]. cookie_to_iodata_max_age_test() -> F = fun(N, V, O) -> binary:split(iolist_to_binary( cookie_to_iodata(N, V, O)), <<";">>, [global]) end, [<<"Customer=WILE_E_COYOTE">>, <<" Version=1">>, <<" Expires=", _/binary>>, <<" Max-Age=111">>, <<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, 111}, {secure, true}]), case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, -111}]) of {'EXIT', {{case_clause, {max_age, -111}}, _}} -> ok end, [<<"Customer=WILE_E_COYOTE">>, <<" Version=1">>, <<" Expires=", _/binary>>, <<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, 86417}]), ok. cookie_to_iodata_failures_test_() -> F = fun(N, V) -> try cookie_to_iodata(N, V, []) of _ -> false catch _:_ -> true end end, Tests = [ {<<"Na=me">>, <<"Value">>}, {<<"Name;">>, <<"Value">>}, {<<"\r\name">>, <<"Value">>}, {<<"Name">>, <<"Value;">>}, {<<"Name">>, <<"\value">>} ], [{iolist_to_binary(io_lib:format("{~p, ~p} failure", [N, V])), fun() -> true = F(N, V) end} || {N, V} <- Tests]. x_www_form_urlencoded_test_() -> %% {Qs, Result} Tests = [ {<<"">>, []}, {<<"a=b">>, [{<<"a">>, <<"b">>}]}, {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]}, {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]}, {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}]}, {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]}, {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]} ], [{Qs, fun() -> R = x_www_form_urlencoded(Qs) end} || {Qs, R} <- Tests]. urldecode_test_() -> F = fun(Qs, O) -> try urldecode(Qs, O) of R -> {ok, R} catch _:E -> {error, E} end end, Tests = [ {<<"%20">>, crash, {ok, <<" ">>}}, {<<"+">>, crash, {ok, <<" ">>}}, {<<"%00">>, crash, {ok, <<0>>}}, {<<"%fF">>, crash, {ok, <<255>>}}, {<<"123">>, crash, {ok, <<"123">>}}, {<<"%i5">>, skip, {ok, <<"%i5">>}}, {<<"%5">>, skip, {ok, <<"%5">>}}, {<<"%i5">>, crash, {error, badarg}}, {<<"%5">>, crash, {error, badarg}} ], [{Qs, fun() -> R = F(Qs,O) end} || {Qs, O, R} <- Tests]. urlencode_test_() -> Tests = [ {<<255,0>>, [], <<"%ff%00">>}, {<<255,0>>, [upper], <<"%FF%00">>}, {<<" ">>, [], <<"+">>}, {<<" ">>, [noplus], <<"%20">>}, {<<"aBc">>, [], <<"aBc">>}, {<<".-~_">>, [], <<".-~_">>} ], Tests2 = [{<<255, " ">>,<<"%ff+">>}], [{V, fun() -> R = urlencode(V, O) end} || {V, O, R} <- Tests] ++ [{V, fun() -> R = urlencode(V) end} || {V, R} <- Tests2]. http_authorization_test_() -> Tests = [ {<<"basic">>, <<"QWxsYWRpbjpvcGVuIHNlc2FtZQ==">>, {<<"basic">>, {<<"Alladin">>, <<"open sesame">>}}}, {<<"basic">>, <<"dXNlcm5hbWUK">>, {error, badarg}}, {<<"basic">>, <<"_[]@#$%^&*()-AA==">>, {error, badarg}}, {<<"basic">>, <<"dXNlcjpwYXNzCA==">>, {error, badarg}}, {<<"bearer">>, <<" some_secret_key">>, {<<"bearer">>,<<"some_secret_key">>}} ], [{V, fun() -> R = authorization(V,T) end} || {T, V, R} <- Tests]. http_range_test_() -> Tests = [ {<<"bytes=1-20">>, {<<"bytes">>, [{1, 20}]}}, {<<"bytes=-100">>, {<<"bytes">>, [-100]}}, {<<"bytes=1-">>, {<<"bytes">>, [{1, infinity}]}}, {<<"bytes=1-20,30-40,50-">>, {<<"bytes">>, [{1, 20}, {30, 40}, {50, infinity}]}}, {<<"bytes = 1 - 20 , 50 - , - 300 ">>, {<<"bytes">>, [{1, 20}, {50, infinity}, -300]}}, {<<"bytes=1-20,-500,30-40">>, {<<"bytes">>, [{1, 20}, -500, {30, 40}]}}, {<<"test=1-20,-500,30-40">>, {<<"test">>, [{1, 20}, -500, {30, 40}]}}, {<<"bytes=-">>, {error, badarg}}, {<<"bytes=-30,-">>, {error, badarg}} ], [fun() -> R = range(V) end ||{V, R} <- Tests]. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_http_handler.erl000066400000000000000000000045251223335133400226330ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Behaviour for short-lived HTTP handlers. %% %% init/3 allows you to initialize a state for all subsequent %% callbacks, and indicate to Cowboy whether you accept to handle the %% request or want to shutdown without handling it, in which case the %% handle/2 call will simply be skipped. %% %% handle/2 allows you to handle the request. It receives the %% state previously defined. %% %% terminate/3 allows you to clean up. It receives the %% termination reason and the state previously defined. %% %% There is no required operation to perform in any of these callbacks %% other than returning the proper values. Make sure you always return %% the last modified Req so that Cowboy has the up to date information %% about the request. -module(cowboy_http_handler). -type opts() :: any(). -type state() :: any(). -type terminate_reason() :: {normal, shutdown} | {normal, timeout} %% Only occurs in loop handlers. | {error, closed} %% Only occurs in loop handlers. | {error, overflow} %% Only occurs in loop handlers. | {error, atom()}. -callback init({atom(), http}, Req, opts()) -> {ok, Req, state()} | {loop, Req, state()} | {loop, Req, state(), hibernate} | {loop, Req, state(), timeout()} | {loop, Req, state(), timeout(), hibernate} | {shutdown, Req, state()} | {upgrade, protocol, module()} | {upgrade, protocol, module(), Req, opts()} when Req::cowboy_req:req(). -callback handle(Req, State) -> {ok, Req, State} when Req::cowboy_req:req(), State::state(). -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. erlang-cowboy-0.8.6+dfsg1/src/cowboy_loop_handler.erl000066400000000000000000000052111223335133400226160ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Behaviour for long-lived HTTP handlers. %% %% init/3 allows you to initialize a state for all subsequent %% callbacks, and indicate to Cowboy whether you accept to handle the %% request or want to shutdown without handling it, in which case the %% receive loop and info/3 calls will simply be skipped. %% %% info/3 allows you to handle the messages this process will %% receive. It receives the message and the state previously defined. %% It can decide to stop the receive loop or continue receiving. %% %% terminate/3 allows you to clean up. It receives the %% termination reason and the state previously defined. %% %% There is no required operation to perform in any of these callbacks %% other than returning the proper values. Make sure you always return %% the last modified Req so that Cowboy has the up to date information %% about the request. %% %% It is recommended to use hibernate if this process is not going to %% receive a lot of messages. It is also recommended to use a timeout %% value so that the connection gets closed after a long period of %% inactivity. -module(cowboy_loop_handler). -type opts() :: any(). -type state() :: any(). -type terminate_reason() :: {normal, shutdown} | {normal, timeout} | {error, closed} | {error, overflow} | {error, atom()}. -callback init({atom(), http}, Req, opts()) -> {ok, Req, state()} | {loop, Req, state()} | {loop, Req, state(), hibernate} | {loop, Req, state(), timeout()} | {loop, Req, state(), timeout(), hibernate} | {shutdown, Req, state()} | {upgrade, protocol, module()} | {upgrade, protocol, module(), Req, opts()} when Req::cowboy_req:req(). -callback info(any(), Req, State) -> {ok, Req, State} | {loop, Req, State} | {loop, Req, State, hibernate} when Req::cowboy_req:req(), State::state(). -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. erlang-cowboy-0.8.6+dfsg1/src/cowboy_middleware.erl000066400000000000000000000030021223335133400222610ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Behaviour for middlewares. %% %% Only one function needs to be implemented, execute/2. %% It receives the Req and the environment and returns them %% optionally modified. It can decide to stop the processing with %% or without an error. It is also possible to hibernate the process %% if needed. %% %% A middleware can perform any operation. Make sure you always return %% the last modified Req so that Cowboy has the up to date information %% about the request. -module(cowboy_middleware). -type env() :: [{atom(), any()}]. -export_type([env/0]). -callback execute(Req, Env) -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {halt, Req} | {error, cowboy:http_status(), Req} when Req::cowboy_req:req(), Env::env(). erlang-cowboy-0.8.6+dfsg1/src/cowboy_multipart.erl000066400000000000000000000263441223335133400222030ustar00rootroot00000000000000%% Copyright (c) 2011, Anthony Ramine %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Multipart parser. -module(cowboy_multipart). -export([parser/1]). -export([content_disposition/1]). -type part_parser() :: parser(more(part_result())). -type parser(T) :: fun((binary()) -> T). -type more(T) :: T | {more, parser(T)}. -type part_result() :: headers() | eof. -type headers() :: {headers, http_headers(), body_cont()}. -type http_headers() :: [{binary(), binary()}]. -type body_cont() :: cont(more(body_result())). -type cont(T) :: fun(() -> T). -type body_result() :: {body, binary(), body_cont()} | end_of_part(). -type end_of_part() :: {end_of_part, cont(more(part_result()))}. -type disposition() :: {binary(), [{binary(), binary()}]}. %% API. %% @doc Return a multipart parser for the given boundary. -spec parser(binary()) -> part_parser(). parser(Boundary) when is_binary(Boundary) -> fun (Bin) when is_binary(Bin) -> parse(Bin, Boundary) end. %% @doc Parse a content disposition. %% @todo Parse the MIME header instead of the HTTP one. -spec content_disposition(binary()) -> disposition(). content_disposition(Data) -> cowboy_http:token_ci(Data, fun (_Rest, <<>>) -> {error, badarg}; (Rest, Disposition) -> cowboy_http:params(Rest, fun (<<>>, Params) -> {Disposition, Params}; (_Rest2, _) -> {error, badarg} end) end). %% Internal. %% @doc Entry point of the multipart parser, skips over the preamble if any. -spec parse(binary(), binary()) -> more(part_result()). parse(Bin, Boundary) when byte_size(Bin) >= byte_size(Boundary) + 2 -> BoundarySize = byte_size(Boundary), Pattern = pattern(Boundary), case Bin of <<"--", Boundary:BoundarySize/binary, Rest/binary>> -> % Data starts with initial boundary, skip preamble parsing. parse_boundary_tail(Rest, Pattern); _ -> % Parse preamble. skip(Bin, Pattern) end; parse(Bin, Boundary) -> % Not enough data to know if the data begins with a boundary. more(Bin, fun (NewBin) -> parse(NewBin, Boundary) end). -type pattern() :: {binary:cp(), non_neg_integer()}. -type patterns() :: {pattern(), pattern()}. %% @doc Return two compiled binary patterns with their sizes in bytes. %% The boundary pattern is the boundary prepended with "\r\n--". %% The boundary suffix pattern matches all prefixes of the boundary. -spec pattern(binary()) -> patterns(). pattern(Boundary) -> MatchPattern = <<"\r\n--", Boundary/binary>>, MatchPrefixes = prefixes(MatchPattern), {{binary:compile_pattern(MatchPattern), byte_size(MatchPattern)}, {binary:compile_pattern(MatchPrefixes), byte_size(MatchPattern)}}. %% @doc Return all prefixes of a binary string. %% The list of prefixes includes the full string. -spec prefixes(binary()) -> [binary()]. prefixes(<>) -> prefixes(Rest, <>). -spec prefixes(binary(), binary()) -> [binary()]. prefixes(<>, Acc) -> [Acc|prefixes(Rest, <>)]; prefixes(<<>>, Acc) -> [Acc]. %% @doc Test if a boundary is a possble suffix. %% The patterns are expected to have been returned from `pattern/1'. -spec suffix_match(binary(), patterns()) -> nomatch | {integer(), integer()}. suffix_match(Bin, {_Boundary, {Pat, Len}}) -> Size = byte_size(Bin), suffix_match(Bin, Pat, Size, max(-Size, -Len)). -spec suffix_match(binary(), binary:cp(), non_neg_integer(), 0|neg_integer()) -> nomatch | {integer(), integer()}. suffix_match(_Bin, _Pat, _Size, _Match=0) -> nomatch; suffix_match(Bin, Pat, Size, Match) when Match < 0 -> case binary:match(Bin, Pat, [{scope, {Size, Match}}]) of {Pos, Len}=Part when Pos + Len =:= Size -> Part; {_, Len} -> suffix_match(Bin, Pat, Size, Match + Len); nomatch -> nomatch end. %% @doc Parse remaining characters of a line beginning with the boundary. %% If followed by "--", eof is returned and parsing is finished. -spec parse_boundary_tail(binary(), patterns()) -> more(part_result()). parse_boundary_tail(Bin, Pattern) when byte_size(Bin) >= 2 -> case Bin of <<"--", _Rest/binary>> -> % Boundary is followed by "--", end parsing. eof; _ -> % No dash after boundary, proceed with unknown chars and lwsp % removal. parse_boundary_eol(Bin, Pattern) end; parse_boundary_tail(Bin, Pattern) -> % Boundary may be followed by "--", need more data. more(Bin, fun (NewBin) -> parse_boundary_tail(NewBin, Pattern) end). %% @doc Skip whitespace and unknown chars until CRLF. -spec parse_boundary_eol(binary(), patterns()) -> more(part_result()). parse_boundary_eol(Bin, Pattern) -> case binary:match(Bin, <<"\r\n">>) of {CrlfStart, _Length} -> % End of line found, remove optional whitespace. <<_:CrlfStart/binary, Rest/binary>> = Bin, Fun = fun (Rest2) -> parse_boundary_crlf(Rest2, Pattern) end, cowboy_http:whitespace(Rest, Fun); nomatch -> % CRLF not found in the given binary. RestStart = max(byte_size(Bin) - 1, 0), <<_:RestStart/binary, Rest/binary>> = Bin, more(Rest, fun (NewBin) -> parse_boundary_eol(NewBin, Pattern) end) end. -spec parse_boundary_crlf(binary(), patterns()) -> more(part_result()). parse_boundary_crlf(<<"\r\n", Rest/binary>>, Pattern) -> % The binary is at least 2 bytes long as this function is only called by % parse_boundary_eol/3 when CRLF has been found so a more tuple will never % be returned from here. parse_headers(Rest, Pattern); parse_boundary_crlf(Bin, Pattern) -> % Unspecified behaviour here: RFC 2046 doesn't say what to do when LWSP is % not followed directly by a new line. In this implementation it is % considered part of the boundary so EOL needs to be searched again. parse_boundary_eol(Bin, Pattern). -spec parse_headers(binary(), patterns()) -> more(part_result()). parse_headers(Bin, Pattern) -> parse_headers(Bin, Pattern, []). -spec parse_headers(binary(), patterns(), http_headers()) -> more(part_result()). parse_headers(Bin, Pattern, Acc) -> case erlang:decode_packet(httph_bin, Bin, []) of {ok, {http_header, _, Name, _, Value}, Rest} -> Name2 = case is_atom(Name) of true -> cowboy_bstr:to_lower(atom_to_binary(Name, latin1)); false -> cowboy_bstr:to_lower(Name) end, parse_headers(Rest, Pattern, [{Name2, Value} | Acc]); {ok, http_eoh, Rest} -> Headers = lists:reverse(Acc), {headers, Headers, fun () -> parse_body(Rest, Pattern) end}; {ok, {http_error, _}, _} -> % Skip malformed parts. skip(Bin, Pattern); {more, _} -> more(Bin, fun (NewBin) -> parse_headers(NewBin, Pattern, Acc) end) end. -spec parse_body(binary(), patterns()) -> more(body_result()). parse_body(Bin, Pattern = {{P, PSize}, _}) when byte_size(Bin) >= PSize -> case binary:match(Bin, P) of {0, _Length} -> <<_:PSize/binary, Rest/binary>> = Bin, end_of_part(Rest, Pattern); {BoundaryStart, _Length} -> % Boundary found, this is the latest partial body that will be % returned for this part. <> = Bin, FResult = end_of_part(Rest, Pattern), {body, PBody, fun () -> FResult end}; nomatch -> case suffix_match(Bin, Pattern) of nomatch -> %% Prefix of boundary not found at end of input. it's %% safe to return the whole binary. Saves copying of %% next input onto tail of current input binary. {body, Bin, fun () -> parse_body(<<>>, Pattern) end}; {BoundaryStart, Len} -> PBody = binary:part(Bin, 0, BoundaryStart), Rest = binary:part(Bin, BoundaryStart, Len), {body, PBody, fun () -> parse_body(Rest, Pattern) end} end end; parse_body(Bin, Pattern) -> more(Bin, fun (NewBin) -> parse_body(NewBin, Pattern) end). -spec end_of_part(binary(), patterns()) -> end_of_part(). end_of_part(Bin, Pattern) -> {end_of_part, fun () -> parse_boundary_tail(Bin, Pattern) end}. -spec skip(binary(), patterns()) -> more(part_result()). skip(Bin, Pattern = {{P, PSize}, _}) -> case binary:match(Bin, P) of {BoundaryStart, _Length} -> % Boundary found, proceed with parsing of the next part. RestStart = BoundaryStart + PSize, <<_:RestStart/binary, Rest/binary>> = Bin, parse_boundary_tail(Rest, Pattern); nomatch -> % Boundary not found, need more data. RestStart = max(byte_size(Bin) - PSize + 1, 0), <<_:RestStart/binary, Rest/binary>> = Bin, more(Rest, fun (NewBin) -> skip(NewBin, Pattern) end) end. -spec more(binary(), parser(T)) -> {more, parser(T)}. more(<<>>, F) -> {more, F}; more(Bin, InnerF) -> F = fun (NewData) when is_binary(NewData) -> InnerF(<>) end, {more, F}. %% Tests. -ifdef(TEST). multipart_test_() -> %% {Body, Result} Tests = [ {<<"--boundary--">>, []}, {<<"preamble\r\n--boundary--">>, []}, {<<"--boundary--\r\nepilogue">>, []}, {<<"\r\n--boundary\r\nA:b\r\nC:d\r\n\r\n\r\n--boundary--">>, [{[{<<"a">>, <<"b">>}, {<<"c">>, <<"d">>}], <<>>}]}, { << "--boundary\r\nX-Name:answer\r\n\r\n42" "\r\n--boundary\r\nServer:Cowboy\r\n\r\nIt rocks!\r\n" "\r\n--boundary--" >>, [ {[{<<"x-name">>, <<"answer">>}], <<"42">>}, {[{<<"server">>, <<"Cowboy">>}], <<"It rocks!\r\n">>} ] } ], [{title(V), fun () -> R = acc_multipart(V) end} || {V, R} <- Tests]. acc_multipart(V) -> acc_multipart((parser(<<"boundary">>))(V), []). acc_multipart({headers, Headers, Cont}, Acc) -> acc_multipart(Cont(), [{Headers, []}|Acc]); acc_multipart({body, Body, Cont}, [{Headers, BodyAcc}|Acc]) -> acc_multipart(Cont(), [{Headers, [Body|BodyAcc]}|Acc]); acc_multipart({end_of_part, Cont}, [{Headers, BodyAcc}|Acc]) -> Body = list_to_binary(lists:reverse(BodyAcc)), acc_multipart(Cont(), [{Headers, Body}|Acc]); acc_multipart(eof, Acc) -> lists:reverse(Acc). content_disposition_test_() -> %% {Disposition, Result} Tests = [ {<<"form-data; name=id">>, {<<"form-data">>, [{<<"name">>, <<"id">>}]}}, {<<"inline">>, {<<"inline">>, []}}, {<<"attachment; \tfilename=brackets-slides.pdf">>, {<<"attachment">>, [{<<"filename">>, <<"brackets-slides.pdf">>}]}} ], [{title(V), fun () -> R = content_disposition(V) end} || {V, R} <- Tests]. title(Bin) -> Title = lists:foldl( fun ({T, R}, V) -> re:replace(V, T, R, [global]) end, Bin, [{"\t", "\\\\t"}, {"\r", "\\\\r"}, {"\n", "\\\\n"}] ), iolist_to_binary(Title). suffix_test_() -> Tests = [ {nomatch, <<>>, <<"ABC">>}, {{0, 1}, <<"\r">>, <<"ABC">>}, {{0, 2}, <<"\r\n">>, <<"ABC">>}, {{0, 4}, <<"\r\n--">>, <<"ABC">>}, {{0, 5}, <<"\r\n--A">>, <<"ABC">>}, {{0, 6}, <<"\r\n--AB">>, <<"ABC">>}, {{0, 7}, <<"\r\n--ABC">>, <<"ABC">>}, {nomatch, <<"\r\n--AB1">>, <<"ABC">>}, {{1, 1}, <<"1\r">>, <<"ABC">>}, {{2, 2}, <<"12\r\n">>, <<"ABC">>}, {{3, 4}, <<"123\r\n--">>, <<"ABC">>} ], [fun() -> Part = suffix_match(Packet, pattern(Boundary)) end || {Part, Packet, Boundary} <- Tests]. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_protocol.erl000066400000000000000000000543341223335133400220230ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% Copyright (c) 2011, Anthony Ramine %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc HTTP protocol handler. %% %% The available options are: %%
%%
compress
Whether to automatically compress the response %% body when the conditions are met. Disabled by default.
%%
env
The environment passed and optionally modified %% by middlewares.
%%
max_empty_lines
Max number of empty lines before a request. %% Defaults to 5.
%%
max_header_name_length
Max length allowed for header names. %% Defaults to 64.
%%
max_header_value_length
Max length allowed for header values. %% Defaults to 4096.
%%
max_headers
Max number of headers allowed. %% Defaults to 100.
%%
max_keepalive
Max number of requests allowed in a single %% keep-alive session. Defaults to 100.
%%
max_request_line_length
Max length allowed for the request %% line. Defaults to 4096.
%%
middlewares
The list of middlewares to execute when a %% request is received.
%%
onrequest
Optional fun that allows Req interaction before %% any dispatching is done. Host info, path info and bindings are thus %% not available at this point.
%%
onresponse
Optional fun that allows replacing a response %% sent by the application.
%%
timeout
Time in milliseconds a client has to send the %% full request line and headers. Defaults to 5000 milliseconds.
%%
%% %% Note that there is no need to monitor these processes when using Cowboy as %% an application as it already supervises them under the listener supervisor. -module(cowboy_protocol). %% API. -export([start_link/4]). %% Internal. -export([init/4]). -export([parse_request/3]). -export([parse_host/2]). -export([resume/6]). -type opts() :: [{compress, boolean()} | {env, cowboy_middleware:env()} | {max_empty_lines, non_neg_integer()} | {max_header_name_length, non_neg_integer()} | {max_header_value_length, non_neg_integer()} | {max_headers, non_neg_integer()} | {max_keepalive, non_neg_integer()} | {max_request_line_length, non_neg_integer()} | {middlewares, [module()]} | {onrequest, cowboy:onrequest_fun()} | {onresponse, cowboy:onresponse_fun()} | {timeout, timeout()}]. -export_type([opts/0]). -record(state, { socket :: inet:socket(), transport :: module(), middlewares :: [module()], compress :: boolean(), env :: cowboy_middleware:env(), onrequest :: undefined | cowboy:onrequest_fun(), onresponse = undefined :: undefined | cowboy:onresponse_fun(), max_empty_lines :: non_neg_integer(), req_keepalive = 1 :: non_neg_integer(), max_keepalive :: non_neg_integer(), max_request_line_length :: non_neg_integer(), max_header_name_length :: non_neg_integer(), max_header_value_length :: non_neg_integer(), max_headers :: non_neg_integer(), timeout :: timeout(), until :: non_neg_integer() | infinity }). %% API. %% @doc Start an HTTP protocol process. -spec start_link(ranch:ref(), inet:socket(), module(), opts()) -> {ok, pid()}. start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. %% Internal. %% @doc Faster alternative to proplists:get_value/3. %% @private get_value(Key, Opts, Default) -> case lists:keyfind(Key, 1, Opts) of {_, Value} -> Value; _ -> Default end. %% @private -spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. init(Ref, Socket, Transport, Opts) -> Compress = get_value(compress, Opts, false), MaxEmptyLines = get_value(max_empty_lines, Opts, 5), MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64), MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096), MaxHeaders = get_value(max_headers, Opts, 100), MaxKeepalive = get_value(max_keepalive, Opts, 100), MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096), Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]), Env = [{listener, Ref}|get_value(env, Opts, [])], OnRequest = get_value(onrequest, Opts, undefined), OnResponse = get_value(onresponse, Opts, undefined), Timeout = get_value(timeout, Opts, 5000), ok = ranch:accept_ack(Ref), wait_request(<<>>, #state{socket=Socket, transport=Transport, middlewares=Middlewares, compress=Compress, env=Env, max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive, max_request_line_length=MaxRequestLineLength, max_header_name_length=MaxHeaderNameLength, max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders, onrequest=OnRequest, onresponse=OnResponse, timeout=Timeout, until=until(Timeout)}, 0). -spec until(timeout()) -> non_neg_integer() | infinity. until(infinity) -> infinity; until(Timeout) -> {Me, S, Mi} = os:timestamp(), Me * 1000000000 + S * 1000 + Mi div 1000 + Timeout. %% Request parsing. %% %% The next set of functions is the request parsing code. All of it %% runs using a single binary match context. This optimization ends %% right after the header parsing is finished and the code becomes %% more interesting past that point. -spec recv(inet:socket(), module(), non_neg_integer() | infinity) -> {ok, binary()} | {error, closed | timeout | atom()}. recv(Socket, Transport, infinity) -> Transport:recv(Socket, 0, infinity); recv(Socket, Transport, Until) -> {Me, S, Mi} = os:timestamp(), Now = Me * 1000000000 + S * 1000 + Mi div 1000, Timeout = Until - Now, if Timeout < 0 -> {error, timeout}; true -> Transport:recv(Socket, 0, Timeout) end. -spec wait_request(binary(), #state{}, non_neg_integer()) -> ok. wait_request(Buffer, State=#state{socket=Socket, transport=Transport, until=Until}, ReqEmpty) -> case recv(Socket, Transport, Until) of {ok, Data} -> parse_request(<< Buffer/binary, Data/binary >>, State, ReqEmpty); {error, _} -> terminate(State) end. %% @private -spec parse_request(binary(), #state{}, non_neg_integer()) -> ok. %% Empty lines must be using \r\n. parse_request(<< $\n, _/binary >>, State, _) -> error_terminate(400, State); %% We limit the length of the Request-line to MaxLength to avoid endlessly %% reading from the socket and eventually crashing. parse_request(Buffer, State=#state{max_request_line_length=MaxLength, max_empty_lines=MaxEmpty}, ReqEmpty) -> case match_eol(Buffer, 0) of nomatch when byte_size(Buffer) > MaxLength -> error_terminate(414, State); nomatch -> wait_request(Buffer, State, ReqEmpty); 1 when ReqEmpty =:= MaxEmpty -> error_terminate(400, State); 1 -> << _:16, Rest/binary >> = Buffer, parse_request(Rest, State, ReqEmpty + 1); _ -> parse_method(Buffer, State, <<>>) end. match_eol(<< $\n, _/bits >>, N) -> N; match_eol(<< _, Rest/bits >>, N) -> match_eol(Rest, N + 1); match_eol(_, _) -> nomatch. parse_method(<< C, Rest/bits >>, State, SoFar) -> case C of $\r -> error_terminate(400, State); $\s -> parse_uri(Rest, State, SoFar); _ -> parse_method(Rest, State, << SoFar/binary, C >>) end. parse_uri(<< $\r, _/bits >>, State, _) -> error_terminate(400, State); parse_uri(<< "* ", Rest/bits >>, State, Method) -> parse_version(Rest, State, Method, <<"*">>, <<>>); parse_uri(<< "http://", Rest/bits >>, State, Method) -> parse_uri_skip_host(Rest, State, Method); parse_uri(<< "https://", Rest/bits >>, State, Method) -> parse_uri_skip_host(Rest, State, Method); parse_uri(Buffer, State, Method) -> parse_uri_path(Buffer, State, Method, <<>>). parse_uri_skip_host(<< C, Rest/bits >>, State, Method) -> case C of $\r -> error_terminate(400, State); $/ -> parse_uri_path(Rest, State, Method, <<"/">>); _ -> parse_uri_skip_host(Rest, State, Method) end. parse_uri_path(<< C, Rest/bits >>, State, Method, SoFar) -> case C of $\r -> error_terminate(400, State); $\s -> parse_version(Rest, State, Method, SoFar, <<>>); $? -> parse_uri_query(Rest, State, Method, SoFar, <<>>); $# -> skip_uri_fragment(Rest, State, Method, SoFar, <<>>); _ -> parse_uri_path(Rest, State, Method, << SoFar/binary, C >>) end. parse_uri_query(<< C, Rest/bits >>, S, M, P, SoFar) -> case C of $\r -> error_terminate(400, S); $\s -> parse_version(Rest, S, M, P, SoFar); $# -> skip_uri_fragment(Rest, S, M, P, SoFar); _ -> parse_uri_query(Rest, S, M, P, << SoFar/binary, C >>) end. skip_uri_fragment(<< C, Rest/bits >>, S, M, P, Q) -> case C of $\r -> error_terminate(400, S); $\s -> parse_version(Rest, S, M, P, Q); _ -> skip_uri_fragment(Rest, S, M, P, Q) end. parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q) -> parse_header(Rest, S, M, P, Q, 'HTTP/1.1', []); parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q) -> parse_header(Rest, S, M, P, Q, 'HTTP/1.0', []); parse_version(_, State, _, _, _) -> error_terminate(505, State). %% Stop receiving data if we have more than allowed number of headers. wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, Headers) when length(Headers) >= MaxHeaders -> error_terminate(400, State); wait_header(Buffer, State=#state{socket=Socket, transport=Transport, until=Until}, M, P, Q, V, H) -> case recv(Socket, Transport, Until) of {ok, Data} -> parse_header(<< Buffer/binary, Data/binary >>, State, M, P, Q, V, H); {error, timeout} -> error_terminate(408, State); {error, _} -> terminate(State) end. parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, V, Headers) -> request(Rest, S, M, P, Q, V, lists:reverse(Headers)); parse_header(Buffer, State=#state{max_header_name_length=MaxLength}, M, P, Q, V, H) -> case match_colon(Buffer, 0) of nomatch when byte_size(Buffer) > MaxLength -> error_terminate(400, State); nomatch -> wait_header(Buffer, State, M, P, Q, V, H); _ -> parse_hd_name(Buffer, State, M, P, Q, V, H, <<>>) end. match_colon(<< $:, _/bits >>, N) -> N; match_colon(<< _, Rest/bits >>, N) -> match_colon(Rest, N + 1); match_colon(_, _) -> nomatch. %% I know, this isn't exactly pretty. But this is the most critical %% code path and as such needs to be optimized to death. %% %% ... Sorry for your eyes. %% %% But let's be honest, that's still pretty readable. parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, V, H, SoFar) -> case C of $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, SoFar); $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar); $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar); $A -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $a >>); $B -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $b >>); $C -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $c >>); $D -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $d >>); $E -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $e >>); $F -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $f >>); $G -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $g >>); $H -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $h >>); $I -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $i >>); $J -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $j >>); $K -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $k >>); $L -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $l >>); $M -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $m >>); $N -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $n >>); $O -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $o >>); $P -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $p >>); $Q -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $q >>); $R -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $r >>); $S -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $s >>); $T -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $t >>); $U -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $u >>); $V -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $v >>); $W -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $w >>); $X -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $x >>); $Y -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $y >>); $Z -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $z >>); C -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, C >>) end. parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, V, H, Name) -> case C of $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name); $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name); $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, Name) end. wait_hd_before_value(Buffer, State=#state{ socket=Socket, transport=Transport, until=Until}, M, P, Q, V, H, N) -> case recv(Socket, Transport, Until) of {ok, Data} -> parse_hd_before_value(<< Buffer/binary, Data/binary >>, State, M, P, Q, V, H, N); {error, timeout} -> error_terminate(408, State); {error, _} -> terminate(State) end. parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, V, H, N) -> parse_hd_before_value(Rest, S, M, P, Q, V, H, N); parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, V, H, N) -> parse_hd_before_value(Rest, S, M, P, Q, V, H, N); parse_hd_before_value(Buffer, State=#state{ max_header_value_length=MaxLength}, M, P, Q, V, H, N) -> case match_eol(Buffer, 0) of nomatch when byte_size(Buffer) > MaxLength -> error_terminate(400, State); nomatch -> wait_hd_before_value(Buffer, State, M, P, Q, V, H, N); _ -> parse_hd_value(Buffer, State, M, P, Q, V, H, N, <<>>) end. %% We completely ignore the first argument which is always %% the empty binary. We keep it there because we don't want %% to change the other arguments' position and trigger costy %% operations for no reasons. wait_hd_value(_, State=#state{ socket=Socket, transport=Transport, until=Until}, M, P, Q, V, H, N, SoFar) -> case recv(Socket, Transport, Until) of {ok, Data} -> parse_hd_value(Data, State, M, P, Q, V, H, N, SoFar); {error, timeout} -> error_terminate(408, State); {error, _} -> terminate(State) end. %% Pushing back as much as we could the retrieval of new data %% to check for multilines allows us to avoid a few tests in %% the critical path, but forces us to have a special function. wait_hd_value_nl(_, State=#state{ socket=Socket, transport=Transport, until=Until}, M, P, Q, V, Headers, Name, SoFar) -> case recv(Socket, Transport, Until) of {ok, << C, Data/bits >>} when C =:= $\s; C =:= $\t -> parse_hd_value(Data, State, M, P, Q, V, Headers, Name, SoFar); {ok, Data} -> parse_header(Data, State, M, P, Q, V, [{Name, SoFar}|Headers]); {error, timeout} -> error_terminate(408, State); {error, _} -> terminate(State) end. parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, V, Headers, Name, SoFar) -> case Rest of << $\n >> -> wait_hd_value_nl(<<>>, S, M, P, Q, V, Headers, Name, SoFar); << $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t -> parse_hd_value(Rest2, S, M, P, Q, V, Headers, Name, SoFar); << $\n, Rest2/bits >> -> parse_header(Rest2, S, M, P, Q, V, [{Name, SoFar}|Headers]) end; parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, V, H, N, SoFar) -> parse_hd_value(Rest, S, M, P, Q, V, H, N, << SoFar/binary, C >>); parse_hd_value(<<>>, State=#state{max_header_value_length=MaxLength}, _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength -> error_terminate(400, State); parse_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar) -> wait_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar). request(B, State=#state{transport=Transport}, M, P, Q, Version, Headers) -> case lists:keyfind(<<"host">>, 1, Headers) of false when Version =:= 'HTTP/1.1' -> error_terminate(400, State); false -> request(B, State, M, P, Q, Version, Headers, <<>>, default_port(Transport:name())); {_, RawHost} -> case catch parse_host(RawHost, <<>>) of {'EXIT', _} -> error_terminate(400, State); {Host, undefined} -> request(B, State, M, P, Q, Version, Headers, Host, default_port(Transport:name())); {Host, Port} -> request(B, State, M, P, Q, Version, Headers, Host, Port) end end. -spec default_port(atom()) -> 80 | 443. default_port(ssl) -> 443; default_port(_) -> 80. %% Another hurtful block of code. :) parse_host(<<>>, Acc) -> {Acc, undefined}; parse_host(<< $:, Rest/bits >>, Acc) -> {Acc, list_to_integer(binary_to_list(Rest))}; parse_host(<< C, Rest/bits >>, Acc) -> case C of $A -> parse_host(Rest, << Acc/binary, $a >>); $B -> parse_host(Rest, << Acc/binary, $b >>); $C -> parse_host(Rest, << Acc/binary, $c >>); $D -> parse_host(Rest, << Acc/binary, $d >>); $E -> parse_host(Rest, << Acc/binary, $e >>); $F -> parse_host(Rest, << Acc/binary, $f >>); $G -> parse_host(Rest, << Acc/binary, $g >>); $H -> parse_host(Rest, << Acc/binary, $h >>); $I -> parse_host(Rest, << Acc/binary, $i >>); $J -> parse_host(Rest, << Acc/binary, $j >>); $K -> parse_host(Rest, << Acc/binary, $k >>); $L -> parse_host(Rest, << Acc/binary, $l >>); $M -> parse_host(Rest, << Acc/binary, $m >>); $N -> parse_host(Rest, << Acc/binary, $n >>); $O -> parse_host(Rest, << Acc/binary, $o >>); $P -> parse_host(Rest, << Acc/binary, $p >>); $Q -> parse_host(Rest, << Acc/binary, $q >>); $R -> parse_host(Rest, << Acc/binary, $r >>); $S -> parse_host(Rest, << Acc/binary, $s >>); $T -> parse_host(Rest, << Acc/binary, $t >>); $U -> parse_host(Rest, << Acc/binary, $u >>); $V -> parse_host(Rest, << Acc/binary, $v >>); $W -> parse_host(Rest, << Acc/binary, $w >>); $X -> parse_host(Rest, << Acc/binary, $x >>); $Y -> parse_host(Rest, << Acc/binary, $y >>); $Z -> parse_host(Rest, << Acc/binary, $z >>); _ -> parse_host(Rest, << Acc/binary, C >>) end. %% End of request parsing. %% %% We create the Req object and start handling the request. request(Buffer, State=#state{socket=Socket, transport=Transport, req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive, compress=Compress, onresponse=OnResponse}, Method, Path, Query, Version, Headers, Host, Port) -> case Transport:peername(Socket) of {ok, Peer} -> Req = cowboy_req:new(Socket, Transport, Peer, Method, Path, Query, Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive, Compress, OnResponse), onrequest(Req, State); {error, _} -> %% Couldn't read the peer address; connection is gone. terminate(State) end. %% Call the global onrequest callback. The callback can send a reply, %% in which case we consider the request handled and move on to the next %% one. Note that since we haven't dispatched yet, we don't know the %% handler, host_info, path_info or bindings yet. -spec onrequest(cowboy_req:req(), #state{}) -> ok. onrequest(Req, State=#state{onrequest=undefined}) -> execute(Req, State); onrequest(Req, State=#state{onrequest=OnRequest}) -> Req2 = OnRequest(Req), case cowboy_req:get(resp_state, Req2) of waiting -> execute(Req2, State); _ -> next_request(Req2, State, ok) end. -spec execute(cowboy_req:req(), #state{}) -> ok. execute(Req, State=#state{middlewares=Middlewares, env=Env}) -> execute(Req, State, Env, Middlewares). -spec execute(cowboy_req:req(), #state{}, cowboy_middleware:env(), [module()]) -> ok. execute(Req, State, Env, []) -> next_request(Req, State, get_value(result, Env, ok)); execute(Req, State, Env, [Middleware|Tail]) -> case Middleware:execute(Req, Env) of {ok, Req2, Env2} -> execute(Req2, State, Env2, Tail); {suspend, Module, Function, Args} -> erlang:hibernate(?MODULE, resume, [State, Env, Tail, Module, Function, Args]); {halt, Req2} -> next_request(Req2, State, ok); {error, Code, Req2} -> error_terminate(Code, Req2, State) end. %% @private -spec resume(#state{}, cowboy_middleware:env(), [module()], module(), module(), [any()]) -> ok. resume(State, Env, Tail, Module, Function, Args) -> case apply(Module, Function, Args) of {ok, Req2, Env2} -> execute(Req2, State, Env2, Tail); {suspend, Module2, Function2, Args2} -> erlang:hibernate(?MODULE, resume, [State, Env, Tail, Module2, Function2, Args2]); {halt, Req2} -> next_request(Req2, State, ok); {error, Code, Req2} -> error_terminate(Code, Req2, State) end. -spec next_request(cowboy_req:req(), #state{}, any()) -> ok. next_request(Req, State=#state{req_keepalive=Keepalive, timeout=Timeout}, HandlerRes) -> cowboy_req:ensure_response(Req, 204), %% If we are going to close the connection, %% we do not want to attempt to skip the body. case cowboy_req:get(connection, Req) of close -> terminate(State); _ -> Buffer = case cowboy_req:skip_body(Req) of {ok, Req2} -> cowboy_req:get(buffer, Req2); _ -> close end, %% Flush the resp_sent message before moving on. receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, if HandlerRes =:= ok, Buffer =/= close -> ?MODULE:parse_request(Buffer, State#state{req_keepalive=Keepalive + 1, until=until(Timeout)}, 0); true -> terminate(State) end end. %% Only send an error reply if there is no resp_sent message. -spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok. error_terminate(Code, Req, State) -> receive {cowboy_req, resp_sent} -> ok after 0 -> _ = cowboy_req:reply(Code, Req), ok end, terminate(State). %% Only send an error reply if there is no resp_sent message. -spec error_terminate(cowboy:http_status(), #state{}) -> ok. error_terminate(Code, State=#state{socket=Socket, transport=Transport, compress=Compress, onresponse=OnResponse}) -> receive {cowboy_req, resp_sent} -> ok after 0 -> _ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport, undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>, undefined, <<>>, false, Compress, OnResponse)), ok end, terminate(State). -spec terminate(#state{}) -> ok. terminate(#state{socket=Socket, transport=Transport}) -> Transport:close(Socket), ok. erlang-cowboy-0.8.6+dfsg1/src/cowboy_req.erl000066400000000000000000001606471223335133400207560ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% Copyright (c) 2011, Anthony Ramine %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc HTTP request manipulation API. %% %% The functions in this module try to follow this pattern for their %% return types: %%
%%
access:
%%
{Value, Req}
%%
action:
%%
{Result, Req} | {Result, Value, Req} | {error, atom()}
%%
modification:
%%
Req
%%
question (has_* or is_*):
%%
boolean()
%%
%% %% Exceptions include chunk/2 which always returns 'ok', %% and to_list/1 which returns a list of key/values. %% %% Also note that all body reading functions perform actions, as Cowboy %% doesn't read the request body until they are called. %% %% Whenever Req is returned, it should always be kept in place of %% the one given as argument in your function call, because it keeps %% track of the request and response state. Doing so allows Cowboy to do %% some lazy evaluation and cache results when possible. -module(cowboy_req). %% Request API. -export([new/14]). -export([method/1]). -export([version/1]). -export([peer/1]). -export([host/1]). -export([host_info/1]). -export([port/1]). -export([path/1]). -export([path_info/1]). -export([qs/1]). -export([qs_val/2]). -export([qs_val/3]). -export([qs_vals/1]). -export([host_url/1]). -export([url/1]). -export([binding/2]). -export([binding/3]). -export([bindings/1]). -export([header/2]). -export([header/3]). -export([headers/1]). -export([parse_header/2]). -export([parse_header/3]). -export([cookie/2]). -export([cookie/3]). -export([cookies/1]). -export([meta/2]). -export([meta/3]). -export([set_meta/3]). %% Request body API. -export([has_body/1]). -export([body_length/1]). -export([init_stream/4]). -export([stream_body/1]). -export([stream_body/2]). -export([skip_body/1]). -export([body/1]). -export([body/2]). -export([body_qs/1]). -export([body_qs/2]). -export([multipart_data/1]). -export([multipart_skip/1]). %% Response API. -export([set_resp_cookie/4]). -export([set_resp_header/3]). -export([set_resp_body/2]). -export([set_resp_body_fun/2]). -export([set_resp_body_fun/3]). -export([has_resp_header/2]). -export([has_resp_body/1]). -export([delete_resp_header/2]). -export([reply/2]). -export([reply/3]). -export([reply/4]). -export([chunked_reply/2]). -export([chunked_reply/3]). -export([chunk/2]). -export([upgrade_reply/3]). -export([ensure_response/2]). %% Private setter/getter API. -export([append_buffer/2]). -export([get/2]). -export([set/2]). -export([set_bindings/4]). %% Misc API. -export([compact/1]). -export([lock/1]). -export([to_list/1]). -type cookie_option() :: {max_age, non_neg_integer()} | {domain, binary()} | {path, binary()} | {secure, boolean()} | {http_only, boolean()}. -type cookie_opts() :: [cookie_option()]. -export_type([cookie_opts/0]). -type content_decode_fun() :: fun((binary()) -> {ok, binary()} | {error, atom()}). -type transfer_decode_fun() :: fun((binary(), any()) -> {ok, binary(), binary(), any()} | more | {more, non_neg_integer(), binary(), any()} | {done, non_neg_integer(), binary()} | {done, binary(), non_neg_integer(), binary()} | {error, atom()}). -type resp_body_fun() :: fun((any(), module()) -> ok). -type send_chunk_fun() :: fun((iodata()) -> ok | {error, atom()}). -type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok). -record(http_req, { %% Transport. socket = undefined :: any(), transport = undefined :: undefined | module(), connection = keepalive :: keepalive | close, %% Request. pid = undefined :: pid(), method = <<"GET">> :: binary(), version = 'HTTP/1.1' :: cowboy:http_version(), peer = undefined :: undefined | {inet:ip_address(), inet:port_number()}, host = undefined :: undefined | binary(), host_info = undefined :: undefined | cowboy_router:tokens(), port = undefined :: undefined | inet:port_number(), path = undefined :: binary(), path_info = undefined :: undefined | cowboy_router:tokens(), qs = undefined :: binary(), qs_vals = undefined :: undefined | list({binary(), binary() | true}), bindings = undefined :: undefined | cowboy_router:bindings(), headers = [] :: cowboy:http_headers(), p_headers = [] :: [any()], %% @todo Improve those specs. cookies = undefined :: undefined | [{binary(), binary()}], meta = [] :: [{atom(), any()}], %% Request body. body_state = waiting :: waiting | done | {stream, non_neg_integer(), transfer_decode_fun(), any(), content_decode_fun()}, multipart = undefined :: undefined | {non_neg_integer(), fun()}, buffer = <<>> :: binary(), %% Response. resp_compress = false :: boolean(), resp_state = waiting :: locked | waiting | chunks | done, resp_headers = [] :: cowboy:http_headers(), resp_body = <<>> :: iodata() | resp_body_fun() | {non_neg_integer(), resp_body_fun()} | {chunked, resp_chunked_fun()}, %% Functions. onresponse = undefined :: undefined | already_called | cowboy:onresponse_fun() }). -opaque req() :: #http_req{}. -export_type([req/0]). %% Request API. %% @doc Create a new HTTP Req object. %% %% This function takes care of setting the owner's pid to self(). %% @private %% %% Since we always need to parse the Connection header, we do it %% in an optimized way and add the parsed value to p_headers' cache. -spec new(any(), module(), undefined | {inet:ip_address(), inet:port_number()}, binary(), binary(), binary(), cowboy:http_version(), cowboy:http_headers(), binary(), inet:port_number() | undefined, binary(), boolean(), boolean(), undefined | cowboy:onresponse_fun()) -> req(). new(Socket, Transport, Peer, Method, Path, Query, Version, Headers, Host, Port, Buffer, CanKeepalive, Compress, OnResponse) -> Req = #http_req{socket=Socket, transport=Transport, pid=self(), peer=Peer, method=Method, path=Path, qs=Query, version=Version, headers=Headers, host=Host, port=Port, buffer=Buffer, resp_compress=Compress, onresponse=OnResponse}, case CanKeepalive and (Version =:= 'HTTP/1.1') of false -> Req#http_req{connection=close}; true -> case lists:keyfind(<<"connection">>, 1, Headers) of false -> Req; %% keepalive {_, ConnectionHeader} -> Tokens = parse_connection_before(ConnectionHeader, []), Connection = connection_to_atom(Tokens), Req#http_req{connection=Connection, p_headers=[{<<"connection">>, Tokens}]} end end. %% @doc Return the HTTP method of the request. -spec method(Req) -> {binary(), Req} when Req::req(). method(Req) -> {Req#http_req.method, Req}. %% @doc Return the HTTP version used for the request. -spec version(Req) -> {cowboy:http_version(), Req} when Req::req(). version(Req) -> {Req#http_req.version, Req}. %% @doc Return the peer address and port number of the remote host. -spec peer(Req) -> {{inet:ip_address(), inet:port_number()}, Req} when Req::req(). peer(Req) -> {Req#http_req.peer, Req}. %% @doc Return the host binary string. -spec host(Req) -> {binary(), Req} when Req::req(). host(Req) -> {Req#http_req.host, Req}. %% @doc Return the extra host information obtained from partially matching %% the hostname using '...'. -spec host_info(Req) -> {cowboy_router:tokens() | undefined, Req} when Req::req(). host_info(Req) -> {Req#http_req.host_info, Req}. %% @doc Return the port used for this request. -spec port(Req) -> {inet:port_number(), Req} when Req::req(). port(Req) -> {Req#http_req.port, Req}. %% @doc Return the path binary string. -spec path(Req) -> {binary(), Req} when Req::req(). path(Req) -> {Req#http_req.path, Req}. %% @doc Return the extra path information obtained from partially matching %% the patch using '...'. -spec path_info(Req) -> {cowboy_router:tokens() | undefined, Req} when Req::req(). path_info(Req) -> {Req#http_req.path_info, Req}. %% @doc Return the raw query string directly taken from the request. -spec qs(Req) -> {binary(), Req} when Req::req(). qs(Req) -> {Req#http_req.qs, Req}. %% @equiv qs_val(Name, Req, undefined) -spec qs_val(binary(), Req) -> {binary() | true | undefined, Req} when Req::req(). qs_val(Name, Req) when is_binary(Name) -> qs_val(Name, Req, undefined). %% @doc Return the query string value for the given key, or a default if %% missing. -spec qs_val(binary(), Req, Default) -> {binary() | true | Default, Req} when Req::req(), Default::any(). qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined}, Default) when is_binary(Name) -> QsVals = cowboy_http:x_www_form_urlencoded(RawQs), qs_val(Name, Req#http_req{qs_vals=QsVals}, Default); qs_val(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.qs_vals) of {Name, Value} -> {Value, Req}; false -> {Default, Req} end. %% @doc Return the full list of query string values. -spec qs_vals(Req) -> {list({binary(), binary() | true}), Req} when Req::req(). qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) -> QsVals = cowboy_http:x_www_form_urlencoded(RawQs), qs_vals(Req#http_req{qs_vals=QsVals}); qs_vals(Req=#http_req{qs_vals=QsVals}) -> {QsVals, Req}. %% @doc Return the request URL as a binary without the path and query string. %% %% The URL includes the scheme, host and port only. %% @see cowboy_req:url/1 -spec host_url(Req) -> {undefined | binary(), Req} when Req::req(). host_url(Req=#http_req{port=undefined}) -> {undefined, Req}; host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) -> TransportName = Transport:name(), Secure = case TransportName of ssl -> <<"s">>; _ -> <<>> end, PortBin = case {TransportName, Port} of {ssl, 443} -> <<>>; {tcp, 80} -> <<>>; _ -> << ":", (list_to_binary(integer_to_list(Port)))/binary >> end, {<< "http", Secure/binary, "://", Host/binary, PortBin/binary >>, Req}. %% @doc Return the full request URL as a binary. %% %% The URL includes the scheme, host, port, path and query string. -spec url(Req) -> {undefined | binary(), Req} when Req::req(). url(Req=#http_req{}) -> {HostURL, Req2} = host_url(Req), url(HostURL, Req2). url(undefined, Req=#http_req{}) -> {undefined, Req}; url(HostURL, Req=#http_req{path=Path, qs=QS}) -> QS2 = case QS of <<>> -> <<>>; _ -> << "?", QS/binary >> end, {<< HostURL/binary, Path/binary, QS2/binary >>, Req}. %% @equiv binding(Name, Req, undefined) -spec binding(atom(), Req) -> {binary() | undefined, Req} when Req::req(). binding(Name, Req) when is_atom(Name) -> binding(Name, Req, undefined). %% @doc Return the binding value for the given key obtained when matching %% the host and path against the dispatch list, or a default if missing. -spec binding(atom(), Req, Default) -> {binary() | Default, Req} when Req::req(), Default::any(). binding(Name, Req, Default) when is_atom(Name) -> case lists:keyfind(Name, 1, Req#http_req.bindings) of {Name, Value} -> {Value, Req}; false -> {Default, Req} end. %% @doc Return the full list of binding values. -spec bindings(Req) -> {list({atom(), binary()}), Req} when Req::req(). bindings(Req) -> {Req#http_req.bindings, Req}. %% @equiv header(Name, Req, undefined) -spec header(binary(), Req) -> {binary() | undefined, Req} when Req::req(). header(Name, Req) -> header(Name, Req, undefined). %% @doc Return the header value for the given key, or a default if missing. -spec header(binary(), Req, Default) -> {binary() | Default, Req} when Req::req(), Default::any(). header(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.headers) of {Name, Value} -> {Value, Req}; false -> {Default, Req} end. %% @doc Return the full list of headers. -spec headers(Req) -> {cowboy:http_headers(), Req} when Req::req(). headers(Req) -> {Req#http_req.headers, Req}. %% @doc Semantically parse headers. %% %% When the value isn't found, a proper default value for the type %% returned is used as a return value. %% @see parse_header/3 -spec parse_header(binary(), Req) -> {ok, any(), Req} | {undefined, binary(), Req} | {error, badarg} when Req::req(). parse_header(Name, Req=#http_req{p_headers=PHeaders}) -> case lists:keyfind(Name, 1, PHeaders) of false -> parse_header(Name, Req, parse_header_default(Name)); {Name, Value} -> {ok, Value, Req} end. %% @doc Default values for semantic header parsing. -spec parse_header_default(binary()) -> any(). parse_header_default(<<"transfer-encoding">>) -> [<<"identity">>]; parse_header_default(_Name) -> undefined. %% @doc Semantically parse headers. %% %% When the header is unknown, the value is returned directly without parsing. -spec parse_header(binary(), Req, any()) -> {ok, any(), Req} | {undefined, binary(), Req} | {error, badarg} when Req::req(). parse_header(Name = <<"accept">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:list(Value, fun cowboy_http:media_range/2) end); parse_header(Name = <<"accept-charset">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2) end); parse_header(Name = <<"accept-encoding">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:list(Value, fun cowboy_http:conneg/2) end); parse_header(Name = <<"accept-language">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2) end); parse_header(Name = <<"authorization">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:token_ci(Value, fun cowboy_http:authorization/2) end); parse_header(Name = <<"content-length">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:digits/1); parse_header(Name = <<"content-type">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:content_type/1); parse_header(Name = <<"cookie">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:cookie_list/1); parse_header(Name = <<"expect">>, Req, Default) -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2) end); parse_header(Name, Req, Default) when Name =:= <<"if-match">>; Name =:= <<"if-none-match">> -> parse_header(Name, Req, Default, fun cowboy_http:entity_tag_match/1); parse_header(Name, Req, Default) when Name =:= <<"if-modified-since">>; Name =:= <<"if-unmodified-since">> -> parse_header(Name, Req, Default, fun cowboy_http:http_date/1); parse_header(Name = <<"range">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:range/1); parse_header(Name, Req, Default) when Name =:= <<"sec-websocket-protocol">>; Name =:= <<"x-forwarded-for">> -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:token/2) end); %% @todo Extension parameters. parse_header(Name, Req, Default) when Name =:= <<"transfer-encoding">>; Name =:= <<"upgrade">> -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) end); parse_header(Name = <<"sec-websocket-extensions">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1); parse_header(Name, Req, Default) -> {Value, Req2} = header(Name, Req, Default), {undefined, Value, Req2}. parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> case header(Name, Req) of {undefined, Req2} -> {ok, Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}}; {Value, Req2} -> case Fun(Value) of {error, badarg} -> {error, badarg}; P -> {ok, P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}} end end. %% @equiv cookie(Name, Req, undefined) -spec cookie(binary(), Req) -> {binary() | undefined, Req} when Req::req(). cookie(Name, Req) when is_binary(Name) -> cookie(Name, Req, undefined). %% @doc Return the cookie value for the given key, or a default if %% missing. -spec cookie(binary(), Req, Default) -> {binary() | Default, Req} when Req::req(), Default::any(). cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> case parse_header(<<"cookie">>, Req) of {ok, undefined, Req2} -> {Default, Req2#http_req{cookies=[]}}; {ok, Cookies, Req2} -> cookie(Name, Req2#http_req{cookies=Cookies}, Default); %% Flash player incorrectly sends an empty Cookie header. {error, badarg} -> {Default, Req#http_req{cookies=[]}} end; cookie(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.cookies) of {Name, Value} -> {Value, Req}; false -> {Default, Req} end. %% @doc Return the full list of cookie values. -spec cookies(Req) -> {list({binary(), binary()}), Req} when Req::req(). cookies(Req=#http_req{cookies=undefined}) -> case parse_header(<<"cookie">>, Req) of {ok, undefined, Req2} -> {[], Req2#http_req{cookies=[]}}; {ok, Cookies, Req2} -> cookies(Req2#http_req{cookies=Cookies}); %% Flash player incorrectly sends an empty Cookie header. {error, badarg} -> {[], Req#http_req{cookies=[]}} end; cookies(Req=#http_req{cookies=Cookies}) -> {Cookies, Req}. %% @equiv meta(Name, Req, undefined) -spec meta(atom(), Req) -> {any() | undefined, Req} when Req::req(). meta(Name, Req) -> meta(Name, Req, undefined). %% @doc Return metadata information about the request. %% %% Metadata information varies from one protocol to another. Websockets %% would define the protocol version here, while REST would use it to %% indicate which media type, language and charset were retained. -spec meta(atom(), Req, any()) -> {any(), Req} when Req::req(). meta(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.meta) of {Name, Value} -> {Value, Req}; false -> {Default, Req} end. %% @doc Set metadata information. %% %% You can use this function to attach information about the request. %% %% If the value already exists it will be overwritten. -spec set_meta(atom(), any(), Req) -> Req when Req::req(). set_meta(Name, Value, Req=#http_req{meta=Meta}) -> Req#http_req{meta=[{Name, Value}|lists:keydelete(Name, 1, Meta)]}. %% Request Body API. %% @doc Return whether the request message has a body. -spec has_body(req()) -> boolean(). has_body(Req) -> case lists:keyfind(<<"content-length">>, 1, Req#http_req.headers) of {_, <<"0">>} -> false; {_, _} -> true; _ -> lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers) end. %% @doc Return the request message body length, if known. %% %% The length may not be known if Transfer-Encoding is not identity, %% and the body hasn't been read at the time of the call. -spec body_length(Req) -> {undefined | non_neg_integer(), Req} when Req::req(). body_length(Req) -> case parse_header(<<"transfer-encoding">>, Req) of {ok, [<<"identity">>], Req2} -> {ok, Length, Req3} = parse_header(<<"content-length">>, Req2, 0), {Length, Req3}; {ok, _, Req2} -> {undefined, Req2} end. %% @doc Initialize body streaming and set custom decoding functions. %% %% Calling this function is optional. It should only be used if you %% need to override the default behavior of Cowboy. Otherwise you %% should call stream_body/{1,2} directly. %% %% Two decodings happen. First a decoding function is applied to the %% transferred data, and then another is applied to the actual content. %% %% Transfer encoding is generally used for chunked bodies. The decoding %% function uses a state to keep track of how much it has read, which is %% also initialized through this function. %% %% Content encoding is generally used for compression. %% %% Standard encodings can be found in cowboy_http. -spec init_stream(transfer_decode_fun(), any(), content_decode_fun(), Req) -> {ok, Req} when Req::req(). init_stream(TransferDecode, TransferState, ContentDecode, Req) -> {ok, Req#http_req{body_state= {stream, 0, TransferDecode, TransferState, ContentDecode}}}. %% @equiv stream_body(1000000, Req) -spec stream_body(Req) -> {ok, binary(), Req} | {done, Req} | {error, atom()} when Req::req(). stream_body(Req) -> stream_body(1000000, Req). %% @doc Stream the request's body. %% %% This is the most low level function to read the request body. %% %% In most cases, if they weren't defined before using init_stream/4, %% this function will guess which transfer and content encodings were %% used for building the request body, and configure the decoding %% functions that will be used when streaming. %% %% It then starts streaming the body, returning {ok, Data, Req} %% for each streamed part, and {done, Req} when it's finished streaming. %% %% You can limit the size of the chunks being returned by using the %% first argument which is the size in bytes. It defaults to 1000000 bytes. -spec stream_body(non_neg_integer(), Req) -> {ok, binary(), Req} | {done, Req} | {error, atom()} when Req::req(). stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version, transport=Transport, socket=Socket}) -> {ok, ExpectHeader, Req1} = parse_header(<<"expect">>, Req), case ExpectHeader of [<<"100-continue">>] -> HTTPVer = atom_to_binary(Version, latin1), Transport:send(Socket, << HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>); undefined -> ok end, case parse_header(<<"transfer-encoding">>, Req1) of {ok, [<<"chunked">>], Req2} -> stream_body(MaxLength, Req2#http_req{body_state= {stream, 0, fun cowboy_http:te_chunked/2, {0, 0}, fun cowboy_http:ce_identity/1}}); {ok, [<<"identity">>], Req2} -> {Length, Req3} = body_length(Req2), case Length of 0 -> {done, Req3#http_req{body_state=done}}; Length -> stream_body(MaxLength, Req3#http_req{body_state= {stream, Length, fun cowboy_http:te_identity/2, {0, Length}, fun cowboy_http:ce_identity/1}}) end end; stream_body(_, Req=#http_req{body_state=done}) -> {done, Req}; stream_body(_, Req=#http_req{buffer=Buffer}) when Buffer =/= <<>> -> transfer_decode(Buffer, Req#http_req{buffer= <<>>}); stream_body(MaxLength, Req) -> stream_body_recv(MaxLength, Req). -spec stream_body_recv(non_neg_integer(), Req) -> {ok, binary(), Req} | {error, atom()} when Req::req(). stream_body_recv(MaxLength, Req=#http_req{ transport=Transport, socket=Socket, buffer=Buffer, body_state={stream, Length, _, _, _}}) -> %% @todo Allow configuring the timeout. case Transport:recv(Socket, min(Length, MaxLength), 5000) of {ok, Data} -> transfer_decode(<< Buffer/binary, Data/binary >>, Req#http_req{buffer= <<>>}); {error, Reason} -> {error, Reason} end. -spec transfer_decode(binary(), Req) -> {ok, binary(), Req} | {error, atom()} when Req::req(). transfer_decode(Data, Req=#http_req{body_state={stream, _, TransferDecode, TransferState, ContentDecode}}) -> case TransferDecode(Data, TransferState) of {ok, Data2, Rest, TransferState2} -> content_decode(ContentDecode, Data2, Req#http_req{buffer=Rest, body_state={stream, 0, TransferDecode, TransferState2, ContentDecode}}); %% @todo {header(s) for chunked more -> stream_body_recv(0, Req#http_req{buffer=Data, body_state={stream, 0, TransferDecode, TransferState, ContentDecode}}); {more, Length, Data2, TransferState2} -> content_decode(ContentDecode, Data2, Req#http_req{body_state={stream, Length, TransferDecode, TransferState2, ContentDecode}}); {done, Length, Rest} -> Req2 = transfer_decode_done(Length, Rest, Req), {done, Req2}; {done, Data2, Length, Rest} -> Req2 = transfer_decode_done(Length, Rest, Req), content_decode(ContentDecode, Data2, Req2); {error, Reason} -> {error, Reason} end. -spec transfer_decode_done(non_neg_integer(), binary(), Req) -> Req when Req::req(). transfer_decode_done(Length, Rest, Req=#http_req{ headers=Headers, p_headers=PHeaders}) -> Headers2 = lists:keystore(<<"content-length">>, 1, Headers, {<<"content-length">>, list_to_binary(integer_to_list(Length))}), %% At this point we just assume TEs were all decoded. Headers3 = lists:keydelete(<<"transfer-encoding">>, 1, Headers2), PHeaders2 = lists:keystore(<<"content-length">>, 1, PHeaders, {<<"content-length">>, Length}), PHeaders3 = lists:keydelete(<<"transfer-encoding">>, 1, PHeaders2), Req#http_req{buffer=Rest, body_state=done, headers=Headers3, p_headers=PHeaders3}. %% @todo Probably needs a Rest. -spec content_decode(content_decode_fun(), binary(), Req) -> {ok, binary(), Req} | {error, atom()} when Req::req(). content_decode(ContentDecode, Data, Req) -> case ContentDecode(Data) of {ok, Data2} -> {ok, Data2, Req}; {error, Reason} -> {error, Reason} end. %% @equiv body(8000000, Req) -spec body(Req) -> {ok, binary(), Req} | {error, atom()} when Req::req(). body(Req) -> body(8000000, Req). %% @doc Return the body sent with the request. -spec body(non_neg_integer() | infinity, Req) -> {ok, binary(), Req} | {error, atom()} when Req::req(). body(infinity, Req) -> case parse_header(<<"transfer-encoding">>, Req) of {ok, [<<"identity">>], Req2} -> read_body(Req2, <<>>); {ok, _, _} -> {error, chunked} end; body(MaxBodyLength, Req) -> case parse_header(<<"transfer-encoding">>, Req) of {ok, [<<"identity">>], Req2} -> {ok, Length, Req3} = parse_header(<<"content-length">>, Req2, 0), if Length > MaxBodyLength -> {error, badlength}; true -> read_body(Req3, <<>>) end; {ok, _, _} -> {error, chunked} end. -spec read_body(Req, binary()) -> {ok, binary(), Req} | {error, atom()} when Req::req(). read_body(Req, Acc) -> case stream_body(Req) of {ok, Data, Req2} -> read_body(Req2, << Acc/binary, Data/binary >>); {done, Req2} -> {ok, Acc, Req2}; {error, Reason} -> {error, Reason} end. -spec skip_body(Req) -> {ok, Req} | {error, atom()} when Req::req(). skip_body(Req) -> case stream_body(Req) of {ok, _, Req2} -> skip_body(Req2); {done, Req2} -> {ok, Req2}; {error, Reason} -> {error, Reason} end. %% @equiv body_qs(16000, Req) -spec body_qs(Req) -> {ok, [{binary(), binary() | true}], Req} | {error, atom()} when Req::req(). body_qs(Req) -> body_qs(16000, Req). %% @doc Return the body sent with the request, parsed as an %% application/x-www-form-urlencoded string. %% Essentially a POST query string. -spec body_qs(non_neg_integer() | infinity, Req) -> {ok, [{binary(), binary() | true}], Req} | {error, atom()} when Req::req(). body_qs(MaxBodyLength, Req) -> case body(MaxBodyLength, Req) of {ok, Body, Req2} -> {ok, cowboy_http:x_www_form_urlencoded(Body), Req2}; {error, Reason} -> {error, Reason} end. %% Multipart Request API. %% @doc Return data from the multipart parser. %% %% Use this function for multipart streaming. For each part in the request, %% this function returns {headers, Headers} followed by a sequence of %% {body, Data} tuples and finally end_of_part. When there %% is no part to parse anymore, eof is returned. -spec multipart_data(Req) -> {headers, cowboy:http_headers(), Req} | {body, binary(), Req} | {end_of_part | eof, Req} when Req::req(). multipart_data(Req=#http_req{body_state=waiting}) -> {ok, {<<"multipart">>, _SubType, Params}, Req2} = parse_header(<<"content-type">>, Req), {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), {ok, Length, Req3} = parse_header(<<"content-length">>, Req2), multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)}); multipart_data(Req=#http_req{multipart={Length, Cont}}) -> multipart_data(Req, Length, Cont()); multipart_data(Req=#http_req{body_state=done}) -> {eof, Req}. multipart_data(Req, Length, {headers, Headers, Cont}) -> {headers, Headers, Req#http_req{multipart={Length, Cont}}}; multipart_data(Req, Length, {body, Data, Cont}) -> {body, Data, Req#http_req{multipart={Length, Cont}}}; multipart_data(Req, Length, {end_of_part, Cont}) -> {end_of_part, Req#http_req{multipart={Length, Cont}}}; multipart_data(Req, 0, eof) -> {eof, Req#http_req{body_state=done, multipart=undefined}}; multipart_data(Req=#http_req{socket=Socket, transport=Transport}, Length, eof) -> %% We just want to skip so no need to stream data here. {ok, _Data} = Transport:recv(Socket, Length, 5000), {eof, Req#http_req{body_state=done, multipart=undefined}}; multipart_data(Req, Length, {more, Parser}) when Length > 0 -> case stream_body(Req) of {ok, << Data:Length/binary, Buffer/binary >>, Req2} -> multipart_data(Req2#http_req{buffer=Buffer}, 0, Parser(Data)); {ok, Data, Req2} -> multipart_data(Req2, Length - byte_size(Data), Parser(Data)) end. %% @doc Skip a part returned by the multipart parser. %% %% This function repeatedly calls multipart_data/1 until %% end_of_part or eof is parsed. -spec multipart_skip(Req) -> {ok, Req} when Req::req(). multipart_skip(Req) -> case multipart_data(Req) of {end_of_part, Req2} -> {ok, Req2}; {eof, Req2} -> {ok, Req2}; {_, _, Req2} -> multipart_skip(Req2) end. %% Response API. %% @doc Add a cookie header to the response. %% %% The cookie name cannot contain any of the following characters: %% =,;\s\t\r\n\013\014 %% %% The cookie value cannot contain any of the following characters: %% ,; \t\r\n\013\014 -spec set_resp_cookie(iodata(), iodata(), cookie_opts(), Req) -> Req when Req::req(). set_resp_cookie(Name, Value, Opts, Req) -> Cookie = cowboy_http:cookie_to_iodata(Name, Value, Opts), set_resp_header(<<"set-cookie">>, Cookie, Req). %% @doc Add a header to the response. -spec set_resp_header(binary(), iodata(), Req) -> Req when Req::req(). set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> Req#http_req{resp_headers=[{Name, Value}|RespHeaders]}. %% @doc Add a body to the response. %% %% The body set here is ignored if the response is later sent using %% anything other than reply/2 or reply/3. The response body is expected %% to be a binary or an iolist. -spec set_resp_body(iodata(), Req) -> Req when Req::req(). set_resp_body(Body, Req) -> Req#http_req{resp_body=Body}. %% @doc Add a body stream function to the response. %% %% The body set here is ignored if the response is later sent using %% anything other than reply/2 or reply/3. %% %% Setting a response stream function without a length means that the %% body will be sent until the connection is closed. Cowboy will make %% sure that the connection is closed with no extra step required. %% %% To inform the client that a body has been sent with this request, %% Cowboy will add a "Transfer-Encoding: identity" header to the %% response. -spec set_resp_body_fun(resp_body_fun(), Req) -> Req when Req::req(). set_resp_body_fun(StreamFun, Req) when is_function(StreamFun) -> Req#http_req{resp_body=StreamFun}. %% @doc Add a body function to the response. %% %% The body set here is ignored if the response is later sent using %% anything other than reply/2 or reply/3. %% %% Cowboy will call the given response stream function after sending the %% headers. This function must send the specified number of bytes to the %% socket it will receive as argument. %% %% If the body function crashes while writing the response body or writes %% fewer bytes than declared the behaviour is undefined. -spec set_resp_body_fun(non_neg_integer(), resp_body_fun(), Req) -> Req when Req::req(); (chunked, resp_chunked_fun(), Req) -> Req when Req::req(). set_resp_body_fun(StreamLen, StreamFun, Req) when is_integer(StreamLen), is_function(StreamFun) -> Req#http_req{resp_body={StreamLen, StreamFun}}; set_resp_body_fun(chunked, StreamFun, Req) when is_function(StreamFun) -> Req#http_req{resp_body={chunked, StreamFun}}. %% @doc Return whether the given header has been set for the response. -spec has_resp_header(binary(), req()) -> boolean(). has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> lists:keymember(Name, 1, RespHeaders). %% @doc Return whether a body has been set for the response. -spec has_resp_body(req()) -> boolean(). has_resp_body(#http_req{resp_body=RespBody}) when is_function(RespBody) -> true; has_resp_body(#http_req{resp_body={chunked, _}}) -> true; has_resp_body(#http_req{resp_body={Length, _}}) -> Length > 0; has_resp_body(#http_req{resp_body=RespBody}) -> iolist_size(RespBody) > 0. %% @doc Remove a header previously set for the response. -spec delete_resp_header(binary(), Req) -> Req when Req::req(). delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) -> RespHeaders2 = lists:keydelete(Name, 1, RespHeaders), Req#http_req{resp_headers=RespHeaders2}. %% @equiv reply(Status, [], [], Req) -spec reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req(). reply(Status, Req=#http_req{resp_body=Body}) -> reply(Status, [], Body, Req). %% @equiv reply(Status, Headers, [], Req) -spec reply(cowboy:http_status(), cowboy:http_headers(), Req) -> {ok, Req} when Req::req(). reply(Status, Headers, Req=#http_req{resp_body=Body}) -> reply(Status, Headers, Body, Req). %% @doc Send a reply to the client. -spec reply(cowboy:http_status(), cowboy:http_headers(), iodata() | {non_neg_integer() | resp_body_fun()}, Req) -> {ok, Req} when Req::req(). reply(Status, Headers, Body, Req=#http_req{ socket=Socket, transport=Transport, version=Version, connection=Connection, method=Method, resp_compress=Compress, resp_state=waiting, resp_headers=RespHeaders}) -> HTTP11Headers = if Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' -> [{<<"connection">>, atom_to_connection(Connection)}]; true -> [] end, Req3 = case Body of BodyFun when is_function(BodyFun) -> %% We stream the response body until we close the connection. RespConn = close, {RespType, Req2} = if Transport =:= cowboy_spdy -> response(Status, Headers, RespHeaders, [ {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} ], stream, Req); true -> response(Status, Headers, RespHeaders, [ {<<"connection">>, <<"close">>}, {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>}, {<<"transfer-encoding">>, <<"identity">>} ], <<>>, Req) end, if RespType =/= hook, Method =/= <<"HEAD">> -> BodyFun(Socket, Transport); true -> ok end, Req2#http_req{connection=RespConn}; {chunked, BodyFun} -> %% We stream the response body in chunks. {RespType, Req2} = chunked_response(Status, Headers, Req), if RespType =/= hook, Method =/= <<"HEAD">> -> ChunkFun = fun(IoData) -> chunk(IoData, Req2) end, BodyFun(ChunkFun), %% Terminate the chunked body for HTTP/1.1 only. case Version of 'HTTP/1.0' -> Req2; _ -> last_chunk(Req2) end; true -> Req2 end; {ContentLength, BodyFun} -> %% We stream the response body for ContentLength bytes. RespConn = response_connection(Headers, Connection), {RespType, Req2} = response(Status, Headers, RespHeaders, [ {<<"content-length">>, integer_to_list(ContentLength)}, {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} |HTTP11Headers], stream, Req), if RespType =/= hook, Method =/= <<"HEAD">> -> BodyFun(Socket, Transport); true -> ok end, Req2#http_req{connection=RespConn}; _ when Compress -> RespConn = response_connection(Headers, Connection), Req2 = reply_may_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method), Req2#http_req{connection=RespConn}; _ -> RespConn = response_connection(Headers, Connection), Req2 = reply_no_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method, iolist_size(Body)), Req2#http_req{connection=RespConn} end, {ok, Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. reply_may_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method) -> BodySize = iolist_size(Body), case parse_header(<<"accept-encoding">>, Req) of {ok, Encodings, Req2} -> CanGzip = (BodySize > 300) andalso (false =:= lists:keyfind(<<"content-encoding">>, 1, Headers)) andalso (false =:= lists:keyfind(<<"content-encoding">>, 1, RespHeaders)) andalso (false =:= lists:keyfind(<<"transfer-encoding">>, 1, Headers)) andalso (false =:= lists:keyfind(<<"transfer-encoding">>, 1, RespHeaders)) andalso (Encodings =/= undefined) andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)), case CanGzip of true -> GzBody = zlib:gzip(Body), {_, Req3} = response(Status, Headers, RespHeaders, [ {<<"content-length">>, integer_to_list(byte_size(GzBody))}, {<<"content-encoding">>, <<"gzip">>}, {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} |HTTP11Headers], case Method of <<"HEAD">> -> <<>>; _ -> GzBody end, Req2), Req3; false -> reply_no_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method, BodySize) end; {error, badarg} -> reply_no_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method, BodySize) end. reply_no_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method, BodySize) -> {_, Req2} = response(Status, Headers, RespHeaders, [ {<<"content-length">>, integer_to_list(BodySize)}, {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} |HTTP11Headers], case Method of <<"HEAD">> -> <<>>; _ -> Body end, Req), Req2. %% @equiv chunked_reply(Status, [], Req) -spec chunked_reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req(). chunked_reply(Status, Req) -> chunked_reply(Status, [], Req). %% @doc Initiate the sending of a chunked reply to the client. %% @see cowboy_req:chunk/2 -spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req) -> {ok, Req} when Req::req(). chunked_reply(Status, Headers, Req) -> {_, Req2} = chunked_response(Status, Headers, Req), {ok, Req2}. %% @doc Send a chunk of data. %% %% A chunked reply must have been initiated before calling this function. -spec chunk(iodata(), req()) -> ok | {error, atom()}. chunk(_Data, #http_req{method= <<"HEAD">>}) -> ok; chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy, resp_state=chunks}) -> cowboy_spdy:stream_data(Socket, Data); chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks, version='HTTP/1.0'}) -> Transport:send(Socket, Data); chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> Transport:send(Socket, [integer_to_list(iolist_size(Data), 16), <<"\r\n">>, Data, <<"\r\n">>]). %% @doc Finish the chunked reply. %% @todo If ever made public, need to send nothing if HEAD. -spec last_chunk(Req) -> Req when Req::req(). last_chunk(Req=#http_req{socket=Socket, transport=cowboy_spdy}) -> _ = cowboy_spdy:stream_close(Socket), Req#http_req{resp_state=done}; last_chunk(Req=#http_req{socket=Socket, transport=Transport}) -> _ = Transport:send(Socket, <<"0\r\n\r\n">>), Req#http_req{resp_state=done}. %% @doc Send an upgrade reply. %% @private -spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req) -> {ok, Req} when Req::req(). upgrade_reply(Status, Headers, Req=#http_req{transport=Transport, resp_state=waiting, resp_headers=RespHeaders}) when Transport =/= cowboy_spdy -> {_, Req2} = response(Status, Headers, RespHeaders, [ {<<"connection">>, <<"Upgrade">>} ], <<>>, Req), {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. %% @doc Ensure the response has been sent fully. %% @private -spec ensure_response(req(), cowboy:http_status()) -> ok. %% The response has already been fully sent to the client. ensure_response(#http_req{resp_state=done}, _) -> ok; %% No response has been sent but everything apparently went fine. %% Reply with the status code found in the second argument. ensure_response(Req=#http_req{resp_state=waiting}, Status) -> _ = reply(Status, [], [], Req), ok; %% Terminate the chunked body for HTTP/1.1 only. ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) -> ok; ensure_response(#http_req{version='HTTP/1.0', resp_state=chunks}, _) -> ok; ensure_response(Req=#http_req{resp_state=chunks}, _) -> _ = last_chunk(Req), ok. %% Private setter/getter API. %% @private -spec append_buffer(binary(), Req) -> Req when Req::req(). append_buffer(Suffix, Req=#http_req{buffer=Buffer}) -> Req#http_req{buffer= << Buffer/binary, Suffix/binary >>}. %% @private -spec get(atom(), req()) -> any(); ([atom()], req()) -> any(). get(List, Req) when is_list(List) -> [g(Atom, Req) || Atom <- List]; get(Atom, Req) when is_atom(Atom) -> g(Atom, Req). g(bindings, #http_req{bindings=Ret}) -> Ret; g(body_state, #http_req{body_state=Ret}) -> Ret; g(buffer, #http_req{buffer=Ret}) -> Ret; g(connection, #http_req{connection=Ret}) -> Ret; g(cookies, #http_req{cookies=Ret}) -> Ret; g(headers, #http_req{headers=Ret}) -> Ret; g(host, #http_req{host=Ret}) -> Ret; g(host_info, #http_req{host_info=Ret}) -> Ret; g(meta, #http_req{meta=Ret}) -> Ret; g(method, #http_req{method=Ret}) -> Ret; g(multipart, #http_req{multipart=Ret}) -> Ret; g(onresponse, #http_req{onresponse=Ret}) -> Ret; g(p_headers, #http_req{p_headers=Ret}) -> Ret; g(path, #http_req{path=Ret}) -> Ret; g(path_info, #http_req{path_info=Ret}) -> Ret; g(peer, #http_req{peer=Ret}) -> Ret; g(pid, #http_req{pid=Ret}) -> Ret; g(port, #http_req{port=Ret}) -> Ret; g(qs, #http_req{qs=Ret}) -> Ret; g(qs_vals, #http_req{qs_vals=Ret}) -> Ret; g(resp_body, #http_req{resp_body=Ret}) -> Ret; g(resp_compress, #http_req{resp_compress=Ret}) -> Ret; g(resp_headers, #http_req{resp_headers=Ret}) -> Ret; g(resp_state, #http_req{resp_state=Ret}) -> Ret; g(socket, #http_req{socket=Ret}) -> Ret; g(transport, #http_req{transport=Ret}) -> Ret; g(version, #http_req{version=Ret}) -> Ret. %% @private -spec set([{atom(), any()}], Req) -> Req when Req::req(). set([], Req) -> Req; set([{bindings, Val}|Tail], Req) -> set(Tail, Req#http_req{bindings=Val}); set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val}); set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val}); set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val}); set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val}); set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val}); set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val}); set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val}); set([{meta, Val}|Tail], Req) -> set(Tail, Req#http_req{meta=Val}); set([{method, Val}|Tail], Req) -> set(Tail, Req#http_req{method=Val}); set([{multipart, Val}|Tail], Req) -> set(Tail, Req#http_req{multipart=Val}); set([{onresponse, Val}|Tail], Req) -> set(Tail, Req#http_req{onresponse=Val}); set([{p_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{p_headers=Val}); set([{path, Val}|Tail], Req) -> set(Tail, Req#http_req{path=Val}); set([{path_info, Val}|Tail], Req) -> set(Tail, Req#http_req{path_info=Val}); set([{peer, Val}|Tail], Req) -> set(Tail, Req#http_req{peer=Val}); set([{pid, Val}|Tail], Req) -> set(Tail, Req#http_req{pid=Val}); set([{port, Val}|Tail], Req) -> set(Tail, Req#http_req{port=Val}); set([{qs, Val}|Tail], Req) -> set(Tail, Req#http_req{qs=Val}); set([{qs_vals, Val}|Tail], Req) -> set(Tail, Req#http_req{qs_vals=Val}); set([{resp_body, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_body=Val}); set([{resp_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_headers=Val}); set([{resp_state, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_state=Val}); set([{socket, Val}|Tail], Req) -> set(Tail, Req#http_req{socket=Val}); set([{transport, Val}|Tail], Req) -> set(Tail, Req#http_req{transport=Val}); set([{version, Val}|Tail], Req) -> set(Tail, Req#http_req{version=Val}). %% @private -spec set_bindings(cowboy_router:tokens(), cowboy_router:tokens(), cowboy_router:bindings(), Req) -> Req when Req::req(). set_bindings(HostInfo, PathInfo, Bindings, Req) -> Req#http_req{host_info=HostInfo, path_info=PathInfo, bindings=Bindings}. %% Misc API. %% @doc Compact the request data by removing all non-system information. %% %% This essentially removes the host and path info, query string, bindings, %% headers and cookies. %% %% Use it when you really need to save up memory, for example when having %% many concurrent long-running connections. -spec compact(Req) -> Req when Req::req(). compact(Req) -> Req#http_req{host_info=undefined, path_info=undefined, qs_vals=undefined, bindings=undefined, headers=[], p_headers=[], cookies=[]}. %% @doc Prevent any further responses. %% @private -spec lock(Req) -> Req when Req::req(). lock(Req) -> Req#http_req{resp_state=locked}. %% @doc Convert the Req object to a list of key/values. -spec to_list(req()) -> [{atom(), any()}]. to_list(Req) -> lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))). %% Internal. -spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) -> {normal | hook, Req} when Req::req(). chunked_response(Status, Headers, Req=#http_req{ transport=cowboy_spdy, resp_state=waiting, resp_headers=RespHeaders}) -> {RespType, Req2} = response(Status, Headers, RespHeaders, [ {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} ], stream, Req), {RespType, Req2#http_req{resp_state=chunks, resp_headers=[], resp_body= <<>>}}; chunked_response(Status, Headers, Req=#http_req{ version=Version, connection=Connection, resp_state=waiting, resp_headers=RespHeaders}) -> RespConn = response_connection(Headers, Connection), HTTP11Headers = case Version of 'HTTP/1.1' -> [ {<<"connection">>, atom_to_connection(Connection)}, {<<"transfer-encoding">>, <<"chunked">>}]; _ -> [] end, {RespType, Req2} = response(Status, Headers, RespHeaders, [ {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} |HTTP11Headers], <<>>, Req), {RespType, Req2#http_req{connection=RespConn, resp_state=chunks, resp_headers=[], resp_body= <<>>}}. -spec response(cowboy:http_status(), cowboy:http_headers(), cowboy:http_headers(), cowboy:http_headers(), stream | iodata(), Req) -> {normal | hook, Req} when Req::req(). response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{ socket=Socket, transport=Transport, version=Version, pid=ReqPid, onresponse=OnResponse}) -> FullHeaders = case OnResponse of already_called -> Headers; _ -> response_merge_headers(Headers, RespHeaders, DefaultHeaders) end, Body2 = case Body of stream -> <<>>; _ -> Body end, Req2 = case OnResponse of already_called -> Req; undefined -> Req; OnResponse -> OnResponse(Status, FullHeaders, Body2, %% Don't call 'onresponse' from the hook itself. Req#http_req{resp_headers=[], resp_body= <<>>, onresponse=already_called}) end, ReplyType = case Req2#http_req.resp_state of waiting when Transport =:= cowboy_spdy, Body =:= stream -> cowboy_spdy:stream_reply(Socket, status(Status), FullHeaders), ReqPid ! {?MODULE, resp_sent}, normal; waiting when Transport =:= cowboy_spdy -> cowboy_spdy:reply(Socket, status(Status), FullHeaders, Body), ReqPid ! {?MODULE, resp_sent}, normal; waiting -> HTTPVer = atom_to_binary(Version, latin1), StatusLine = << HTTPVer/binary, " ", (status(Status))/binary, "\r\n" >>, HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] || {Key, Value} <- FullHeaders], Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]), ReqPid ! {?MODULE, resp_sent}, normal; _ -> hook end, {ReplyType, Req2}. -spec response_connection(cowboy:http_headers(), keepalive | close) -> keepalive | close. response_connection([], Connection) -> Connection; response_connection([{Name, Value}|Tail], Connection) -> case Name of <<"connection">> -> Tokens = parse_connection_before(Value, []), connection_to_atom(Tokens); _ -> response_connection(Tail, Connection) end. -spec response_merge_headers(cowboy:http_headers(), cowboy:http_headers(), cowboy:http_headers()) -> cowboy:http_headers(). response_merge_headers(Headers, RespHeaders, DefaultHeaders) -> Headers2 = [{Key, Value} || {Key, Value} <- Headers], merge_headers( merge_headers(Headers2, RespHeaders), DefaultHeaders). -spec merge_headers(cowboy:http_headers(), cowboy:http_headers()) -> cowboy:http_headers(). %% Merge headers by prepending the tuples in the second list to the %% first list. It also handles Set-Cookie properly, which supports %% duplicated entries. Notice that, while the RFC2109 does allow more %% than one cookie to be set per Set-Cookie header, we are following %% the implementation of common web servers and applications which %% return many distinct headers per each Set-Cookie entry to avoid %% issues with clients/browser which may not support it. merge_headers(Headers, []) -> Headers; merge_headers(Headers, [{<<"set-cookie">>, Value}|Tail]) -> merge_headers([{<<"set-cookie">>, Value}|Headers], Tail); merge_headers(Headers, [{Name, Value}|Tail]) -> Headers2 = case lists:keymember(Name, 1, Headers) of true -> Headers; false -> [{Name, Value}|Headers] end, merge_headers(Headers2, Tail). -spec atom_to_connection(keepalive) -> <<_:80>>; (close) -> <<_:40>>. atom_to_connection(keepalive) -> <<"keep-alive">>; atom_to_connection(close) -> <<"close">>. %% Optimized parsing functions for the Connection header. parse_connection_before(<<>>, Acc) -> lists:reverse(Acc); parse_connection_before(<< C, Rest/bits >>, Acc) when C =:= $,; C =:= $\s; C =:= $\t -> parse_connection_before(Rest, Acc); parse_connection_before(Buffer, Acc) -> parse_connection(Buffer, Acc, <<>>). %% An evil block of code appeared! parse_connection(<<>>, Acc, <<>>) -> lists:reverse(Acc); parse_connection(<<>>, Acc, Token) -> lists:reverse([Token|Acc]); parse_connection(<< C, Rest/bits >>, Acc, Token) when C =:= $,; C =:= $\s; C =:= $\t -> parse_connection_before(Rest, [Token|Acc]); parse_connection(<< C, Rest/bits >>, Acc, Token) -> case C of $A -> parse_connection(Rest, Acc, << Token/binary, $a >>); $B -> parse_connection(Rest, Acc, << Token/binary, $b >>); $C -> parse_connection(Rest, Acc, << Token/binary, $c >>); $D -> parse_connection(Rest, Acc, << Token/binary, $d >>); $E -> parse_connection(Rest, Acc, << Token/binary, $e >>); $F -> parse_connection(Rest, Acc, << Token/binary, $f >>); $G -> parse_connection(Rest, Acc, << Token/binary, $g >>); $H -> parse_connection(Rest, Acc, << Token/binary, $h >>); $I -> parse_connection(Rest, Acc, << Token/binary, $i >>); $J -> parse_connection(Rest, Acc, << Token/binary, $j >>); $K -> parse_connection(Rest, Acc, << Token/binary, $k >>); $L -> parse_connection(Rest, Acc, << Token/binary, $l >>); $M -> parse_connection(Rest, Acc, << Token/binary, $m >>); $N -> parse_connection(Rest, Acc, << Token/binary, $n >>); $O -> parse_connection(Rest, Acc, << Token/binary, $o >>); $P -> parse_connection(Rest, Acc, << Token/binary, $p >>); $Q -> parse_connection(Rest, Acc, << Token/binary, $q >>); $R -> parse_connection(Rest, Acc, << Token/binary, $r >>); $S -> parse_connection(Rest, Acc, << Token/binary, $s >>); $T -> parse_connection(Rest, Acc, << Token/binary, $t >>); $U -> parse_connection(Rest, Acc, << Token/binary, $u >>); $V -> parse_connection(Rest, Acc, << Token/binary, $v >>); $W -> parse_connection(Rest, Acc, << Token/binary, $w >>); $X -> parse_connection(Rest, Acc, << Token/binary, $x >>); $Y -> parse_connection(Rest, Acc, << Token/binary, $y >>); $Z -> parse_connection(Rest, Acc, << Token/binary, $z >>); C -> parse_connection(Rest, Acc, << Token/binary, C >>) end. %% @doc Walk through a tokens list and return whether %% the connection is keepalive or closed. %% %% We don't match on "keep-alive" since it is the default value. -spec connection_to_atom([binary()]) -> keepalive | close. connection_to_atom([]) -> keepalive; connection_to_atom([<<"close">>|_]) -> close; connection_to_atom([_|Tail]) -> connection_to_atom(Tail). -spec status(cowboy:http_status()) -> binary(). status(100) -> <<"100 Continue">>; status(101) -> <<"101 Switching Protocols">>; status(102) -> <<"102 Processing">>; status(200) -> <<"200 OK">>; status(201) -> <<"201 Created">>; status(202) -> <<"202 Accepted">>; status(203) -> <<"203 Non-Authoritative Information">>; status(204) -> <<"204 No Content">>; status(205) -> <<"205 Reset Content">>; status(206) -> <<"206 Partial Content">>; status(207) -> <<"207 Multi-Status">>; status(226) -> <<"226 IM Used">>; status(300) -> <<"300 Multiple Choices">>; status(301) -> <<"301 Moved Permanently">>; status(302) -> <<"302 Found">>; status(303) -> <<"303 See Other">>; status(304) -> <<"304 Not Modified">>; status(305) -> <<"305 Use Proxy">>; status(306) -> <<"306 Switch Proxy">>; status(307) -> <<"307 Temporary Redirect">>; status(400) -> <<"400 Bad Request">>; status(401) -> <<"401 Unauthorized">>; status(402) -> <<"402 Payment Required">>; status(403) -> <<"403 Forbidden">>; status(404) -> <<"404 Not Found">>; status(405) -> <<"405 Method Not Allowed">>; status(406) -> <<"406 Not Acceptable">>; status(407) -> <<"407 Proxy Authentication Required">>; status(408) -> <<"408 Request Timeout">>; status(409) -> <<"409 Conflict">>; status(410) -> <<"410 Gone">>; status(411) -> <<"411 Length Required">>; status(412) -> <<"412 Precondition Failed">>; status(413) -> <<"413 Request Entity Too Large">>; status(414) -> <<"414 Request-URI Too Long">>; status(415) -> <<"415 Unsupported Media Type">>; status(416) -> <<"416 Requested Range Not Satisfiable">>; status(417) -> <<"417 Expectation Failed">>; status(418) -> <<"418 I'm a teapot">>; status(422) -> <<"422 Unprocessable Entity">>; status(423) -> <<"423 Locked">>; status(424) -> <<"424 Failed Dependency">>; status(425) -> <<"425 Unordered Collection">>; status(426) -> <<"426 Upgrade Required">>; status(428) -> <<"428 Precondition Required">>; status(429) -> <<"429 Too Many Requests">>; status(431) -> <<"431 Request Header Fields Too Large">>; status(500) -> <<"500 Internal Server Error">>; status(501) -> <<"501 Not Implemented">>; status(502) -> <<"502 Bad Gateway">>; status(503) -> <<"503 Service Unavailable">>; status(504) -> <<"504 Gateway Timeout">>; status(505) -> <<"505 HTTP Version Not Supported">>; status(506) -> <<"506 Variant Also Negotiates">>; status(507) -> <<"507 Insufficient Storage">>; status(510) -> <<"510 Not Extended">>; status(511) -> <<"511 Network Authentication Required">>; status(B) when is_binary(B) -> B. %% Tests. -ifdef(TEST). url_test() -> {undefined, _} = url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined, path= <<>>, qs= <<>>, pid=self()}), {<<"http://localhost/path">>, _ } = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80, path= <<"/path">>, qs= <<>>, pid=self()}), {<<"http://localhost:443/path">>, _} = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443, path= <<"/path">>, qs= <<>>, pid=self()}), {<<"http://localhost:8080/path">>, _} = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080, path= <<"/path">>, qs= <<>>, pid=self()}), {<<"http://localhost:8080/path?dummy=2785">>, _} = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080, path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}), {<<"https://localhost/path">>, _} = url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443, path= <<"/path">>, qs= <<>>, pid=self()}), {<<"https://localhost:8443/path">>, _} = url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443, path= <<"/path">>, qs= <<>>, pid=self()}), {<<"https://localhost:8443/path?dummy=2785">>, _} = url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443, path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}), ok. parse_connection_test_() -> %% {Binary, Result} Tests = [ {<<"close">>, [<<"close">>]}, {<<"ClOsE">>, [<<"close">>]}, {<<"Keep-Alive">>, [<<"keep-alive">>]}, {<<"keep-alive, Upgrade">>, [<<"keep-alive">>, <<"upgrade">>]} ], [{B, fun() -> R = parse_connection_before(B, []) end} || {B, R} <- Tests]. connection_to_atom_test_() -> %% {Tokens, Result} Tests = [ {[<<"close">>], close}, {[<<"keep-alive">>], keepalive}, {[<<"keep-alive">>, <<"upgrade">>], keepalive} ], [{lists:flatten(io_lib:format("~p", [T])), fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests]. merge_headers_test_() -> Tests = [ {[{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}], [{<<"set-cookie">>,<<"foo=bar">>},{<<"content-length">>,<<"11">>}], [{<<"set-cookie">>,<<"foo=bar">>}, {<<"content-length">>,<<"13">>}, {<<"server">>,<<"Cowboy">>}]}, {[{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}], [{<<"set-cookie">>,<<"foo=bar">>},{<<"set-cookie">>,<<"bar=baz">>}], [{<<"set-cookie">>,<<"bar=baz">>}, {<<"set-cookie">>,<<"foo=bar">>}, {<<"content-length">>,<<"13">>}, {<<"server">>,<<"Cowboy">>}]} ], [fun() -> Res = merge_headers(L,R) end || {L, R, Res} <- Tests]. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_rest.erl000066400000000000000000001157761223335133400211470ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc REST protocol implementation. %% %% Originally based on the Webmachine Diagram from Alan Dean and %% Justin Sheehy. -module(cowboy_rest). -behaviour(cowboy_sub_protocol). -export([upgrade/4]). -record(state, { env :: cowboy_middleware:env(), method = undefined :: binary(), %% Handler. handler :: atom(), handler_state :: any(), %% Allowed methods. Only used for OPTIONS requests. allowed_methods :: [binary()], %% Media type. content_types_p = [] :: [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'}, atom()}], content_type_a :: undefined | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'}, atom()}, %% Language. languages_p = [] :: [binary()], language_a :: undefined | binary(), %% Charset. charsets_p = [] :: [binary()], charset_a :: undefined | binary(), %% Whether the resource exists. exists = false :: boolean(), %% Cached resource calls. etag :: undefined | no_call | {strong | weak, binary()}, last_modified :: undefined | no_call | calendar:datetime(), expires :: undefined | no_call | calendar:datetime() }). %% @doc Upgrade a HTTP request to the REST protocol. %% %% You do not need to call this function manually. To upgrade to the REST %% protocol, you simply need to return {upgrade, protocol, {@module}} %% in your cowboy_http_handler:init/3 handler function. -spec upgrade(Req, Env, module(), any()) -> {ok, Req, Env} | {error, 500, Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). upgrade(Req, Env, Handler, HandlerOpts) -> try Method = cowboy_req:get(method, Req), case erlang:function_exported(Handler, rest_init, 2) of true -> try Handler:rest_init(Req, HandlerOpts) of {ok, Req2, HandlerState} -> service_available(Req2, #state{env=Env, method=Method, handler=Handler, handler_state=HandlerState}) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Options were ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, rest_init, 2, Class, Reason, HandlerOpts, cowboy_req:to_list(Req), erlang:get_stacktrace()]), {error, 500, Req} end; false -> service_available(Req, #state{env=Env, method=Method, handler=Handler}) end catch throw:{?MODULE, error} -> {error, 500, Req} end. service_available(Req, State) -> expect(Req, State, service_available, true, fun known_methods/2, 503). %% known_methods/2 should return a list of binary methods. known_methods(Req, State=#state{method=Method}) -> case call(Req, State, known_methods) of no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>; Method =:= <<"POST">>; Method =:= <<"PUT">>; Method =:= <<"PATCH">>; Method =:= <<"DELETE">>; Method =:= <<"OPTIONS">> -> next(Req, State, fun uri_too_long/2); no_call -> next(Req, State, 501); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {List, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState}, case lists:member(Method, List) of true -> next(Req2, State2, fun uri_too_long/2); false -> next(Req2, State2, 501) end end. uri_too_long(Req, State) -> expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414). %% allowed_methods/2 should return a list of binary methods. allowed_methods(Req, State=#state{method=Method}) -> case call(Req, State, allowed_methods) of no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> next(Req, State, fun malformed_request/2); no_call when Method =:= <<"OPTIONS">> -> next(Req, State#state{allowed_methods= [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]}, fun malformed_request/2); no_call -> method_not_allowed(Req, State, [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {List, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState}, case lists:member(Method, List) of true when Method =:= <<"OPTIONS">> -> next(Req2, State2#state{allowed_methods=List}, fun malformed_request/2); true -> next(Req2, State2, fun malformed_request/2); false -> method_not_allowed(Req2, State2, List) end end. method_not_allowed(Req, State, []) -> Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), respond(Req2, State, 405); method_not_allowed(Req, State, Methods) -> << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), respond(Req2, State, 405). malformed_request(Req, State) -> expect(Req, State, malformed_request, false, fun is_authorized/2, 400). %% is_authorized/2 should return true or {false, WwwAuthenticateHeader}. is_authorized(Req, State) -> case call(Req, State, is_authorized) of no_call -> forbidden(Req, State); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {true, Req2, HandlerState} -> forbidden(Req2, State#state{handler_state=HandlerState}); {{false, AuthHead}, Req2, HandlerState} -> Req3 = cowboy_req:set_resp_header( <<"www-authenticate">>, AuthHead, Req2), respond(Req3, State#state{handler_state=HandlerState}, 401) end. forbidden(Req, State) -> expect(Req, State, forbidden, false, fun valid_content_headers/2, 403). valid_content_headers(Req, State) -> expect(Req, State, valid_content_headers, true, fun known_content_type/2, 501). known_content_type(Req, State) -> expect(Req, State, known_content_type, true, fun valid_entity_length/2, 415). valid_entity_length(Req, State) -> expect(Req, State, valid_entity_length, true, fun options/2, 413). %% If you need to add additional headers to the response at this point, %% you should do it directly in the options/2 call using set_resp_headers. options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) -> case call(Req, State, options) of no_call when Methods =:= [] -> Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), respond(Req2, State, 200); no_call -> << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), respond(Req2, State, 200); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {ok, Req2, HandlerState} -> respond(Req2, State#state{handler_state=HandlerState}, 200) end; options(Req, State) -> content_types_provided(Req, State). %% content_types_provided/2 should return a list of content types and their %% associated callback function as a tuple: {{Type, SubType, Params}, Fun}. %% Type and SubType are the media type as binary. Params is a list of %% Key/Value tuple, with Key and Value a binary. Fun is the name of the %% callback that will be used to return the content of the response. It is %% given as an atom. %% %% An example of such return value would be: %% {{<<"text">>, <<"html">>, []}, to_html} %% %% Note that it is also possible to return a binary content type that will %% then be parsed by Cowboy. However note that while this may make your %% resources a little more readable, this is a lot less efficient. %% %% An example of such return value would be: %% {<<"text/html">>, to_html} content_types_provided(Req, State) -> case call(Req, State, content_types_provided) of no_call -> State2 = State#state{ content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]}, case cowboy_req:parse_header(<<"accept">>, Req) of {error, badarg} -> respond(Req, State2, 400); {ok, undefined, Req2} -> languages_provided( cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req2), State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}}); {ok, Accept, Req2} -> Accept2 = prioritize_accept(Accept), choose_media_type(Req2, State2, Accept2) end; {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {[], Req2, HandlerState} -> not_acceptable(Req2, State#state{handler_state=HandlerState}); {CTP, Req2, HandlerState} -> CTP2 = [normalize_content_types(P) || P <- CTP], State2 = State#state{ handler_state=HandlerState, content_types_p=CTP2}, case cowboy_req:parse_header(<<"accept">>, Req2) of {error, badarg} -> respond(Req2, State2, 400); {ok, undefined, Req3} -> {PMT, _Fun} = HeadCTP = hd(CTP2), languages_provided( cowboy_req:set_meta(media_type, PMT, Req3), State2#state{content_type_a=HeadCTP}); {ok, Accept, Req3} -> Accept2 = prioritize_accept(Accept), choose_media_type(Req3, State2, Accept2) end end. normalize_content_types({ContentType, Callback}) when is_binary(ContentType) -> {cowboy_http:content_type(ContentType), Callback}; normalize_content_types(Normalized) -> Normalized. prioritize_accept(Accept) -> lists:sort( fun ({MediaTypeA, Quality, _AcceptParamsA}, {MediaTypeB, Quality, _AcceptParamsB}) -> %% Same quality, check precedence in more details. prioritize_mediatype(MediaTypeA, MediaTypeB); ({_MediaTypeA, QualityA, _AcceptParamsA}, {_MediaTypeB, QualityB, _AcceptParamsB}) -> %% Just compare the quality. QualityA > QualityB end, Accept). %% Media ranges can be overridden by more specific media ranges or %% specific media types. If more than one media range applies to a given %% type, the most specific reference has precedence. %% %% We always choose B over A when we can't decide between the two. prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) -> case TypeB of TypeA -> case SubTypeB of SubTypeA -> length(ParamsA) > length(ParamsB); <<"*">> -> true; _Any -> false end; <<"*">> -> true; _Any -> false end. %% Ignoring the rare AcceptParams. Not sure what should be done about them. choose_media_type(Req, State, []) -> not_acceptable(Req, State); choose_media_type(Req, State=#state{content_types_p=CTP}, [MediaType|Tail]) -> match_media_type(Req, State, Tail, CTP, MediaType). match_media_type(Req, State, Accept, [], _MediaType) -> choose_media_type(Req, State, Accept); match_media_type(Req, State, Accept, CTP, MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) -> match_media_type_params(Req, State, Accept, CTP, MediaType); match_media_type(Req, State, Accept, CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail], MediaType = {{Type, SubType_A, _PA}, _QA, _APA}) when SubType_P =:= SubType_A; SubType_A =:= <<"*">> -> match_media_type_params(Req, State, Accept, CTP, MediaType); match_media_type(Req, State, Accept, [_Any|Tail], MediaType) -> match_media_type(Req, State, Accept, Tail, MediaType). match_media_type_params(Req, State, _Accept, [Provided = {{TP, STP, '*'}, _Fun}|_Tail], {{_TA, _STA, Params_A}, _QA, _APA}) -> PMT = {TP, STP, Params_A}, languages_provided(cowboy_req:set_meta(media_type, PMT, Req), State#state{content_type_a=Provided}); match_media_type_params(Req, State, Accept, [Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail], MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) -> case lists:sort(Params_P) =:= lists:sort(Params_A) of true -> languages_provided(cowboy_req:set_meta(media_type, PMT, Req), State#state{content_type_a=Provided}); false -> match_media_type(Req, State, Accept, Tail, MediaType) end. %% languages_provided should return a list of binary values indicating %% which languages are accepted by the resource. %% %% @todo I suppose we should also ask the resource if it wants to %% set a language itself or if it wants it to be automatically chosen. languages_provided(Req, State) -> case call(Req, State, languages_provided) of no_call -> charsets_provided(Req, State); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {[], Req2, HandlerState} -> not_acceptable(Req2, State#state{handler_state=HandlerState}); {LP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, languages_p=LP}, {ok, AcceptLanguage, Req3} = cowboy_req:parse_header(<<"accept-language">>, Req2), case AcceptLanguage of undefined -> set_language(Req3, State2#state{language_a=hd(LP)}); AcceptLanguage -> AcceptLanguage2 = prioritize_languages(AcceptLanguage), choose_language(Req3, State2, AcceptLanguage2) end end. %% A language-range matches a language-tag if it exactly equals the tag, %% or if it exactly equals a prefix of the tag such that the first tag %% character following the prefix is "-". The special range "*", if %% present in the Accept-Language field, matches every tag not matched %% by any other range present in the Accept-Language field. %% %% @todo The last sentence probably means we should always put '*' %% at the end of the list. prioritize_languages(AcceptLanguages) -> lists:sort( fun ({_TagA, QualityA}, {_TagB, QualityB}) -> QualityA > QualityB end, AcceptLanguages). choose_language(Req, State, []) -> not_acceptable(Req, State); choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) -> match_language(Req, State, Tail, LP, Language). match_language(Req, State, Accept, [], _Language) -> choose_language(Req, State, Accept); match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) -> set_language(Req, State#state{language_a=Provided}); match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) -> set_language(Req, State#state{language_a=Provided}); match_language(Req, State, Accept, [Provided|Tail], Language = {Tag, _Quality}) -> Length = byte_size(Tag), case Provided of << Tag:Length/binary, $-, _Any/bits >> -> set_language(Req, State#state{language_a=Provided}); _Any -> match_language(Req, State, Accept, Tail, Language) end. set_language(Req, State=#state{language_a=Language}) -> Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req), charsets_provided(cowboy_req:set_meta(language, Language, Req2), State). %% charsets_provided should return a list of binary values indicating %% which charsets are accepted by the resource. charsets_provided(Req, State) -> case call(Req, State, charsets_provided) of no_call -> set_content_type(Req, State); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {[], Req2, HandlerState} -> not_acceptable(Req2, State#state{handler_state=HandlerState}); {CP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, charsets_p=CP}, {ok, AcceptCharset, Req3} = cowboy_req:parse_header(<<"accept-charset">>, Req2), case AcceptCharset of undefined -> set_content_type(Req3, State2#state{charset_a=hd(CP)}); AcceptCharset -> AcceptCharset2 = prioritize_charsets(AcceptCharset), choose_charset(Req3, State2, AcceptCharset2) end end. %% The special value "*", if present in the Accept-Charset field, %% matches every character set (including ISO-8859-1) which is not %% mentioned elsewhere in the Accept-Charset field. If no "*" is present %% in an Accept-Charset field, then all character sets not explicitly %% mentioned get a quality value of 0, except for ISO-8859-1, which gets %% a quality value of 1 if not explicitly mentioned. prioritize_charsets(AcceptCharsets) -> AcceptCharsets2 = lists:sort( fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) -> QualityA > QualityB end, AcceptCharsets), case lists:keymember(<<"*">>, 1, AcceptCharsets2) of true -> AcceptCharsets2; false -> case lists:keymember(<<"iso-8859-1">>, 1, AcceptCharsets2) of true -> AcceptCharsets2; false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2] end end. choose_charset(Req, State, []) -> not_acceptable(Req, State); choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) -> match_charset(Req, State, Tail, CP, Charset). match_charset(Req, State, Accept, [], _Charset) -> choose_charset(Req, State, Accept); match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) -> set_content_type(Req, State#state{charset_a=Provided}); match_charset(Req, State, Accept, [_|Tail], Charset) -> match_charset(Req, State, Accept, Tail, Charset). set_content_type(Req, State=#state{ content_type_a={{Type, SubType, Params}, _Fun}, charset_a=Charset}) -> ParamsBin = set_content_type_build_params(Params, []), ContentType = [Type, <<"/">>, SubType, ParamsBin], ContentType2 = case Charset of undefined -> ContentType; Charset -> [ContentType, <<"; charset=">>, Charset] end, Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req), encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State). set_content_type_build_params('*', []) -> <<>>; set_content_type_build_params([], []) -> <<>>; set_content_type_build_params([], Acc) -> lists:reverse(Acc); set_content_type_build_params([{Attr, Value}|Tail], Acc) -> set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]). %% @todo Match for identity as we provide nothing else for now. %% @todo Don't forget to set the Content-Encoding header when we reply a body %% and the found encoding is something other than identity. encodings_provided(Req, State) -> variances(Req, State). not_acceptable(Req, State) -> respond(Req, State, 406). %% variances/2 should return a list of headers that will be added %% to the Vary response header. The Accept, Accept-Language, %% Accept-Charset and Accept-Encoding headers do not need to be %% specified. %% %% @todo Do Accept-Encoding too when we handle it. %% @todo Does the order matter? variances(Req, State=#state{content_types_p=CTP, languages_p=LP, charsets_p=CP}) -> Variances = case CTP of [] -> []; [_] -> []; [_|_] -> [<<"accept">>] end, Variances2 = case LP of [] -> Variances; [_] -> Variances; [_|_] -> [<<"accept-language">>|Variances] end, Variances3 = case CP of [] -> Variances2; [_] -> Variances2; [_|_] -> [<<"accept-charset">>|Variances2] end, try variances(Req, State, Variances3) of {Variances4, Req2, State2} -> case [[<<", ">>, V] || V <- Variances4] of [] -> resource_exists(Req2, State2); [[<<", ">>, H]|Variances5] -> Req3 = cowboy_req:set_resp_header( <<"vary">>, [H|Variances5], Req2), resource_exists(Req3, State2) end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, variances, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. variances(Req, State, Variances) -> case unsafe_call(Req, State, variances) of no_call -> {Variances, Req, State}; {HandlerVariances, Req2, HandlerState} -> {Variances ++ HandlerVariances, Req2, State#state{handler_state=HandlerState}} end. resource_exists(Req, State) -> expect(Req, State, resource_exists, true, fun if_match_exists/2, fun if_match_must_not_exist/2). if_match_exists(Req, State) -> State2 = State#state{exists=true}, case cowboy_req:parse_header(<<"if-match">>, Req) of {ok, undefined, Req2} -> if_unmodified_since_exists(Req2, State2); {ok, '*', Req2} -> if_unmodified_since_exists(Req2, State2); {ok, ETagsList, Req2} -> if_match(Req2, State2, ETagsList) end. if_match(Req, State, EtagsList) -> try generate_etag(Req, State) of {Etag, Req2, State2} -> case lists:member(Etag, EtagsList) of true -> if_unmodified_since_exists(Req2, State2); %% Etag may be `undefined' which cannot be a member. false -> precondition_failed(Req2, State2) end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, generate_etag, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. if_match_must_not_exist(Req, State) -> case cowboy_req:header(<<"if-match">>, Req) of {undefined, Req2} -> is_put_to_missing_resource(Req2, State); {_Any, Req2} -> precondition_failed(Req2, State) end. if_unmodified_since_exists(Req, State) -> case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of {ok, undefined, Req2} -> if_none_match_exists(Req2, State); {ok, IfUnmodifiedSince, Req2} -> if_unmodified_since(Req2, State, IfUnmodifiedSince); {error, badarg} -> if_none_match_exists(Req, State) end. %% If LastModified is the atom 'no_call', we continue. if_unmodified_since(Req, State, IfUnmodifiedSince) -> try last_modified(Req, State) of {LastModified, Req2, State2} -> case LastModified > IfUnmodifiedSince of true -> precondition_failed(Req2, State2); false -> if_none_match_exists(Req2, State2) end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, last_modified, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. if_none_match_exists(Req, State) -> case cowboy_req:parse_header(<<"if-none-match">>, Req) of {ok, undefined, Req2} -> if_modified_since_exists(Req2, State); {ok, '*', Req2} -> precondition_is_head_get(Req2, State); {ok, EtagsList, Req2} -> if_none_match(Req2, State, EtagsList) end. if_none_match(Req, State, EtagsList) -> try generate_etag(Req, State) of {Etag, Req2, State2} -> case Etag of undefined -> precondition_failed(Req2, State2); Etag -> case lists:member(Etag, EtagsList) of true -> precondition_is_head_get(Req2, State2); false -> if_modified_since_exists(Req2, State2) end end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, generate_etag, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. precondition_is_head_get(Req, State=#state{method=Method}) when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> not_modified(Req, State); precondition_is_head_get(Req, State) -> precondition_failed(Req, State). if_modified_since_exists(Req, State) -> case cowboy_req:parse_header(<<"if-modified-since">>, Req) of {ok, undefined, Req2} -> method(Req2, State); {ok, IfModifiedSince, Req2} -> if_modified_since_now(Req2, State, IfModifiedSince); {error, badarg} -> method(Req, State) end. if_modified_since_now(Req, State, IfModifiedSince) -> case IfModifiedSince > erlang:universaltime() of true -> method(Req, State); false -> if_modified_since(Req, State, IfModifiedSince) end. if_modified_since(Req, State, IfModifiedSince) -> try last_modified(Req, State) of {no_call, Req2, State2} -> method(Req2, State2); {LastModified, Req2, State2} -> case LastModified > IfModifiedSince of true -> method(Req2, State2); false -> not_modified(Req2, State2) end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, last_modified, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. not_modified(Req, State) -> Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req), try set_resp_etag(Req2, State) of {Req3, State2} -> try set_resp_expires(Req3, State2) of {Req4, State3} -> respond(Req4, State3, 304) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, expires, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req2, State) end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, generate_etag, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req2, State) end. precondition_failed(Req, State) -> respond(Req, State, 412). is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) -> moved_permanently(Req, State, fun is_conflict/2); is_put_to_missing_resource(Req, State) -> previously_existed(Req, State). %% moved_permanently/2 should return either false or {true, Location} %% with Location the full new URI of the resource. moved_permanently(Req, State, OnFalse) -> case call(Req, State, moved_permanently) of {{true, Location}, Req2, HandlerState} -> Req3 = cowboy_req:set_resp_header( <<"location">>, Location, Req2), respond(Req3, State#state{handler_state=HandlerState}, 301); {false, Req2, HandlerState} -> OnFalse(Req2, State#state{handler_state=HandlerState}); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); no_call -> OnFalse(Req, State) end. previously_existed(Req, State) -> expect(Req, State, previously_existed, false, fun (R, S) -> is_post_to_missing_resource(R, S, 404) end, fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end). %% moved_temporarily/2 should return either false or {true, Location} %% with Location the full new URI of the resource. moved_temporarily(Req, State) -> case call(Req, State, moved_temporarily) of {{true, Location}, Req2, HandlerState} -> Req3 = cowboy_req:set_resp_header( <<"location">>, Location, Req2), respond(Req3, State#state{handler_state=HandlerState}, 307); {false, Req2, HandlerState} -> is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); no_call -> is_post_to_missing_resource(Req, State, 410) end. is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) -> allow_missing_post(Req, State, OnFalse); is_post_to_missing_resource(Req, State, OnFalse) -> respond(Req, State, OnFalse). allow_missing_post(Req, State, OnFalse) -> expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse). method(Req, State=#state{method= <<"DELETE">>}) -> delete_resource(Req, State); method(Req, State=#state{method= <<"PUT">>}) -> is_conflict(Req, State); method(Req, State=#state{method=Method}) when Method =:= <<"POST">>; Method =:= <<"PATCH">> -> accept_resource(Req, State); method(Req, State=#state{method=Method}) when Method =:= <<"GET">>; Method =:= <<"HEAD">> -> set_resp_body_etag(Req, State); method(Req, State) -> multiple_choices(Req, State). %% delete_resource/2 should start deleting the resource and return. delete_resource(Req, State) -> expect(Req, State, delete_resource, false, 500, fun delete_completed/2). %% delete_completed/2 indicates whether the resource has been deleted yet. delete_completed(Req, State) -> expect(Req, State, delete_completed, true, fun has_resp_body/2, 202). is_conflict(Req, State) -> expect(Req, State, is_conflict, false, fun accept_resource/2, 409). %% content_types_accepted should return a list of media types and their %% associated callback functions in the same format as content_types_provided. %% %% The callback will then be called and is expected to process the content %% pushed to the resource in the request body. %% %% content_types_accepted SHOULD return a different list %% for each HTTP method. accept_resource(Req, State) -> case call(Req, State, content_types_accepted) of no_call -> respond(Req, State, 415); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {CTA, Req2, HandlerState} -> CTA2 = [normalize_content_types(P) || P <- CTA], State2 = State#state{handler_state=HandlerState}, case cowboy_req:parse_header(<<"content-type">>, Req2) of {ok, ContentType, Req3} -> choose_content_type(Req3, State2, ContentType, CTA2); {error, badarg} -> respond(Req2, State2, 415) end end. %% The special content type '*' will always match. It can be used as a %% catch-all content type for accepting any kind of request content. %% Note that because it will always match, it should be the last of the %% list of content types, otherwise it'll shadow the ones following. choose_content_type(Req, State, _ContentType, []) -> respond(Req, State, 415); choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail]) when Accepted =:= '*'; Accepted =:= ContentType -> process_content_type(Req, State, Fun); %% The special parameter '*' will always match any kind of content type %% parameters. %% Note that because it will always match, it should be the last of the %% list for specific content type, otherwise it'll shadow the ones following. choose_content_type(Req, State, {Type, SubType, Param}, [{{Type, SubType, AcceptedParam}, Fun}|_Tail]) when AcceptedParam =:= '*'; AcceptedParam =:= Param -> process_content_type(Req, State, Fun); choose_content_type(Req, State, ContentType, [_Any|Tail]) -> choose_content_type(Req, State, ContentType, Tail). process_content_type(Req, State=#state{method=Method, handler=Handler, handler_state=HandlerState, exists=Exists}, Fun) -> case call(Req, State, Fun) of no_call -> error_logger:error_msg( "** Cowboy handler ~p terminating; " "function ~p/~p was not exported~n" "** Request was ~p~n** State was ~p~n~n", [Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]), {error, 500, Req}; {halt, Req2, HandlerState2} -> terminate(Req2, State#state{handler_state=HandlerState2}); {true, Req2, HandlerState2} when Exists -> State2 = State#state{handler_state=HandlerState2}, next(Req2, State2, fun has_resp_body/2); {true, Req2, HandlerState2} -> State2 = State#state{handler_state=HandlerState2}, next(Req2, State2, fun maybe_created/2); {false, Req2, HandlerState2} -> State2 = State#state{handler_state=HandlerState2}, respond(Req2, State2, 422); {{true, ResURL}, Req2, HandlerState2} when Method =:= <<"POST">> -> State2 = State#state{handler_state=HandlerState2}, Req3 = cowboy_req:set_resp_header( <<"location">>, ResURL, Req2), if Exists -> respond(Req3, State2, 303); true -> respond(Req3, State2, 201) end end. %% If the resource is new and has been created at another location %% we send a 201. Otherwise we continue as normal. maybe_created(Req, State) -> case cowboy_req:has_resp_header(<<"location">>, Req) of true -> respond(Req, State, 201); false -> has_resp_body(Req, State) end. has_resp_body(Req, State) -> case cowboy_req:has_resp_body(Req) of true -> multiple_choices(Req, State); false -> respond(Req, State, 204) end. %% Set the Etag header if any for the response provided. set_resp_body_etag(Req, State) -> try set_resp_etag(Req, State) of {Req2, State2} -> set_resp_body_last_modified(Req2, State2) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, generate_etag, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. %% Set the Last-Modified header if any for the response provided. set_resp_body_last_modified(Req, State) -> try last_modified(Req, State) of {LastModified, Req2, State2} -> case LastModified of LastModified when is_atom(LastModified) -> set_resp_body_expires(Req2, State2); LastModified -> LastModifiedBin = cowboy_clock:rfc1123(LastModified), Req3 = cowboy_req:set_resp_header( <<"last-modified">>, LastModifiedBin, Req2), set_resp_body_expires(Req3, State2) end catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, last_modified, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. %% Set the Expires header if any for the response provided. set_resp_body_expires(Req, State) -> try set_resp_expires(Req, State) of {Req2, State2} -> set_resp_body(Req2, State2) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [State#state.handler, expires, 2, Class, Reason, State#state.handler_state, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end. %% Set the response headers and call the callback found using %% content_types_provided/2 to obtain the request body and add %% it to the response. set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState, content_type_a={_Type, Callback}}) -> case call(Req, State, Callback) of no_call -> error_logger:error_msg( "** Cowboy handler ~p terminating; " "function ~p/~p was not exported~n" "** Request was ~p~n** State was ~p~n~n", [Handler, Callback, 2, cowboy_req:to_list(Req), HandlerState]), {error, 500, Req}; {halt, Req2, HandlerState2} -> terminate(Req2, State#state{handler_state=HandlerState2}); {Body, Req2, HandlerState2} -> State2 = State#state{handler_state=HandlerState2}, Req3 = case Body of {stream, StreamFun} -> cowboy_req:set_resp_body_fun(StreamFun, Req2); {stream, Len, StreamFun} -> cowboy_req:set_resp_body_fun(Len, StreamFun, Req2); {chunked, StreamFun} -> cowboy_req:set_resp_body_fun(chunked, StreamFun, Req2); _Contents -> cowboy_req:set_resp_body(Body, Req2) end, multiple_choices(Req3, State2) end. multiple_choices(Req, State) -> expect(Req, State, multiple_choices, false, 200, 300). %% Response utility functions. set_resp_etag(Req, State) -> {Etag, Req2, State2} = generate_etag(Req, State), case Etag of undefined -> {Req2, State2}; Etag -> Req3 = cowboy_req:set_resp_header( <<"etag">>, encode_etag(Etag), Req2), {Req3, State2} end. -spec encode_etag({strong | weak, binary()}) -> iolist(). encode_etag({strong, Etag}) -> [$",Etag,$"]; encode_etag({weak, Etag}) -> ["W/\"",Etag,$"]. set_resp_expires(Req, State) -> {Expires, Req2, State2} = expires(Req, State), case Expires of Expires when is_atom(Expires) -> {Req2, State2}; Expires -> ExpiresBin = cowboy_clock:rfc1123(Expires), Req3 = cowboy_req:set_resp_header( <<"expires">>, ExpiresBin, Req2), {Req3, State2} end. %% Info retrieval. No logic. generate_etag(Req, State=#state{etag=no_call}) -> {undefined, Req, State}; generate_etag(Req, State=#state{etag=undefined}) -> case unsafe_call(Req, State, generate_etag) of no_call -> {undefined, Req, State#state{etag=no_call}}; {Etag, Req2, HandlerState} when is_binary(Etag) -> [Etag2] = cowboy_http:entity_tag_match(Etag), {Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}}; {Etag, Req2, HandlerState} -> {Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}} end; generate_etag(Req, State=#state{etag=Etag}) -> {Etag, Req, State}. last_modified(Req, State=#state{last_modified=no_call}) -> {undefined, Req, State}; last_modified(Req, State=#state{last_modified=undefined}) -> case unsafe_call(Req, State, last_modified) of no_call -> {undefined, Req, State#state{last_modified=no_call}}; {LastModified, Req2, HandlerState} -> {LastModified, Req2, State#state{handler_state=HandlerState, last_modified=LastModified}} end; last_modified(Req, State=#state{last_modified=LastModified}) -> {LastModified, Req, State}. expires(Req, State=#state{expires=no_call}) -> {undefined, Req, State}; expires(Req, State=#state{expires=undefined}) -> case unsafe_call(Req, State, expires) of no_call -> {undefined, Req, State#state{expires=no_call}}; {Expires, Req2, HandlerState} -> {Expires, Req2, State#state{handler_state=HandlerState, expires=Expires}} end; expires(Req, State=#state{expires=Expires}) -> {Expires, Req, State}. %% REST primitives. expect(Req, State, Callback, Expected, OnTrue, OnFalse) -> case call(Req, State, Callback) of no_call -> next(Req, State, OnTrue); {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); {Expected, Req2, HandlerState} -> next(Req2, State#state{handler_state=HandlerState}, OnTrue); {_Unexpected, Req2, HandlerState} -> next(Req2, State#state{handler_state=HandlerState}, OnFalse) end. call(Req, State=#state{handler=Handler, handler_state=HandlerState}, Callback) -> case erlang:function_exported(Handler, Callback, 2) of true -> try Handler:Callback(Req, HandlerState) catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, Callback, 2, Class, Reason, HandlerState, cowboy_req:to_list(Req), erlang:get_stacktrace()]), error_terminate(Req, State) end; false -> no_call end. unsafe_call(Req, #state{handler=Handler, handler_state=HandlerState}, Callback) -> case erlang:function_exported(Handler, Callback, 2) of true -> Handler:Callback(Req, HandlerState); false -> no_call end. next(Req, State, Next) when is_function(Next) -> Next(Req, State); next(Req, State, StatusCode) when is_integer(StatusCode) -> respond(Req, State, StatusCode). respond(Req, State, StatusCode) -> {ok, Req2} = cowboy_req:reply(StatusCode, Req), terminate(Req2, State). terminate(Req, State=#state{env=Env}) -> rest_terminate(Req, State), {ok, Req, [{result, ok}|Env]}. -spec error_terminate(cowboy_req:req(), #state{}) -> no_return(). error_terminate(Req, State) -> rest_terminate(Req, State), erlang:raise(throw, {?MODULE, error}, erlang:get_stacktrace()). rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> case erlang:function_exported(Handler, rest_terminate, 2) of true -> ok = Handler:rest_terminate( cowboy_req:lock(Req), HandlerState); false -> ok end. erlang-cowboy-0.8.6+dfsg1/src/cowboy_router.erl000066400000000000000000000520551223335133400215000ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Routing middleware. %% %% Resolve the handler to be used for the request based on the %% routing information found in the dispatch environment value. %% When found, the handler module and associated data are added to %% the environment as the handler and handler_opts values %% respectively. %% %% If the route cannot be found, processing stops with either %% a 400 or a 404 reply. -module(cowboy_router). -behaviour(cowboy_middleware). -export([compile/1]). -export([execute/2]). -type bindings() :: [{atom(), binary()}]. -type tokens() :: [binary()]. -export_type([bindings/0]). -export_type([tokens/0]). -type constraints() :: [{atom(), int} | {atom(), function, fun ((binary()) -> true | {true, any()} | false)}]. -export_type([constraints/0]). -type route_match() :: '_' | iodata(). -type route_path() :: {Path::route_match(), Handler::module(), Opts::any()} | {Path::route_match(), constraints(), Handler::module(), Opts::any()}. -type route_rule() :: {Host::route_match(), Paths::[route_path()]} | {Host::route_match(), constraints(), Paths::[route_path()]}. -type routes() :: [route_rule()]. -export_type([routes/0]). -type dispatch_match() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()]. -type dispatch_path() :: {dispatch_match(), module(), any()}. -type dispatch_rule() :: {Host::dispatch_match(), Paths::[dispatch_path()]}. -opaque dispatch_rules() :: [dispatch_rule()]. -export_type([dispatch_rules/0]). %% @doc Compile a list of routes into the dispatch format used %% by Cowboy's routing. -spec compile(routes()) -> dispatch_rules(). compile(Routes) -> compile(Routes, []). compile([], Acc) -> lists:reverse(Acc); compile([{Host, Paths}|Tail], Acc) -> compile([{Host, [], Paths}|Tail], Acc); compile([{HostMatch, Constraints, Paths}|Tail], Acc) -> HostRules = case HostMatch of '_' -> '_'; _ -> compile_host(HostMatch) end, PathRules = compile_paths(Paths, []), Hosts = case HostRules of '_' -> [{'_', Constraints, PathRules}]; _ -> [{R, Constraints, PathRules} || R <- HostRules] end, compile(Tail, Hosts ++ Acc). compile_host(HostMatch) when is_list(HostMatch) -> compile_host(list_to_binary(HostMatch)); compile_host(HostMatch) when is_binary(HostMatch) -> compile_rules(HostMatch, $., [], [], <<>>). compile_paths([], Acc) -> lists:reverse(Acc); compile_paths([{PathMatch, Handler, Opts}|Tail], Acc) -> compile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc); compile_paths([{PathMatch, Constraints, Handler, Opts}|Tail], Acc) when is_list(PathMatch) -> compile_paths([{iolist_to_binary(PathMatch), Constraints, Handler, Opts}|Tail], Acc); compile_paths([{'_', Constraints, Handler, Opts}|Tail], Acc) -> compile_paths(Tail, [{'_', Constraints, Handler, Opts}] ++ Acc); compile_paths([{<< $/, PathMatch/binary >>, Constraints, Handler, Opts}|Tail], Acc) -> PathRules = compile_rules(PathMatch, $/, [], [], <<>>), Paths = [{lists:reverse(R), Constraints, Handler, Opts} || R <- PathRules], compile_paths(Tail, Paths ++ Acc). compile_rules(<<>>, _, Segments, Rules, <<>>) -> [Segments|Rules]; compile_rules(<<>>, _, Segments, Rules, Acc) -> [[Acc|Segments]|Rules]; compile_rules(<< S, Rest/binary >>, S, Segments, Rules, <<>>) -> compile_rules(Rest, S, Segments, Rules, <<>>); compile_rules(<< S, Rest/binary >>, S, Segments, Rules, Acc) -> compile_rules(Rest, S, [Acc|Segments], Rules, <<>>); compile_rules(<< $:, Rest/binary >>, S, Segments, Rules, <<>>) -> {NameBin, Rest2} = compile_binding(Rest, S, <<>>), Name = binary_to_atom(NameBin, utf8), compile_rules(Rest2, S, Segments, Rules, Name); compile_rules(<< $:, _/binary >>, _, _, _, _) -> erlang:error(badarg); compile_rules(<< $[, $., $., $., $], Rest/binary >>, S, Segments, Rules, Acc) when Acc =:= <<>> -> compile_rules(Rest, S, ['...'|Segments], Rules, Acc); compile_rules(<< $[, $., $., $., $], Rest/binary >>, S, Segments, Rules, Acc) -> compile_rules(Rest, S, ['...', Acc|Segments], Rules, Acc); compile_rules(<< $[, S, Rest/binary >>, S, Segments, Rules, Acc) -> compile_brackets(Rest, S, [Acc|Segments], Rules); compile_rules(<< $[, Rest/binary >>, S, Segments, Rules, <<>>) -> compile_brackets(Rest, S, Segments, Rules); %% Open bracket in the middle of a segment. compile_rules(<< $[, _/binary >>, _, _, _, _) -> erlang:error(badarg); %% Missing an open bracket. compile_rules(<< $], _/binary >>, _, _, _, _) -> erlang:error(badarg); compile_rules(<< C, Rest/binary >>, S, Segments, Rules, Acc) -> compile_rules(Rest, S, Segments, Rules, << Acc/binary, C >>). %% Everything past $: until the segment separator ($. for hosts, %% $/ for paths) or $[ or $] or end of binary is the binding name. compile_binding(<<>>, _, <<>>) -> erlang:error(badarg); compile_binding(Rest = <<>>, _, Acc) -> {Acc, Rest}; compile_binding(Rest = << C, _/binary >>, S, Acc) when C =:= S; C =:= $[; C =:= $] -> {Acc, Rest}; compile_binding(<< C, Rest/binary >>, S, Acc) -> compile_binding(Rest, S, << Acc/binary, C >>). compile_brackets(Rest, S, Segments, Rules) -> {Bracket, Rest2} = compile_brackets_split(Rest, <<>>, 0), Rules1 = compile_rules(Rest2, S, Segments, [], <<>>), Rules2 = compile_rules(<< Bracket/binary, Rest2/binary >>, S, Segments, [], <<>>), Rules ++ Rules2 ++ Rules1. %% Missing a close bracket. compile_brackets_split(<<>>, _, _) -> erlang:error(badarg); %% Make sure we don't confuse the closing bracket we're looking for. compile_brackets_split(<< C, Rest/binary >>, Acc, N) when C =:= $[ -> compile_brackets_split(Rest, << Acc/binary, C >>, N + 1); compile_brackets_split(<< C, Rest/binary >>, Acc, N) when C =:= $], N > 0 -> compile_brackets_split(Rest, << Acc/binary, C >>, N - 1); %% That's the right one. compile_brackets_split(<< $], Rest/binary >>, Acc, 0) -> {Acc, Rest}; compile_brackets_split(<< C, Rest/binary >>, Acc, N) -> compile_brackets_split(Rest, << Acc/binary, C >>, N). %% @private -spec execute(Req, Env) -> {ok, Req, Env} | {error, 400 | 404, Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). execute(Req, Env) -> {_, Dispatch} = lists:keyfind(dispatch, 1, Env), [Host, Path] = cowboy_req:get([host, path], Req), case match(Dispatch, Host, Path) of {ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} -> Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req), {ok, Req2, [{handler, Handler}, {handler_opts, HandlerOpts}|Env]}; {error, notfound, host} -> {error, 400, Req}; {error, badrequest, path} -> {error, 400, Req}; {error, notfound, path} -> {error, 404, Req} end. %% Internal. %% @doc Match hostname tokens and path tokens against dispatch rules. %% %% It is typically used for matching tokens for the hostname and path of %% the request against a global dispatch rule for your listener. %% %% Dispatch rules are a list of {Hostname, PathRules} tuples, with %% PathRules being a list of {Path, HandlerMod, HandlerOpts}. %% %% Hostname and Path are match rules and can be either the %% atom '_', which matches everything, `<<"*">>', which match the %% wildcard path, or a list of tokens. %% %% Each token can be either a binary, the atom '_', %% the atom '...' or a named atom. A binary token must match exactly, %% '_' matches everything for a single token, '...' matches %% everything for the rest of the tokens and a named atom will bind the %% corresponding token value and return it. %% %% The list of hostname tokens is reversed before matching. For example, if %% we were to match "www.ninenines.eu", we would first match "eu", then %% "ninenines", then "www". This means that in the context of hostnames, %% the '...' atom matches properly the lower levels of the domain %% as would be expected. %% %% When a result is found, this function will return the handler module and %% options found in the dispatch list, a key-value list of bindings and %% the tokens that were matched by the '...' atom for both the %% hostname and path. -spec match(dispatch_rules(), Host::binary() | tokens(), Path::binary()) -> {ok, module(), any(), bindings(), HostInfo::undefined | tokens(), PathInfo::undefined | tokens()} | {error, notfound, host} | {error, notfound, path} | {error, badrequest, path}. match([], _, _) -> {error, notfound, host}; %% If the host is '_' then there can be no constraints. match([{'_', [], PathMatchs}|_Tail], _, Path) -> match_path(PathMatchs, undefined, Path, []); match([{HostMatch, Constraints, PathMatchs}|Tail], Tokens, Path) when is_list(Tokens) -> case list_match(Tokens, HostMatch, []) of false -> match(Tail, Tokens, Path); {true, Bindings, HostInfo} -> HostInfo2 = case HostInfo of undefined -> undefined; _ -> lists:reverse(HostInfo) end, case check_constraints(Constraints, Bindings) of {ok, Bindings2} -> match_path(PathMatchs, HostInfo2, Path, Bindings2); nomatch -> match(Tail, Tokens, Path) end end; match(Dispatch, Host, Path) -> match(Dispatch, split_host(Host), Path). -spec match_path([dispatch_path()], HostInfo::undefined | tokens(), binary() | tokens(), bindings()) -> {ok, module(), any(), bindings(), HostInfo::undefined | tokens(), PathInfo::undefined | tokens()} | {error, notfound, path} | {error, badrequest, path}. match_path([], _, _, _) -> {error, notfound, path}; %% If the path is '_' then there can be no constraints. match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) -> {ok, Handler, Opts, Bindings, HostInfo, undefined}; match_path([{<<"*">>, _Constraints, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) -> {ok, Handler, Opts, Bindings, HostInfo, undefined}; match_path([{PathMatch, Constraints, Handler, Opts}|Tail], HostInfo, Tokens, Bindings) when is_list(Tokens) -> case list_match(Tokens, PathMatch, Bindings) of false -> match_path(Tail, HostInfo, Tokens, Bindings); {true, PathBinds, PathInfo} -> case check_constraints(Constraints, PathBinds) of {ok, PathBinds2} -> {ok, Handler, Opts, PathBinds2, HostInfo, PathInfo}; nomatch -> match_path(Tail, HostInfo, Tokens, Bindings) end end; match_path(_Dispatch, _HostInfo, badrequest, _Bindings) -> {error, badrequest, path}; match_path(Dispatch, HostInfo, Path, Bindings) -> match_path(Dispatch, HostInfo, split_path(Path), Bindings). check_constraints([], Bindings) -> {ok, Bindings}; check_constraints([Constraint|Tail], Bindings) -> Name = element(1, Constraint), case lists:keyfind(Name, 1, Bindings) of false -> check_constraints(Tail, Bindings); {_, Value} -> case check_constraint(Constraint, Value) of true -> check_constraints(Tail, Bindings); {true, Value2} -> Bindings2 = lists:keyreplace(Name, 1, Bindings, {Name, Value2}), check_constraints(Tail, Bindings2); false -> nomatch end end. check_constraint({_, int}, Value) -> try {true, list_to_integer(binary_to_list(Value))} catch _:_ -> false end; check_constraint({_, function, Fun}, Value) -> Fun(Value). %% @doc Split a hostname into a list of tokens. -spec split_host(binary()) -> tokens(). split_host(Host) -> split_host(Host, []). split_host(Host, Acc) -> case binary:match(Host, <<".">>) of nomatch when Host =:= <<>> -> Acc; nomatch -> [Host|Acc]; {Pos, _} -> << Segment:Pos/binary, _:8, Rest/bits >> = Host, false = byte_size(Segment) == 0, split_host(Rest, [Segment|Acc]) end. %% @doc Split a path into a list of path segments. %% %% Following RFC2396, this function may return path segments containing any %% character, including / if, and only if, a / was escaped %% and part of a path segment. -spec split_path(binary()) -> tokens(). split_path(<< $/, Path/bits >>) -> split_path(Path, []); split_path(_) -> badrequest. split_path(Path, Acc) -> try case binary:match(Path, <<"/">>) of nomatch when Path =:= <<>> -> lists:reverse([cowboy_http:urldecode(S) || S <- Acc]); nomatch -> lists:reverse([cowboy_http:urldecode(S) || S <- [Path|Acc]]); {Pos, _} -> << Segment:Pos/binary, _:8, Rest/bits >> = Path, split_path(Rest, [Segment|Acc]) end catch error:badarg -> badrequest end. -spec list_match(tokens(), dispatch_match(), bindings()) -> {true, bindings(), undefined | tokens()} | false. %% Atom '...' matches any trailing path, stop right now. list_match(List, ['...'], Binds) -> {true, Binds, List}; %% Atom '_' matches anything, continue. list_match([_E|Tail], ['_'|TailMatch], Binds) -> list_match(Tail, TailMatch, Binds); %% Both values match, continue. list_match([E|Tail], [E|TailMatch], Binds) -> list_match(Tail, TailMatch, Binds); %% Bind E to the variable name V and continue, %% unless V was already defined and E isn't identical to the previous value. list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) -> case lists:keyfind(V, 1, Binds) of {_, E} -> list_match(Tail, TailMatch, Binds); {_, _} -> false; false -> list_match(Tail, TailMatch, [{V, E}|Binds]) end; %% Match complete. list_match([], [], Binds) -> {true, Binds, undefined}; %% Values don't match, stop. list_match(_List, _Match, _Binds) -> false. %% Tests. -ifdef(TEST). compile_test_() -> %% {Routes, Result} Tests = [ %% Match any host and path. {[{'_', [{'_', h, o}]}], [{'_', [], [{'_', [], h, o}]}]}, {[{"cowboy.example.org", [{"/", ha, oa}, {"/path/to/resource", hb, ob}]}], [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [ {[], [], ha, oa}, {[<<"path">>, <<"to">>, <<"resource">>], [], hb, ob}]}]}, {[{'_', [{"/path/to/resource/", h, o}]}], [{'_', [], [{[<<"path">>, <<"to">>, <<"resource">>], [], h, o}]}]}, {[{'_', [{"/путь/к/ресурсу/", h, o}]}], [{'_', [], [{[<<"путь">>, <<"к">>, <<"ресурсу">>], [], h, o}]}]}, {[{"cowboy.example.org.", [{'_', h, o}]}], [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]}, {[{".cowboy.example.org", [{'_', h, o}]}], [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]}, {[{"некий.сайт.рф.", [{'_', h, o}]}], [{[<<"рф">>, <<"сайт">>, <<"некий">>], [], [{'_', [], h, o}]}]}, {[{":subdomain.example.org", [{"/hats/:name/prices", h, o}]}], [{[<<"org">>, <<"example">>, subdomain], [], [ {[<<"hats">>, name, <<"prices">>], [], h, o}]}]}, {[{"ninenines.:_", [{"/hats/:_", h, o}]}], [{['_', <<"ninenines">>], [], [{[<<"hats">>, '_'], [], h, o}]}]}, {[{"[www.]ninenines.eu", [{"/horses", h, o}, {"/hats/[page/:number]", h, o}]}], [ {[<<"eu">>, <<"ninenines">>], [], [ {[<<"horses">>], [], h, o}, {[<<"hats">>], [], h, o}, {[<<"hats">>, <<"page">>, number], [], h, o}]}, {[<<"eu">>, <<"ninenines">>, <<"www">>], [], [ {[<<"horses">>], [], h, o}, {[<<"hats">>], [], h, o}, {[<<"hats">>, <<"page">>, number], [], h, o}]}]}, {[{'_', [{"/hats/[page/[:number]]", h, o}]}], [{'_', [], [ {[<<"hats">>], [], h, o}, {[<<"hats">>, <<"page">>], [], h, o}, {[<<"hats">>, <<"page">>, number], [], h, o}]}]}, {[{"[...]ninenines.eu", [{"/hats/[...]", h, o}]}], [{[<<"eu">>, <<"ninenines">>, '...'], [], [ {[<<"hats">>, '...'], [], h, o}]}]} ], [{lists:flatten(io_lib:format("~p", [Rt])), fun() -> Rs = compile(Rt) end} || {Rt, Rs} <- Tests]. split_host_test_() -> %% {Host, Result} Tests = [ {<<"">>, []}, {<<"*">>, [<<"*">>]}, {<<"cowboy.ninenines.eu">>, [<<"eu">>, <<"ninenines">>, <<"cowboy">>]}, {<<"ninenines.eu">>, [<<"eu">>, <<"ninenines">>]}, {<<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>, [<<"z">>, <<"y">>, <<"x">>, <<"w">>, <<"v">>, <<"u">>, <<"t">>, <<"s">>, <<"r">>, <<"q">>, <<"p">>, <<"o">>, <<"n">>, <<"m">>, <<"l">>, <<"k">>, <<"j">>, <<"i">>, <<"h">>, <<"g">>, <<"f">>, <<"e">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>]} ], [{H, fun() -> R = split_host(H) end} || {H, R} <- Tests]. split_path_test_() -> %% {Path, Result, QueryString} Tests = [ {<<"/">>, []}, {<<"/extend//cowboy">>, [<<"extend">>, <<>>, <<"cowboy">>]}, {<<"/users">>, [<<"users">>]}, {<<"/users/42/friends">>, [<<"users">>, <<"42">>, <<"friends">>]}, {<<"/users/a+b/c%21d">>, [<<"users">>, <<"a b">>, <<"c!d">>]} ], [{P, fun() -> R = split_path(P) end} || {P, R} <- Tests]. match_test_() -> Dispatch = [ {[<<"eu">>, <<"ninenines">>, '_', <<"www">>], [], [ {[<<"users">>, '_', <<"mails">>], [], match_any_subdomain_users, []} ]}, {[<<"eu">>, <<"ninenines">>], [], [ {[<<"users">>, id, <<"friends">>], [], match_extend_users_friends, []}, {'_', [], match_extend, []} ]}, {[var, <<"ninenines">>], [], [ {[<<"threads">>, var], [], match_duplicate_vars, [we, {expect, two}, var, here]} ]}, {[ext, <<"erlang">>], [], [ {'_', [], match_erlang_ext, []} ]}, {'_', [], [ {[<<"users">>, id, <<"friends">>], [], match_users_friends, []}, {'_', [], match_any, []} ]} ], %% {Host, Path, Result} Tests = [ {<<"any">>, <<"/">>, {ok, match_any, [], []}}, {<<"www.any.ninenines.eu">>, <<"/users/42/mails">>, {ok, match_any_subdomain_users, [], []}}, {<<"www.ninenines.eu">>, <<"/users/42/mails">>, {ok, match_any, [], []}}, {<<"www.ninenines.eu">>, <<"/">>, {ok, match_any, [], []}}, {<<"www.any.ninenines.eu">>, <<"/not_users/42/mails">>, {error, notfound, path}}, {<<"ninenines.eu">>, <<"/">>, {ok, match_extend, [], []}}, {<<"ninenines.eu">>, <<"/users/42/friends">>, {ok, match_extend_users_friends, [], [{id, <<"42">>}]}}, {<<"erlang.fr">>, '_', {ok, match_erlang_ext, [], [{ext, <<"fr">>}]}}, {<<"any">>, <<"/users/444/friends">>, {ok, match_users_friends, [], [{id, <<"444">>}]}} ], [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() -> {ok, Handler, Opts, Binds, undefined, undefined} = match(Dispatch, H, P) end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests]. match_info_test_() -> Dispatch = [ {[<<"eu">>, <<"ninenines">>, <<"www">>], [], [ {[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], [], match_path, []} ]}, {[<<"eu">>, <<"ninenines">>, '...'], [], [ {'_', [], match_any, []} ]}, {[<<"рф">>, <<"сайт">>], [], [ {[<<"путь">>, '...'], [], match_path, []} ]} ], Tests = [ {<<"ninenines.eu">>, <<"/">>, {ok, match_any, [], [], [], undefined}}, {<<"bugs.ninenines.eu">>, <<"/">>, {ok, match_any, [], [], [<<"bugs">>], undefined}}, {<<"cowboy.bugs.ninenines.eu">>, <<"/">>, {ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}}, {<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>, {ok, match_path, [], [], undefined, []}}, {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>, {ok, match_path, [], [], undefined, [<<"path_info">>]}}, {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>, {ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}}, {<<"сайт.рф">>, <<"/путь/домой">>, {ok, match_path, [], [], undefined, [<<"домой">>]}} ], [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() -> R = match(Dispatch, H, P) end} || {H, P, R} <- Tests]. match_constraints_test() -> Dispatch = [{'_', [], [{[<<"path">>, value], [{value, int}], match, []}]}], {ok, _, [], [{value, 123}], _, _} = match(Dispatch, <<"ninenines.eu">>, <<"/path/123">>), {ok, _, [], [{value, 123}], _, _} = match(Dispatch, <<"ninenines.eu">>, <<"/path/123/">>), {error, notfound, path} = match(Dispatch, <<"ninenines.eu">>, <<"/path/NaN/">>), Dispatch2 = [{'_', [], [{[<<"path">>, username], [{username, function, fun(Value) -> Value =:= cowboy_bstr:to_lower(Value) end}], match, []}]}], {ok, _, [], [{username, <<"essen">>}], _, _} = match(Dispatch2, <<"ninenines.eu">>, <<"/path/essen">>), {error, notfound, path} = match(Dispatch2, <<"ninenines.eu">>, <<"/path/ESSEN">>), ok. match_same_bindings_test() -> Dispatch = [{[same, same], [], [{'_', [], match, []}]}], {ok, _, [], [{same, <<"eu">>}], _, _} = match(Dispatch, <<"eu.eu">>, <<"/">>), {error, notfound, host} = match(Dispatch, <<"ninenines.eu">>, <<"/">>), Dispatch2 = [{[<<"eu">>, <<"ninenines">>, user], [], [{[<<"path">>, user], [], match, []}]}], {ok, _, [], [{user, <<"essen">>}], _, _} = match(Dispatch2, <<"essen.ninenines.eu">>, <<"/path/essen">>), {ok, _, [], [{user, <<"essen">>}], _, _} = match(Dispatch2, <<"essen.ninenines.eu">>, <<"/path/essen/">>), {error, notfound, path} = match(Dispatch2, <<"essen.ninenines.eu">>, <<"/path/notessen">>), Dispatch3 = [{'_', [], [{[same, same], [], match, []}]}], {ok, _, [], [{same, <<"path">>}], _, _} = match(Dispatch3, <<"ninenines.eu">>, <<"/path/path">>), {error, notfound, path} = match(Dispatch3, <<"ninenines.eu">>, <<"/path/to">>), ok. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_spdy.erl000066400000000000000000000432731223335133400211410ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc SPDY protocol handler. %% %% The available options are: %%
%%
%% %% Note that there is no need to monitor these processes when using Cowboy as %% an application as it already supervises them under the listener supervisor. -module(cowboy_spdy). %% API. -export([start_link/4]). %% Internal. -export([init/5]). -export([system_continue/3]). -export([system_terminate/4]). -export([system_code_change/4]). %% Internal request process. -export([request_init/9]). -export([resume/5]). -export([reply/4]). -export([stream_reply/3]). -export([stream_data/2]). -export([stream_close/1]). %% Internal transport functions. -export([name/0]). -export([send/2]). -export([sendfile/2]). -record(child, { streamid :: non_neg_integer(), pid :: pid(), input = nofin :: fin | nofin, output = nofin :: fin | nofin }). -record(state, { parent = undefined :: pid(), socket, transport, buffer = <<>> :: binary(), middlewares, env, onrequest, onresponse, peer, zdef, zinf, last_streamid = 0 :: non_neg_integer(), children = [] :: [#child{}] }). -record(special_headers, { method, path, version, host, scheme %% @todo We don't use it. }). -type opts() :: []. -export_type([opts/0]). -include("cowboy_spdy.hrl"). %% API. %% @doc Start a SPDY protocol process. -spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}. start_link(Ref, Socket, Transport, Opts) -> proc_lib:start_link(?MODULE, init, [self(), Ref, Socket, Transport, Opts]). %% Internal. %% @doc Faster alternative to proplists:get_value/3. %% @private get_value(Key, Opts, Default) -> case lists:keyfind(Key, 1, Opts) of {_, Value} -> Value; _ -> Default end. %% @private -spec init(pid(), ranch:ref(), inet:socket(), module(), opts()) -> ok. init(Parent, Ref, Socket, Transport, Opts) -> process_flag(trap_exit, true), ok = proc_lib:init_ack(Parent, {ok, self()}), {ok, Peer} = Transport:peername(Socket), Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]), Env = [{listener, Ref}|get_value(env, Opts, [])], OnRequest = get_value(onrequest, Opts, undefined), OnResponse = get_value(onresponse, Opts, undefined), Zdef = zlib:open(), ok = zlib:deflateInit(Zdef), _ = zlib:deflateSetDictionary(Zdef, ?ZDICT), Zinf = zlib:open(), ok = zlib:inflateInit(Zinf), ok = ranch:accept_ack(Ref), loop(#state{parent=Parent, socket=Socket, transport=Transport, middlewares=Middlewares, env=Env, onrequest=OnRequest, onresponse=OnResponse, peer=Peer, zdef=Zdef, zinf=Zinf}). loop(State=#state{parent=Parent, socket=Socket, transport=Transport, buffer=Buffer, children=Children}) -> {OK, Closed, Error} = Transport:messages(), Transport:setopts(Socket, [{active, once}]), receive {OK, Socket, Data} -> Data2 = << Buffer/binary, Data/binary >>, case Data2 of << _:40, Length:24, _/bits >> when byte_size(Data2) >= Length + 8 -> Length2 = Length + 8, << Frame:Length2/binary, Rest/bits >> = Data2, control_frame(State#state{buffer=Rest}, Frame); Rest -> loop(State#state{buffer=Rest}) end; {Closed, Socket} -> terminate(State); {Error, Socket, _Reason} -> terminate(State); {reply, {Pid, StreamID}, Status, Headers} when Pid =:= self() -> Child = #child{output=nofin} = lists:keyfind(StreamID, #child.streamid, Children), syn_reply(State, fin, StreamID, Status, Headers), Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child#child{output=fin}), loop(State#state{children=Children2}); {reply, {Pid, StreamID}, Status, Headers, Body} when Pid =:= self() -> Child = #child{output=nofin} = lists:keyfind(StreamID, #child.streamid, Children), syn_reply(State, nofin, StreamID, Status, Headers), data(State, fin, StreamID, Body), Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child#child{output=fin}), loop(State#state{children=Children2}); {stream_reply, {Pid, StreamID}, Status, Headers} when Pid =:= self() -> #child{output=nofin} = lists:keyfind(StreamID, #child.streamid, Children), syn_reply(State, nofin, StreamID, Status, Headers), loop(State); {stream_data, {Pid, StreamID}, Data} when Pid =:= self() -> #child{output=nofin} = lists:keyfind(StreamID, #child.streamid, Children), data(State, nofin, StreamID, Data), loop(State); {stream_close, {Pid, StreamID}} when Pid =:= self() -> Child = #child{output=nofin} = lists:keyfind(StreamID, #child.streamid, Children), data(State, fin, StreamID), Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child#child{output=fin}), loop(State#state{children=Children2}); {sendfile, {Pid, StreamID}, Filepath} when Pid =:= self() -> Child = #child{output=nofin} = lists:keyfind(StreamID, #child.streamid, Children), data_from_file(State, StreamID, Filepath), Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child#child{output=fin}), loop(State#state{children=Children2}); {'EXIT', Parent, Reason} -> exit(Reason); {'EXIT', Pid, _} -> Children2 = lists:keydelete(Pid, #child.pid, Children), loop(State#state{children=Children2}); {system, From, Request} -> sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State); %% Calls from the supervisor module. {'$gen_call', {To, Tag}, which_children} -> Children = [{?MODULE, Pid, worker, [?MODULE]} || #child{pid=Pid} <- Children], To ! {Tag, Children}, loop(State); {'$gen_call', {To, Tag}, count_children} -> NbChildren = length(Children), Counts = [{specs, 1}, {active, NbChildren}, {supervisors, 0}, {workers, NbChildren}], To ! {Tag, Counts}, loop(State); {'$gen_call', {To, Tag}, _} -> To ! {Tag, {error, ?MODULE}}, loop(State) after 60000 -> goaway(State, ok), terminate(State) end. system_continue(_, _, State) -> loop(State). -spec system_terminate(any(), _, _, _) -> no_return(). system_terminate(Reason, _, _, _) -> exit(Reason). system_code_change(Misc, _, _, _) -> {ok, Misc}. %% We do not support SYN_STREAM with FLAG_UNIDIRECTIONAL set. control_frame(State, << 1:1, 3:15, 1:16, _:6, 1:1, _:26, StreamID:31, _/bits >>) -> rst_stream(State, StreamID, internal_error), loop(State); %% We do not support Associated-To-Stream-ID and CREDENTIAL Slot. control_frame(State, << 1:1, 3:15, 1:16, _:33, StreamID:31, _:1, AssocToStreamID:31, _:8, Slot:8, _/bits >>) when AssocToStreamID =/= 0; Slot =/= 0 -> rst_stream(State, StreamID, internal_error), loop(State); %% SYN_STREAM %% %% Erlang does not allow us to control the priority of processes %% so we ignore that value entirely. control_frame(State=#state{middlewares=Middlewares, env=Env, onrequest=OnRequest, onresponse=OnResponse, peer=Peer, zinf=Zinf, children=Children}, << 1:1, 3:15, 1:16, Flags:8, _:25, StreamID:31, _:32, _Priority:3, _:13, Rest/bits >>) -> IsFin = case Flags of 1 -> fin; 0 -> nofin end, [<< NbHeaders:32, Rest2/bits >>] = try zlib:inflate(Zinf, Rest) catch _:_ -> ok = zlib:inflateSetDictionary(Zinf, ?ZDICT), zlib:inflate(Zinf, <<>>) end, case syn_stream_headers(Rest2, NbHeaders, [], #special_headers{}) of {ok, Headers, Special} -> Pid = spawn_link(?MODULE, request_init, [self(), StreamID, Peer, Headers, OnRequest, OnResponse, Env, Middlewares, Special]), loop(State#state{last_streamid=StreamID, children=[#child{streamid=StreamID, pid=Pid, input=IsFin, output=nofin}|Children]}); {error, badname} -> rst_stream(State, StreamID, protocol_error), loop(State#state{last_streamid=StreamID}); {error, special} -> rst_stream(State, StreamID, protocol_error), loop(State#state{last_streamid=StreamID}) end; %% SYN_REPLY control_frame(State, << 1:1, 3:15, 2:16, _/bits >>) -> error_logger:error_msg("Ignored SYN_REPLY control frame~n"), loop(State); %% RST_STREAM control_frame(State, << 1:1, 3:15, 3:16, _Flags:8, _Length:24, _:1, _StreamID:31, StatusCode:32 >>) -> Status = case StatusCode of 1 -> protocol_error; 2 -> invalid_stream; 3 -> refused_stream; 4 -> unsupported_version; 5 -> cancel; 6 -> internal_error; 7 -> flow_control_error; 8 -> stream_in_use; 9 -> stream_already_closed; 10 -> invalid_credentials; 11 -> frame_too_large end, error_logger:error_msg("Received RST_STREAM control frame: ~p~n", [Status]), %% @todo Stop StreamID. loop(State); %% SETTINGS control_frame(State, << 1:1, 3:15, 4:16, 0:8, _:24, NbEntries:32, Rest/bits >>) -> Settings = [begin Name = case ID of 1 -> upload_bandwidth; 2 -> download_bandwidth; 3 -> round_trip_time; 4 -> max_concurrent_streams; 5 -> current_cwnd; 6 -> download_retrans_rate; 7 -> initial_window_size; 8 -> client_certificate_vector_size end, {Flags, Name, Value} end || << Flags:8, ID:24, Value:32 >> <= Rest], if NbEntries =/= length(Settings) -> goaway(State, protocol_error), terminate(State); true -> error_logger:error_msg("Ignored SETTINGS control frame: ~p~n", [Settings]), loop(State) end; %% PING initiated by the server; ignore, we don't send any control_frame(State, << 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>) when PingID rem 2 =:= 0 -> error_logger:error_msg("Ignored PING control frame: ~p~n", [PingID]), loop(State); %% PING initiated by the client; send it back control_frame(State=#state{socket=Socket, transport=Transport}, Data = << 1:1, 3:15, 6:16, 0:8, 4:24, _:32 >>) -> Transport:send(Socket, Data), loop(State); %% GOAWAY control_frame(State, << 1:1, 3:15, 7:16, _/bits >>) -> error_logger:error_msg("Ignored GOAWAY control frame~n"), loop(State); %% HEADERS control_frame(State, << 1:1, 3:15, 8:16, _/bits >>) -> error_logger:error_msg("Ignored HEADERS control frame~n"), loop(State); %% WINDOW_UPDATE control_frame(State, << 1:1, 3:15, 9:16, 0:8, _/bits >>) -> error_logger:error_msg("Ignored WINDOW_UPDATE control frame~n"), loop(State); %% CREDENTIAL control_frame(State, << 1:1, 3:15, 10:16, _/bits >>) -> error_logger:error_msg("Ignored CREDENTIAL control frame~n"), loop(State); %% ??? control_frame(State, _) -> goaway(State, protocol_error), terminate(State). %% @todo We must wait for the children to finish here, %% but only up to N milliseconds. Then we shutdown. terminate(_State) -> ok. syn_stream_headers(<<>>, 0, Acc, Special=#special_headers{ method=Method, path=Path, version=Version, host=Host, scheme=Scheme}) -> if Method =:= undefined; Path =:= undefined; Version =:= undefined; Host =:= undefined; Scheme =:= undefined -> {error, special}; true -> {ok, lists:reverse(Acc), Special} end; syn_stream_headers(<< 0:32, _Rest/bits >>, _NbHeaders, _Acc, _Special) -> {error, badname}; syn_stream_headers(<< NameLen:32, Rest/bits >>, NbHeaders, Acc, Special) -> << Name:NameLen/binary, ValueLen:32, Rest2/bits >> = Rest, << Value:ValueLen/binary, Rest3/bits >> = Rest2, case Name of <<":host">> -> syn_stream_headers(Rest3, NbHeaders - 1, [{<<"host">>, Value}|Acc], Special#special_headers{host=Value}); <<":method">> -> syn_stream_headers(Rest3, NbHeaders - 1, Acc, Special#special_headers{method=Value}); <<":path">> -> syn_stream_headers(Rest3, NbHeaders - 1, Acc, Special#special_headers{path=Value}); <<":version">> -> syn_stream_headers(Rest3, NbHeaders - 1, Acc, Special#special_headers{version=Value}); <<":scheme">> -> syn_stream_headers(Rest3, NbHeaders - 1, Acc, Special#special_headers{scheme=Value}); _ -> syn_stream_headers(Rest3, NbHeaders - 1, [{Name, Value}|Acc], Special) end. syn_reply(#state{socket=Socket, transport=Transport, zdef=Zdef}, IsFin, StreamID, Status, Headers) -> Headers2 = [{<<":status">>, Status}, {<<":version">>, <<"HTTP/1.1">>}|Headers], NbHeaders = length(Headers2), HeaderBlock = [begin NameLen = byte_size(Name), ValueLen = iolist_size(Value), [<< NameLen:32, Name/binary, ValueLen:32 >>, Value] end || {Name, Value} <- Headers2], HeaderBlock2 = [<< NbHeaders:32 >>, HeaderBlock], HeaderBlock3 = zlib:deflate(Zdef, HeaderBlock2, full), Flags = case IsFin of fin -> 1; nofin -> 0 end, Len = 4 + iolist_size(HeaderBlock3), Transport:send(Socket, [ << 1:1, 3:15, 2:16, Flags:8, Len:24, 0:1, StreamID:31 >>, HeaderBlock3]). rst_stream(#state{socket=Socket, transport=Transport}, StreamID, Status) -> StatusCode = case Status of protocol_error -> 1; %% invalid_stream -> 2; %% refused_stream -> 3; %% unsupported_version -> 4; %% cancel -> 5; internal_error -> 6 %% flow_control_error -> 7; %% stream_in_use -> 8; %% stream_already_closed -> 9; %% invalid_credentials -> 10; %% frame_too_large -> 11 end, Transport:send(Socket, << 1:1, 3:15, 3:16, 0:8, 8:24, 0:1, StreamID:31, StatusCode:32 >>). goaway(#state{socket=Socket, transport=Transport, last_streamid=LastStreamID}, Status) -> StatusCode = case Status of ok -> 0; protocol_error -> 1 %% internal_error -> 2 end, Transport:send(Socket, << 1:1, 3:15, 7:16, 0:8, 8:24, 0:1, LastStreamID:31, StatusCode:32 >>). data(#state{socket=Socket, transport=Transport}, fin, StreamID) -> Transport:send(Socket, << 0:1, StreamID:31, 1:8, 0:24 >>). data(#state{socket=Socket, transport=Transport}, IsFin, StreamID, Data) -> Flags = case IsFin of fin -> 1; nofin -> 0 end, Len = iolist_size(Data), Transport:send(Socket, [ << 0:1, StreamID:31, Flags:8, Len:24 >>, Data]). data_from_file(#state{socket=Socket, transport=Transport}, StreamID, Filepath) -> {ok, IoDevice} = file:open(Filepath, [read, binary, raw]), data_from_file(Socket, Transport, StreamID, IoDevice). data_from_file(Socket, Transport, StreamID, IoDevice) -> case file:read(IoDevice, 16#1fff) of eof -> _ = Transport:send(Socket, << 0:1, StreamID:31, 1:8, 0:24 >>), ok; {ok, Data} -> Len = byte_size(Data), Data2 = [<< 0:1, StreamID:31, 0:8, Len:24 >>, Data], case Transport:send(Socket, Data2) of ok -> data_from_file(Socket, Transport, StreamID, IoDevice); {error, _} -> ok end end. %% Request process. request_init(Parent, StreamID, Peer, Headers, OnRequest, OnResponse, Env, Middlewares, #special_headers{method=Method, path=Path, version=Version, host=Host}) -> Version2 = parse_version(Version), {Host2, Port} = cowboy_protocol:parse_host(Host, <<>>), {Path2, Query} = parse_path(Path, <<>>), Req = cowboy_req:new({Parent, StreamID}, ?MODULE, Peer, Method, Path2, Query, Version2, Headers, Host2, Port, <<>>, true, false, OnResponse), case OnRequest of undefined -> execute(Req, Env, Middlewares); _ -> Req2 = OnRequest(Req), case cowboy_req:get(resp_state, Req2) of waiting -> execute(Req2, Env, Middlewares); _ -> ok end end. parse_version(<<"HTTP/1.1">>) -> 'HTTP/1.1'; parse_version(<<"HTTP/1.0">>) -> 'HTTP/1.0'. parse_path(<<>>, Path) -> {Path, <<>>}; parse_path(<< $?, Rest/binary >>, Path) -> parse_query(Rest, Path, <<>>); parse_path(<< C, Rest/binary >>, SoFar) -> parse_path(Rest, << SoFar/binary, C >>). parse_query(<<>>, Path, Query) -> {Path, Query}; parse_query(<< C, Rest/binary >>, Path, SoFar) -> parse_query(Rest, Path, << SoFar/binary, C >>). -spec execute(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok. execute(Req, _, []) -> cowboy_req:ensure_response(Req, 204); execute(Req, Env, [Middleware|Tail]) -> case Middleware:execute(Req, Env) of {ok, Req2, Env2} -> execute(Req2, Env2, Tail); {suspend, Module, Function, Args} -> erlang:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]); {halt, Req2} -> cowboy_req:ensure_response(Req2, 204); {error, Code, Req2} -> error_terminate(Code, Req2) end. %% @private -spec resume(cowboy_middleware:env(), [module()], module(), module(), [any()]) -> ok. resume(Env, Tail, Module, Function, Args) -> case apply(Module, Function, Args) of {ok, Req2, Env2} -> execute(Req2, Env2, Tail); {suspend, Module2, Function2, Args2} -> erlang:hibernate(?MODULE, resume, [Env, Tail, Module2, Function2, Args2]); {halt, Req2} -> cowboy_req:ensure_response(Req2, 204); {error, Code, Req2} -> error_terminate(Code, Req2) end. %% Only send an error reply if there is no resp_sent message. -spec error_terminate(cowboy:http_status(), cowboy_req:req()) -> ok. error_terminate(Code, Req) -> receive {cowboy_req, resp_sent} -> ok after 0 -> _ = cowboy_req:reply(Code, Req), ok end. %% Reply functions used by cowboy_req. reply(Socket = {Pid, _}, Status, Headers, Body) -> _ = case iolist_size(Body) of 0 -> Pid ! {reply, Socket, Status, Headers}; _ -> Pid ! {reply, Socket, Status, Headers, Body} end, ok. stream_reply(Socket = {Pid, _}, Status, Headers) -> _ = Pid ! {stream_reply, Socket, Status, Headers}, ok. stream_data(Socket = {Pid, _}, Data) -> _ = Pid ! {stream_data, Socket, Data}, ok. stream_close(Socket = {Pid, _}) -> _ = Pid ! {stream_close, Socket}, ok. %% Internal transport functions. %% @todo recv name() -> spdy. send(Socket, Data) -> stream_data(Socket, Data). %% We don't wait for the result of the actual sendfile call, %% therefore we can't know how much was actually sent. sendfile(Socket = {Pid, _}, Filepath) -> _ = Pid ! {sendfile, Socket, Filepath}, {ok, undefined}. erlang-cowboy-0.8.6+dfsg1/src/cowboy_spdy.hrl000066400000000000000000000237061223335133400211430ustar00rootroot00000000000000%% Zlib dictionary. -define(ZDICT, << 16#00, 16#00, 16#00, 16#07, 16#6f, 16#70, 16#74, 16#69, 16#6f, 16#6e, 16#73, 16#00, 16#00, 16#00, 16#04, 16#68, 16#65, 16#61, 16#64, 16#00, 16#00, 16#00, 16#04, 16#70, 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#03, 16#70, 16#75, 16#74, 16#00, 16#00, 16#00, 16#06, 16#64, 16#65, 16#6c, 16#65, 16#74, 16#65, 16#00, 16#00, 16#00, 16#05, 16#74, 16#72, 16#61, 16#63, 16#65, 16#00, 16#00, 16#00, 16#06, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#00, 16#00, 16#00, 16#0e, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00, 16#00, 16#00, 16#0d, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#73, 16#00, 16#00, 16#00, 16#03, 16#61, 16#67, 16#65, 16#00, 16#00, 16#00, 16#05, 16#61, 16#6c, 16#6c, 16#6f, 16#77, 16#00, 16#00, 16#00, 16#0d, 16#61, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#61, 16#63, 16#68, 16#65, 16#2d, 16#63, 16#6f, 16#6e, 16#74, 16#72, 16#6f, 16#6c, 16#00, 16#00, 16#00, 16#0a, 16#63, 16#6f, 16#6e, 16#6e, 16#65, 16#63, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#62, 16#61, 16#73, 16#65, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00, 16#00, 16#00, 16#0e, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#65, 16#6e, 16#67, 16#74, 16#68, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#6f, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0b, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6d, 16#64, 16#35, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#74, 16#79, 16#70, 16#65, 16#00, 16#00, 16#00, 16#04, 16#64, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00, 16#04, 16#65, 16#74, 16#61, 16#67, 16#00, 16#00, 16#00, 16#06, 16#65, 16#78, 16#70, 16#65, 16#63, 16#74, 16#00, 16#00, 16#00, 16#07, 16#65, 16#78, 16#70, 16#69, 16#72, 16#65, 16#73, 16#00, 16#00, 16#00, 16#04, 16#66, 16#72, 16#6f, 16#6d, 16#00, 16#00, 16#00, 16#04, 16#68, 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#08, 16#69, 16#66, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00, 16#11, 16#69, 16#66, 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d, 16#69, 16#66, 16#2d, 16#6e, 16#6f, 16#6e, 16#65, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00, 16#08, 16#69, 16#66, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00, 16#13, 16#69, 16#66, 16#2d, 16#75, 16#6e, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d, 16#6c, 16#61, 16#73, 16#74, 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#00, 16#00, 16#00, 16#08, 16#6c, 16#6f, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0c, 16#6d, 16#61, 16#78, 16#2d, 16#66, 16#6f, 16#72, 16#77, 16#61, 16#72, 16#64, 16#73, 16#00, 16#00, 16#00, 16#06, 16#70, 16#72, 16#61, 16#67, 16#6d, 16#61, 16#00, 16#00, 16#00, 16#12, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00, 16#13, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#05, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00, 16#07, 16#72, 16#65, 16#66, 16#65, 16#72, 16#65, 16#72, 16#00, 16#00, 16#00, 16#0b, 16#72, 16#65, 16#74, 16#72, 16#79, 16#2d, 16#61, 16#66, 16#74, 16#65, 16#72, 16#00, 16#00, 16#00, 16#06, 16#73, 16#65, 16#72, 16#76, 16#65, 16#72, 16#00, 16#00, 16#00, 16#02, 16#74, 16#65, 16#00, 16#00, 16#00, 16#07, 16#74, 16#72, 16#61, 16#69, 16#6c, 16#65, 16#72, 16#00, 16#00, 16#00, 16#11, 16#74, 16#72, 16#61, 16#6e, 16#73, 16#66, 16#65, 16#72, 16#2d, 16#65, 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#07, 16#75, 16#70, 16#67, 16#72, 16#61, 16#64, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#75, 16#73, 16#65, 16#72, 16#2d, 16#61, 16#67, 16#65, 16#6e, 16#74, 16#00, 16#00, 16#00, 16#04, 16#76, 16#61, 16#72, 16#79, 16#00, 16#00, 16#00, 16#03, 16#76, 16#69, 16#61, 16#00, 16#00, 16#00, 16#07, 16#77, 16#61, 16#72, 16#6e, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#77, 16#77, 16#77, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00, 16#06, 16#6d, 16#65, 16#74, 16#68, 16#6f, 16#64, 16#00, 16#00, 16#00, 16#03, 16#67, 16#65, 16#74, 16#00, 16#00, 16#00, 16#06, 16#73, 16#74, 16#61, 16#74, 16#75, 16#73, 16#00, 16#00, 16#00, 16#06, 16#32, 16#30, 16#30, 16#20, 16#4f, 16#4b, 16#00, 16#00, 16#00, 16#07, 16#76, 16#65, 16#72, 16#73, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#08, 16#48, 16#54, 16#54, 16#50, 16#2f, 16#31, 16#2e, 16#31, 16#00, 16#00, 16#00, 16#03, 16#75, 16#72, 16#6c, 16#00, 16#00, 16#00, 16#06, 16#70, 16#75, 16#62, 16#6c, 16#69, 16#63, 16#00, 16#00, 16#00, 16#0a, 16#73, 16#65, 16#74, 16#2d, 16#63, 16#6f, 16#6f, 16#6b, 16#69, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#6b, 16#65, 16#65, 16#70, 16#2d, 16#61, 16#6c, 16#69, 16#76, 16#65, 16#00, 16#00, 16#00, 16#06, 16#6f, 16#72, 16#69, 16#67, 16#69, 16#6e, 16#31, 16#30, 16#30, 16#31, 16#30, 16#31, 16#32, 16#30, 16#31, 16#32, 16#30, 16#32, 16#32, 16#30, 16#35, 16#32, 16#30, 16#36, 16#33, 16#30, 16#30, 16#33, 16#30, 16#32, 16#33, 16#30, 16#33, 16#33, 16#30, 16#34, 16#33, 16#30, 16#35, 16#33, 16#30, 16#36, 16#33, 16#30, 16#37, 16#34, 16#30, 16#32, 16#34, 16#30, 16#35, 16#34, 16#30, 16#36, 16#34, 16#30, 16#37, 16#34, 16#30, 16#38, 16#34, 16#30, 16#39, 16#34, 16#31, 16#30, 16#34, 16#31, 16#31, 16#34, 16#31, 16#32, 16#34, 16#31, 16#33, 16#34, 16#31, 16#34, 16#34, 16#31, 16#35, 16#34, 16#31, 16#36, 16#34, 16#31, 16#37, 16#35, 16#30, 16#32, 16#35, 16#30, 16#34, 16#35, 16#30, 16#35, 16#32, 16#30, 16#33, 16#20, 16#4e, 16#6f, 16#6e, 16#2d, 16#41, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#74, 16#61, 16#74, 16#69, 16#76, 16#65, 16#20, 16#49, 16#6e, 16#66, 16#6f, 16#72, 16#6d, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#32, 16#30, 16#34, 16#20, 16#4e, 16#6f, 16#20, 16#43, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#33, 16#30, 16#31, 16#20, 16#4d, 16#6f, 16#76, 16#65, 16#64, 16#20, 16#50, 16#65, 16#72, 16#6d, 16#61, 16#6e, 16#65, 16#6e, 16#74, 16#6c, 16#79, 16#34, 16#30, 16#30, 16#20, 16#42, 16#61, 16#64, 16#20, 16#52, 16#65, 16#71, 16#75, 16#65, 16#73, 16#74, 16#34, 16#30, 16#31, 16#20, 16#55, 16#6e, 16#61, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#65, 16#64, 16#34, 16#30, 16#33, 16#20, 16#46, 16#6f, 16#72, 16#62, 16#69, 16#64, 16#64, 16#65, 16#6e, 16#34, 16#30, 16#34, 16#20, 16#4e, 16#6f, 16#74, 16#20, 16#46, 16#6f, 16#75, 16#6e, 16#64, 16#35, 16#30, 16#30, 16#20, 16#49, 16#6e, 16#74, 16#65, 16#72, 16#6e, 16#61, 16#6c, 16#20, 16#53, 16#65, 16#72, 16#76, 16#65, 16#72, 16#20, 16#45, 16#72, 16#72, 16#6f, 16#72, 16#35, 16#30, 16#31, 16#20, 16#4e, 16#6f, 16#74, 16#20, 16#49, 16#6d, 16#70, 16#6c, 16#65, 16#6d, 16#65, 16#6e, 16#74, 16#65, 16#64, 16#35, 16#30, 16#33, 16#20, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#55, 16#6e, 16#61, 16#76, 16#61, 16#69, 16#6c, 16#61, 16#62, 16#6c, 16#65, 16#4a, 16#61, 16#6e, 16#20, 16#46, 16#65, 16#62, 16#20, 16#4d, 16#61, 16#72, 16#20, 16#41, 16#70, 16#72, 16#20, 16#4d, 16#61, 16#79, 16#20, 16#4a, 16#75, 16#6e, 16#20, 16#4a, 16#75, 16#6c, 16#20, 16#41, 16#75, 16#67, 16#20, 16#53, 16#65, 16#70, 16#74, 16#20, 16#4f, 16#63, 16#74, 16#20, 16#4e, 16#6f, 16#76, 16#20, 16#44, 16#65, 16#63, 16#20, 16#30, 16#30, 16#3a, 16#30, 16#30, 16#3a, 16#30, 16#30, 16#20, 16#4d, 16#6f, 16#6e, 16#2c, 16#20, 16#54, 16#75, 16#65, 16#2c, 16#20, 16#57, 16#65, 16#64, 16#2c, 16#20, 16#54, 16#68, 16#75, 16#2c, 16#20, 16#46, 16#72, 16#69, 16#2c, 16#20, 16#53, 16#61, 16#74, 16#2c, 16#20, 16#53, 16#75, 16#6e, 16#2c, 16#20, 16#47, 16#4d, 16#54, 16#63, 16#68, 16#75, 16#6e, 16#6b, 16#65, 16#64, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#68, 16#74, 16#6d, 16#6c, 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#70, 16#6e, 16#67, 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#6a, 16#70, 16#67, 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#67, 16#69, 16#66, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78, 16#6d, 16#6c, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78, 16#68, 16#74, 16#6d, 16#6c, 16#2b, 16#78, 16#6d, 16#6c, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#70, 16#6c, 16#61, 16#69, 16#6e, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#6a, 16#61, 16#76, 16#61, 16#73, 16#63, 16#72, 16#69, 16#70, 16#74, 16#2c, 16#70, 16#75, 16#62, 16#6c, 16#69, 16#63, 16#70, 16#72, 16#69, 16#76, 16#61, 16#74, 16#65, 16#6d, 16#61, 16#78, 16#2d, 16#61, 16#67, 16#65, 16#3d, 16#67, 16#7a, 16#69, 16#70, 16#2c, 16#64, 16#65, 16#66, 16#6c, 16#61, 16#74, 16#65, 16#2c, 16#73, 16#64, 16#63, 16#68, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#75, 16#74, 16#66, 16#2d, 16#38, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#69, 16#73, 16#6f, 16#2d, 16#38, 16#38, 16#35, 16#39, 16#2d, 16#31, 16#2c, 16#75, 16#74, 16#66, 16#2d, 16#2c, 16#2a, 16#2c, 16#65, 16#6e, 16#71, 16#3d, 16#30, 16#2e >>). erlang-cowboy-0.8.6+dfsg1/src/cowboy_static.erl000066400000000000000000000475241223335133400214540ustar00rootroot00000000000000%% Copyright (c) 2011, Magnus Klaar %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Static resource handler. %% %% This built in HTTP handler provides a simple file serving capability for %% cowboy applications. It is provided as a convenience for small or temporary %% environments where it is not preferrable to set up a second server just %% to serve files. It is recommended to use a CDN instead for efficiently %% handling static files, preferrably on a cookie-less domain name. %% %% If this handler is used the Erlang node running the cowboy application must %% be configured to use an async thread pool. This is configured by adding the %% `+A $POOL_SIZE' argument to the `erl' command used to start the node. See %% %% this reply from the OTP team to erlang-bugs %% %% == Base configuration == %% %% The handler must be configured with a request path prefix to serve files %% under and the path to a directory to read files from. The request path prefix %% is defined in the path pattern of the cowboy dispatch rule for the handler. %% The request path pattern must end with a `...' token. %% %% The directory path can be set to either an absolute or relative path in the %% form of a list or binary string representation of a file system path. A list %% of binary path segments is also a valid directory path. %% %% The directory path can also be set to a relative path within the `priv/' %% directory of an application. This is configured by setting the value of the %% directory option to a tuple of the form `{priv_dir, Application, Relpath}'. %% %% ==== Examples ==== %% ``` %% %% Serve files from /var/www/ under http://example.com/static/ %% {"/static/[...]", cowboy_static, %% [{directory, "/var/www"}]} %% %% %% Serve files from the current working directory under http://example.com/static/ %% {"/static/[...]", cowboy_static, %% [{directory, <<"./">>}]} %% %% %% Serve files from cowboy/priv/www under http://example.com/ %% {"/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, [<<"www">>]}}]} %% ''' %% %% == Content type configuration == %% %% By default the content type of all static resources will be set to %% `application/octet-stream'. This can be overriden by supplying a list %% of filename extension to mimetypes pairs in the `mimetypes' option. %% The filename extension should be a binary string including the leading dot. %% The mimetypes must be of a type that the `cowboy_rest' protocol can %% handle. %% %% The spawngrid/mimetypes %% application, or an arbitrary function accepting the path to the file being %% served, can also be used to generate the list of content types for a static %% file resource. The function used must accept an additional argument after %% the file path argument. %% %% ==== Example ==== %% ``` %% %% Use a static list of content types. %% {"/static/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, %% {mimetypes, [ %% {<<".css">>, [<<"text/css">>]}, %% {<<".js">>, [<<"application/javascript">>]}]}]} %% %% %% Use the default database in the mimetypes application. %% {"/static/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, %% {mimetypes, {fun mimetypes:path_to_mimes/2, default}}]} %% ''' %% %% == ETag Header Function == %% %% The default behaviour of the static file handler is to not generate ETag %% headers. This is because generating ETag headers based on file metadata %% causes different servers in a cluster to generate different ETag values for %% the same file unless the metadata is also synced. Generating strong ETags %% based on the contents of a file is currently out of scope for this module. %% %% The default behaviour can be overridden to generate an ETag header based on %% a combination of the file path, file size, inode and mtime values. If the %% option value is a non-empty list of attribute names tagged with `attributes' %% a hex encoded checksum of each attribute specified is included in the value %% of the the ETag header. If the list of attribute names is empty no ETag %% header is generated. %% %% If a strong ETag is required a user defined function for generating the %% header value can be supplied. The function must accept a list of key/values %% of the file attributes as the first argument and a second argument %% containing any additional data that the function requires. The function %% must return a term of the type `{weak | strong, binary()}' or `undefined'. %% %% ==== Examples ==== %% ``` %% %% A value of default is equal to not specifying the option. %% {"static/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, %% {etag, default}]} %% %% %% Use all avaliable ETag function arguments to generate a header value. %% {"static/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, %% {etag, {attributes, [filepath, filesize, inode, mtime]}}]} %% %% %% Use a user defined function to generate a strong ETag header value. %% {"static/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, []}}, %% {etag, {fun generate_strong_etag/2, strong_etag_extra}}]} %% %% generate_strong_etag(Arguments, strong_etag_extra) -> %% {_, Filepath} = lists:keyfind(filepath, 1, Arguments), %% {_, _Filesize} = lists:keyfind(filesize, 1, Arguments), %% {_, _INode} = lists:keyfind(inode, 1, Arguments), %% {_, _Modified} = lists:keyfind(mtime, 1, Arguments), %% ChecksumCommand = lists:flatten(io_lib:format("sha1sum ~s", [Filepath])), %% [Checksum|_] = string:tokens(os:cmd(ChecksumCommand), " "), %% {strong, iolist_to_binary(Checksum)}. %% ''' %% %% == File configuration == %% %% If the file system path being served does not share a common suffix with %% the request path it is possible to override the file path using the `file' %% option. The value of this option is expected to be a relative path within %% the static file directory specified using the `directory' option. %% The path must be in the form of a list or binary string representation of a %% file system path. A list of binary path segments, as is used throughout %% cowboy, is also a valid. %% %% When the `file' option is used the same file will be served for all requests %% matching the cowboy dispatch fule for the handler. It is not necessary to %% end the request path pattern with a `...' token because the request path %% will not be used to determine which file to serve from the static directory. %% %% === Examples === %% %% ``` %% %% Serve cowboy/priv/www/index.html as http://example.com/ %% {"/", cowboy_static, %% [{directory, {priv_dir, cowboy, [<<"www">>]}}, %% {file, <<"index.html">>}]} %% %% %% Serve cowboy/priv/www/page.html under http://example.com/*/page %% {"/:_/page", cowboy_static, %% [{directory, {priv_dir, cowboy, [<<"www">>]}}, %% {file, <<"page.html">>}]}. %% %% %% Always serve cowboy/priv/www/other.html under http://example.com/other %% {"/other/[...]", cowboy_static, %% [{directory, {priv_dir, cowboy, [<<"www">>]}}, %% {file, "other.html"}]} %% ''' -module(cowboy_static). %% include files -include_lib("kernel/include/file.hrl"). %% cowboy_protocol callbacks -export([init/3]). %% cowboy_rest callbacks -export([rest_init/2]). -export([allowed_methods/2]). -export([malformed_request/2]). -export([resource_exists/2]). -export([forbidden/2]). -export([last_modified/2]). -export([generate_etag/2]). -export([content_types_provided/2]). -export([file_contents/2]). %% internal -export([path_to_mimetypes/2]). %% types -type dirpath() :: string() | binary() | [binary()]. -type dirspec() :: dirpath() | {priv, atom(), dirpath()}. -type mimedef() :: {binary(), binary(), [{binary(), binary()}]}. -type etagarg() :: {filepath, binary()} | {mtime, calendar:datetime()} | {inode, non_neg_integer()} | {filesize, non_neg_integer()}. %% handler state -record(state, { filepath :: binary() | error, fileinfo :: {ok, #file_info{}} | {error, _} | error, mimetypes :: {fun((binary(), T) -> [mimedef()]), T} | undefined, etag_fun :: {fun(([etagarg()], T) -> undefined | {strong | weak, binary()}), T} }). %% @private Upgrade from HTTP handler to REST handler. init({_Transport, http}, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. %% @private Set up initial state of REST handler. -spec rest_init(Req, list()) -> {ok, Req, #state{}} when Req::cowboy_req:req(). rest_init(Req, Opts) -> {_, DirectoryOpt} = lists:keyfind(directory, 1, Opts), Directory = fullpath(filename:absname(directory_path(DirectoryOpt))), case lists:keyfind(file, 1, Opts) of false -> {PathInfo, Req2} = cowboy_req:path_info(Req), Filepath = filename:join([Directory|PathInfo]), Len = byte_size(Directory), case fullpath(Filepath) of << Directory:Len/binary, $/, _/binary >> -> rest_init(Req2, Opts, Filepath); _ -> {ok, Req2, #state{filepath=error, fileinfo=error, mimetypes=undefined, etag_fun=undefined}} end; {_, FileOpt} -> Filepath = filepath_path(FileOpt), Filepath2 = << Directory/binary, $/, Filepath/binary >>, rest_init(Req, Opts, Filepath2) end. rest_init(Req, Opts, Filepath) -> Fileinfo = file:read_file_info(Filepath, [{time, universal}]), Mimetypes = case lists:keyfind(mimetypes, 1, Opts) of false -> {fun path_to_mimetypes/2, []}; {_, {{M, F}, E}} -> {fun M:F/2, E}; {_, Mtypes} when is_tuple(Mtypes) -> Mtypes; {_, Mtypes} when is_list(Mtypes) -> {fun path_to_mimetypes/2, Mtypes} end, EtagFun = case lists:keyfind(etag, 1, Opts) of false -> {fun no_etag_function/2, undefined}; {_, default} -> {fun no_etag_function/2, undefined}; {_, {attributes, []}} -> {fun no_etag_function/2, undefined}; {_, {attributes, Attrs}} -> {fun attr_etag_function/2, Attrs}; {_, EtagOpt} -> EtagOpt end, {ok, Req, #state{filepath=Filepath, fileinfo=Fileinfo, mimetypes=Mimetypes, etag_fun=EtagFun}}. %% @private Only allow GET and HEAD requests on files. -spec allowed_methods(Req, #state{}) -> {[binary()], Req, #state{}} when Req::cowboy_req:req(). allowed_methods(Req, State) -> {[<<"GET">>, <<"HEAD">>], Req, State}. %% @private -spec malformed_request(Req, #state{}) -> {boolean(), Req, #state{}} when Req::cowboy_req:req(). malformed_request(Req, #state{filepath=error}=State) -> {true, Req, State}; malformed_request(Req, State) -> {false, Req, State}. %% @private Check if the resource exists under the document root. -spec resource_exists(Req, #state{}) -> {boolean(), Req, #state{}} when Req::cowboy_req:req(). resource_exists(Req, #state{fileinfo={error, _}}=State) -> {false, Req, State}; resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) -> {Fileinfo#file_info.type =:= regular, Req, State}. %% @private %% Access to a file resource is forbidden if it exists and the local node does %% not have permission to read it. Directory listings are always forbidden. -spec forbidden(Req, #state{}) -> {boolean(), Req, #state{}} when Req::cowboy_req:req(). forbidden(Req, #state{fileinfo={_, #file_info{type=directory}}}=State) -> {true, Req, State}; forbidden(Req, #state{fileinfo={error, eacces}}=State) -> {true, Req, State}; forbidden(Req, #state{fileinfo={error, _}}=State) -> {false, Req, State}; forbidden(Req, #state{fileinfo={ok, #file_info{access=Access}}}=State) -> {not (Access =:= read orelse Access =:= read_write), Req, State}. %% @private Read the time a file system system object was last modified. -spec last_modified(Req, #state{}) -> {calendar:datetime(), Req, #state{}} when Req::cowboy_req:req(). last_modified(Req, #state{fileinfo={ok, #file_info{mtime=Modified}}}=State) -> {Modified, Req, State}. %% @private Generate the ETag header value for this file. %% The ETag header value is only generated if the resource is a file that %% exists in document root. -spec generate_etag(Req, #state{}) -> {undefined | binary(), Req, #state{}} when Req::cowboy_req:req(). generate_etag(Req, #state{fileinfo={_, #file_info{type=regular, inode=INode, mtime=Modified, size=Filesize}}, filepath=Filepath, etag_fun={ETagFun, ETagData}}=State) -> ETagArgs = [ {filepath, Filepath}, {filesize, Filesize}, {inode, INode}, {mtime, Modified}], {ETagFun(ETagArgs, ETagData), Req, State}; generate_etag(Req, State) -> {undefined, Req, State}. %% @private Return the content type of a file. -spec content_types_provided(cowboy_req:req(), #state{}) -> tuple(). content_types_provided(Req, #state{filepath=Filepath, mimetypes={MimetypesFun, MimetypesData}}=State) -> Mimetypes = [{T, file_contents} || T <- MimetypesFun(Filepath, MimetypesData)], {Mimetypes, Req, State}. %% @private Return a function that writes a file directly to the socket. -spec file_contents(cowboy_req:req(), #state{}) -> tuple(). file_contents(Req, #state{filepath=Filepath, fileinfo={ok, #file_info{size=Filesize}}}=State) -> Writefile = fun(Socket, Transport) -> %% Transport:sendfile/2 may return {error, closed} %% if the connection is closed while sending the file. case Transport:sendfile(Socket, Filepath) of {ok, _} -> ok; {error, closed} -> ok; {error, etimedout} -> ok end end, {{stream, Filesize, Writefile}, Req, State}. %% Internal. -spec directory_path(dirspec()) -> dirpath(). directory_path({priv_dir, App, []}) -> priv_dir_path(App); directory_path({priv_dir, App, [H|_]=Path}) when is_binary(H) -> filename:join(priv_dir_path(App), filename:join(Path)); directory_path({priv_dir, App, Path}) -> filename:join(priv_dir_path(App), Path); directory_path([H|_]=Path) when is_binary(H) -> filename:join(Path); directory_path([H|_]=Path) when is_integer(H) -> list_to_binary(Path); directory_path(Path) when is_binary(Path) -> Path. %% @private Return the path to the priv/ directory of an application. -spec priv_dir_path(atom()) -> string(). priv_dir_path(App) -> case code:priv_dir(App) of {error, bad_name} -> priv_dir_mod(App); Dir -> list_to_binary(Dir) end. -spec priv_dir_mod(atom()) -> string(). priv_dir_mod(Mod) -> case code:which(Mod) of File when not is_list(File) -> <<"../priv">>; File -> filename:join(filename:dirname(File), <<"../priv">>) end. %% @private Ensure that a file path is of the same type as a request path. filepath_path(Path) when is_binary(Path) -> Path; filepath_path([H|_]=Path) when is_binary(H) -> filename:join(Path); filepath_path([H|_]=Path) when is_integer(H) -> list_to_binary(Path). fullpath(Path) when is_binary(Path) -> fullpath(filename:split(Path), []). fullpath([], Acc) -> filename:join(lists:reverse(Acc)); fullpath([<<".">>|Tail], Acc) -> fullpath(Tail, Acc); fullpath([<<"..">>|Tail], Acc=[_]) -> fullpath(Tail, Acc); fullpath([<<"..">>|Tail], [_|Acc]) -> fullpath(Tail, Acc); fullpath([Segment|Tail], Acc) -> fullpath(Tail, [Segment|Acc]). %% @private Use application/octet-stream as the default mimetype. %% If a list of extension - mimetype pairs are provided as the mimetypes %% an attempt to find the mimetype using the file extension. If no match %% is found the default mimetype is returned. -spec path_to_mimetypes(binary(), [{binary(), [mimedef()]}]) -> [mimedef()]. path_to_mimetypes(Filepath, Extensions) when is_binary(Filepath) -> Ext = filename:extension(Filepath), case Ext of <<>> -> default_mimetype(); _Ext -> path_to_mimetypes_(Ext, Extensions) end. -spec path_to_mimetypes_(binary(), [{binary(), [mimedef()]}]) -> [mimedef()]. path_to_mimetypes_(Ext, Extensions) -> case lists:keyfind(cowboy_bstr:to_lower(Ext), 1, Extensions) of {_, MTs} -> MTs; _Unknown -> default_mimetype() end. -spec default_mimetype() -> [mimedef()]. default_mimetype() -> [{<<"application">>, <<"octet-stream">>, []}]. %% @private Do not send ETag headers in the default configuration. -spec no_etag_function([etagarg()], undefined) -> undefined. no_etag_function(_Args, undefined) -> undefined. %% @private A simple alternative is to send an ETag based on file attributes. -type fileattr() :: filepath | filesize | mtime | inode. -spec attr_etag_function([etagarg()], [fileattr()]) -> {strong, binary()}. attr_etag_function(Args, Attrs) -> [[_|H]|T] = [begin {_,Pair} = {_,{_,_}} = {Attr,lists:keyfind(Attr, 1, Args)}, [$-|integer_to_list(erlang:phash2(Pair, 1 bsl 32), 16)] end || Attr <- Attrs], {strong, list_to_binary([H|T])}. -ifdef(TEST). directory_path_test_() -> PL = fun(D) -> length(filename:split(directory_path(D))) end, Base = PL({priv_dir, cowboy, []}), LengthTests = [ Base + 1, {priv_dir, cowboy, "a"}, Base + 1, {priv_dir, cowboy, <<"a">>}, Base + 1, {priv_dir, cowboy, [<<"a">>]}, Base + 2, {priv_dir, cowboy, "a/b"}, Base + 2, {priv_dir, cowboy, <<"a/b">>}, Base + 2, {priv_dir, cowboy, [<<"a">>, <<"b">>]} ], TypeTests = [ {priv_dir, cowboy, []}, {priv_dir, cowboy, "a"}, {priv_dir, cowboy, <<"a">>}, {priv_dir, cowboy, [<<"a">>]}, "a", <<"a">>, [<<"a">>] ], [{lists:flatten(io_lib:format("~p", [D])), fun() -> R = PL(D) end} || {R, D} <- LengthTests] ++ [{lists:flatten(io_lib:format("~p", [D])), fun() -> is_binary(directory_path(D)) end} || D <- TypeTests]. filepath_path_test_() -> Tests = [ {<<"a">>, "a"}, {<<"a">>, <<"a">>}, {<<"a">>, [<<"a">>]}, {<<"a/b">>, "a/b"}, {<<"a/b">>, <<"a/b">>}, {<<"a/b">>, [<<"a">>, <<"b">>]} ], [{lists:flatten(io_lib:format("~p", [F])), fun() -> R = filepath_path(F) end} || {R, F} <- Tests]. fullpath_test_() -> Tests = [ {<<"/home/cowboy">>, <<"/home/cowboy">>}, {<<"/home/cowboy">>, <<"/home/cowboy/">>}, {<<"/home/cowboy">>, <<"/home/cowboy/./">>}, {<<"/home/cowboy">>, <<"/home/cowboy/./././././.">>}, {<<"/home/cowboy">>, <<"/home/cowboy/abc/..">>}, {<<"/home/cowboy">>, <<"/home/cowboy/abc/../">>}, {<<"/home/cowboy">>, <<"/home/cowboy/abc/./../.">>}, {<<"/">>, <<"/home/cowboy/../../../../../..">>}, {<<"/etc/passwd">>, <<"/home/cowboy/../../etc/passwd">>} ], [{P, fun() -> R = fullpath(P) end} || {R, P} <- Tests]. good_path_check_test_() -> Tests = [ <<"/home/cowboy/file">>, <<"/home/cowboy/file/">>, <<"/home/cowboy/./file">>, <<"/home/cowboy/././././././file">>, <<"/home/cowboy/abc/../file">>, <<"/home/cowboy/abc/../file">>, <<"/home/cowboy/abc/./.././file">> ], [{P, fun() -> case fullpath(P) of << "/home/cowboy/", _/binary >> -> ok end end} || P <- Tests]. bad_path_check_test_() -> Tests = [ <<"/home/cowboy/../../../../../../file">>, <<"/home/cowboy/../../etc/passwd">> ], [{P, fun() -> error = case fullpath(P) of << "/home/cowboy/", _/binary >> -> ok; _ -> error end end} || P <- Tests]. good_path_win32_check_test_() -> Tests = case os:type() of {unix, _} -> []; {win32, _} -> [ <<"c:/home/cowboy/file">>, <<"c:/home/cowboy/file/">>, <<"c:/home/cowboy/./file">>, <<"c:/home/cowboy/././././././file">>, <<"c:/home/cowboy/abc/../file">>, <<"c:/home/cowboy/abc/../file">>, <<"c:/home/cowboy/abc/./.././file">> ] end, [{P, fun() -> case fullpath(P) of << "c:/home/cowboy/", _/binary >> -> ok end end} || P <- Tests]. bad_path_win32_check_test_() -> Tests = case os:type() of {unix, _} -> []; {win32, _} -> [ <<"c:/home/cowboy/../../secretfile.bat">>, <<"c:/home/cowboy/c:/secretfile.bat">>, <<"c:/home/cowboy/..\\..\\secretfile.bat">>, <<"c:/home/cowboy/c:\\secretfile.bat">> ] end, [{P, fun() -> error = case fullpath(P) of << "c:/home/cowboy/", _/binary >> -> ok; _ -> error end end} || P <- Tests]. -endif. erlang-cowboy-0.8.6+dfsg1/src/cowboy_sub_protocol.erl000066400000000000000000000034751223335133400226740ustar00rootroot00000000000000%% Copyright (c) 2013, James Fish %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Behaviour for sub protocols. %% %% Only one function needs to be implemented, upgrade/4. %% It receives the Req, the environment, the handler that the request has been %% routed to and the handler's options. It acts exactly the same as a %% middleware, so returns the same values a middleware's execute/2. %% %% Once the sub protocol has processed the request it should add the result %% to the environment. This is done by adding the tuple {result, Value} to the %% environment list. To continue handling requests on the current connection the %% Value should be the atom ok. Any other value will prevent the processing of %% subsequent requests. %% %% upgrade/4 will be called when a handler's init/3 returns %% {upgrade, protocol, Module}, where Module is the module of the sub protocol. -module(cowboy_sub_protocol). -callback upgrade(Req, Env, module(), any()) -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {halt, Req} | {error, cowboy:http_status(), Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). erlang-cowboy-0.8.6+dfsg1/src/cowboy_sup.erl000066400000000000000000000023321223335133400207600ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @private -module(cowboy_sup). -behaviour(supervisor). %% API. -export([start_link/0]). %% supervisor. -export([init/1]). -define(SUPERVISOR, ?MODULE). %% API. -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []). %% supervisor. init([]) -> Procs = [{cowboy_clock, {cowboy_clock, start_link, []}, permanent, 5000, worker, [cowboy_clock]}], {ok, {{one_for_one, 10, 10}, Procs}}. erlang-cowboy-0.8.6+dfsg1/src/cowboy_websocket.erl000066400000000000000000001004701223335133400221410ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Websocket protocol implementation. %% %% Cowboy supports versions 7 through 17 of the Websocket drafts. %% It also supports RFC6455, the proposed standard for Websocket. -module(cowboy_websocket). -behaviour(cowboy_sub_protocol). %% Ignore the deprecation warning for crypto:sha/1. %% @todo Remove when we support only R16B+. -compile(nowarn_deprecated_function). %% API. -export([upgrade/4]). %% Internal. -export([handler_loop/4]). -type close_code() :: 1000..4999. -export_type([close_code/0]). -type frame() :: close | ping | pong | {text | binary | close | ping | pong, iodata()} | {close, close_code(), iodata()}. -export_type([frame/0]). -type opcode() :: 0 | 1 | 2 | 8 | 9 | 10. -type mask_key() :: 0..16#ffffffff. -type frag_state() :: undefined | {nofin, opcode(), binary()} | {fin, opcode(), binary()}. -type rsv() :: << _:3 >>. -record(state, { env :: cowboy_middleware:env(), socket = undefined :: inet:socket(), transport = undefined :: module(), handler :: module(), handler_opts :: any(), key = undefined :: undefined | binary(), timeout = infinity :: timeout(), timeout_ref = undefined :: undefined | reference(), messages = undefined :: undefined | {atom(), atom(), atom()}, hibernate = false :: boolean(), frag_state = undefined :: frag_state(), utf8_state = <<>> :: binary(), deflate_frame = false :: boolean(), inflate_state :: any(), inflate_buffer = <<>> :: binary(), deflate_state :: any() }). %% @doc Upgrade an HTTP request to the Websocket protocol. %% %% You do not need to call this function manually. To upgrade to the Websocket %% protocol, you simply need to return {upgrade, protocol, {@module}} %% in your cowboy_http_handler:init/3 handler function. -spec upgrade(Req, Env, module(), any()) -> {ok, Req, Env} | {error, 400, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). upgrade(Req, Env, Handler, HandlerOpts) -> {_, Ref} = lists:keyfind(listener, 1, Env), ranch:remove_connection(Ref), [Socket, Transport] = cowboy_req:get([socket, transport], Req), State = #state{env=Env, socket=Socket, transport=Transport, handler=Handler, handler_opts=HandlerOpts}, case catch websocket_upgrade(State, Req) of {ok, State2, Req2} -> handler_init(State2, Req2); {'EXIT', _Reason} -> upgrade_error(Req, Env) end. -spec websocket_upgrade(#state{}, Req) -> {ok, #state{}, Req} when Req::cowboy_req:req(). websocket_upgrade(State, Req) -> {ok, ConnTokens, Req2} = cowboy_req:parse_header(<<"connection">>, Req), true = lists:member(<<"upgrade">>, ConnTokens), %% @todo Should probably send a 426 if the Upgrade header is missing. {ok, [<<"websocket">>], Req3} = cowboy_req:parse_header(<<"upgrade">>, Req2), {Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3), IntVersion = list_to_integer(binary_to_list(Version)), true = (IntVersion =:= 7) orelse (IntVersion =:= 8) orelse (IntVersion =:= 13), {Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4), false = Key =:= undefined, websocket_extensions(State#state{key=Key}, cowboy_req:set_meta(websocket_version, IntVersion, Req5)). -spec websocket_extensions(#state{}, Req) -> {ok, #state{}, Req} when Req::cowboy_req:req(). websocket_extensions(State, Req) -> case cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req) of {ok, Extensions, Req2} when Extensions =/= undefined -> [Compress] = cowboy_req:get([resp_compress], Req), case lists:keyfind(<<"x-webkit-deflate-frame">>, 1, Extensions) of {<<"x-webkit-deflate-frame">>, []} when Compress =:= true -> Inflate = zlib:open(), Deflate = zlib:open(), % Since we are negotiating an unconstrained deflate-frame % then we must be willing to accept frames using the % maximum window size which is 2^15. The negative value % indicates that zlib headers are not used. ok = zlib:inflateInit(Inflate, -15), % Initialize the deflater with a window size of 2^15 bits and disable % the zlib headers. ok = zlib:deflateInit(Deflate, best_compression, deflated, -15, 8, default), {ok, State#state{ deflate_frame = true, inflate_state = Inflate, inflate_buffer = <<>>, deflate_state = Deflate }, Req2}; _ -> {ok, State, Req2} end; _ -> {ok, State, Req} end. -spec handler_init(#state{}, Req) -> {ok, Req, cowboy_middleware:env()} | {error, 400, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_init(State=#state{env=Env, transport=Transport, handler=Handler, handler_opts=HandlerOpts}, Req) -> try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of {ok, Req2, HandlerState} -> websocket_handshake(State, Req2, HandlerState); {ok, Req2, HandlerState, hibernate} -> websocket_handshake(State#state{hibernate=true}, Req2, HandlerState); {ok, Req2, HandlerState, Timeout} -> websocket_handshake(State#state{timeout=Timeout}, Req2, HandlerState); {ok, Req2, HandlerState, Timeout, hibernate} -> websocket_handshake(State#state{timeout=Timeout, hibernate=true}, Req2, HandlerState); {shutdown, Req2} -> cowboy_req:ensure_response(Req2, 400), {ok, Req2, [{result, closed}|Env]} catch Class:Reason -> error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Options were ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, websocket_init, 3, Class, Reason, HandlerOpts, cowboy_req:to_list(Req),erlang:get_stacktrace()]), upgrade_error(Req, Env) end. %% Only send an error reply if there is no resp_sent message. -spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). upgrade_error(Req, Env) -> receive {cowboy_req, resp_sent} -> {ok, Req, [{result, closed}|Env]} after 0 -> {error, 400, Req} end. -spec websocket_handshake(#state{}, Req, any()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). websocket_handshake(State=#state{ transport=Transport, key=Key, deflate_frame=DeflateFrame}, Req, HandlerState) -> %% @todo Change into crypto:hash/2 for R17B+ or when supporting only R16B+. Challenge = base64:encode(crypto:sha( << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)), Extensions = case DeflateFrame of false -> []; true -> [{<<"sec-websocket-extensions">>, <<"x-webkit-deflate-frame">>}] end, {ok, Req2} = cowboy_req:upgrade_reply( 101, [{<<"upgrade">>, <<"websocket">>}, {<<"sec-websocket-accept">>, Challenge}| Extensions], Req), %% Flush the resp_sent message before moving on. receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, State2 = handler_loop_timeout(State), handler_before_loop(State2#state{key=undefined, messages=Transport:messages()}, Req2, HandlerState, <<>>). -spec handler_before_loop(#state{}, Req, any(), binary()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_before_loop(State=#state{ socket=Socket, transport=Transport, hibernate=true}, Req, HandlerState, SoFar) -> Transport:setopts(Socket, [{active, once}]), {suspend, ?MODULE, handler_loop, [State#state{hibernate=false}, Req, HandlerState, SoFar]}; handler_before_loop(State=#state{socket=Socket, transport=Transport}, Req, HandlerState, SoFar) -> Transport:setopts(Socket, [{active, once}]), handler_loop(State, Req, HandlerState, SoFar). -spec handler_loop_timeout(#state{}) -> #state{}. handler_loop_timeout(State=#state{timeout=infinity}) -> State#state{timeout_ref=undefined}; handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) -> _ = case PrevRef of undefined -> ignore; PrevRef -> erlang:cancel_timer(PrevRef) end, TRef = erlang:start_timer(Timeout, self(), ?MODULE), State#state{timeout_ref=TRef}. %% @private -spec handler_loop(#state{}, Req, any(), binary()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_loop(State=#state{socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef}, Req, HandlerState, SoFar) -> receive {OK, Socket, Data} -> State2 = handler_loop_timeout(State), websocket_data(State2, Req, HandlerState, << SoFar/binary, Data/binary >>); {Closed, Socket} -> handler_terminate(State, Req, HandlerState, {error, closed}); {Error, Socket, Reason} -> handler_terminate(State, Req, HandlerState, {error, Reason}); {timeout, TRef, ?MODULE} -> websocket_close(State, Req, HandlerState, {normal, timeout}); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> handler_loop(State, Req, HandlerState, SoFar); Message -> handler_call(State, Req, HandlerState, SoFar, websocket_info, Message, fun handler_before_loop/4) end. %% All frames passing through this function are considered valid, %% with the only exception of text and close frames with a payload %% which may still contain errors. -spec websocket_data(#state{}, Req, any(), binary()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). %% RSV bits MUST be 0 unless an extension is negotiated %% that defines meanings for non-zero values. websocket_data(State, Req, HandlerState, << _:1, Rsv:3, _/bits >>) when Rsv =/= 0, State#state.deflate_frame =:= false -> websocket_close(State, Req, HandlerState, {error, badframe}); %% Invalid opcode. Note that these opcodes may be used by extensions. websocket_data(State, Req, HandlerState, << _:4, Opcode:4, _/bits >>) when Opcode > 2, Opcode =/= 8, Opcode =/= 9, Opcode =/= 10 -> websocket_close(State, Req, HandlerState, {error, badframe}); %% Control frames MUST NOT be fragmented. websocket_data(State, Req, HandlerState, << 0:1, _:3, Opcode:4, _/bits >>) when Opcode >= 8 -> websocket_close(State, Req, HandlerState, {error, badframe}); %% A frame MUST NOT use the zero opcode unless fragmentation was initiated. websocket_data(State=#state{frag_state=undefined}, Req, HandlerState, << _:4, 0:4, _/bits >>) -> websocket_close(State, Req, HandlerState, {error, badframe}); %% Non-control opcode when expecting control message or next fragment. websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, << _:4, Opcode:4, _/bits >>) when Opcode =/= 0, Opcode < 8 -> websocket_close(State, Req, HandlerState, {error, badframe}); %% Close control frame length MUST be 0 or >= 2. websocket_data(State, Req, HandlerState, << _:4, 8:4, _:1, 1:7, _/bits >>) -> websocket_close(State, Req, HandlerState, {error, badframe}); %% Close control frame with incomplete close code. Need more data. websocket_data(State, Req, HandlerState, Data = << _:4, 8:4, 1:1, Len:7, _/bits >>) when Len > 1, byte_size(Data) < 8 -> handler_before_loop(State, Req, HandlerState, Data); %% 7 bits payload length. websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1, Len:7, MaskKey:32, Rest/bits >>) when Len < 126 -> websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Rest, Rsv, Fin); %% 16 bits payload length. websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1, 126:7, Len:16, MaskKey:32, Rest/bits >>) when Len > 125, Opcode < 8 -> websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Rest, Rsv, Fin); %% 63 bits payload length. websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1, 127:7, 0:1, Len:63, MaskKey:32, Rest/bits >>) when Len > 16#ffff, Opcode < 8 -> websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Rest, Rsv, Fin); %% When payload length is over 63 bits, the most significant bit MUST be 0. websocket_data(State, Req, HandlerState, << _:8, 1:1, 127:7, 1:1, _:7, _/binary >>) -> websocket_close(State, Req, HandlerState, {error, badframe}); %% All frames sent from the client to the server are masked. websocket_data(State, Req, HandlerState, << _:8, 0:1, _/bits >>) -> websocket_close(State, Req, HandlerState, {error, badframe}); %% For the next two clauses, it can be one of the following: %% %% * The minimal number of bytes MUST be used to encode the length %% * All control frames MUST have a payload length of 125 bytes or less websocket_data(State, Req, HandlerState, << _:9, 126:7, _:48, _/bits >>) -> websocket_close(State, Req, HandlerState, {error, badframe}); websocket_data(State, Req, HandlerState, << _:9, 127:7, _:96, _/bits >>) -> websocket_close(State, Req, HandlerState, {error, badframe}); %% Need more data. websocket_data(State, Req, HandlerState, Data) -> handler_before_loop(State, Req, HandlerState, Data). %% Initialize or update fragmentation state. -spec websocket_data(#state{}, Req, any(), opcode(), non_neg_integer(), mask_key(), binary(), rsv(), 0 | 1) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). %% The opcode is only included in the first frame fragment. websocket_data(State=#state{frag_state=undefined}, Req, HandlerState, Opcode, Len, MaskKey, Data, Rsv, 0) -> websocket_payload(State#state{frag_state={nofin, Opcode, <<>>}}, Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv); %% Subsequent frame fragments. websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, 0, Len, MaskKey, Data, Rsv, 0) -> websocket_payload(State, Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv); %% Final frame fragment. websocket_data(State=#state{frag_state={nofin, Opcode, SoFar}}, Req, HandlerState, 0, Len, MaskKey, Data, Rsv, 1) -> websocket_payload(State#state{frag_state={fin, Opcode, SoFar}}, Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv); %% Unfragmented frame. websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, Rsv, 1) -> websocket_payload(State, Req, HandlerState, Opcode, Len, MaskKey, <<>>, Data, Rsv). -spec websocket_payload(#state{}, Req, any(), opcode(), non_neg_integer(), mask_key(), binary(), binary(), rsv()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). %% Close control frames with a payload MUST contain a valid close code. websocket_payload(State, Req, HandlerState, Opcode=8, Len, MaskKey, <<>>, << MaskedCode:2/binary, Rest/bits >>, Rsv) -> Unmasked = << Code:16 >> = websocket_unmask(MaskedCode, MaskKey, <<>>), if Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006; (Code > 1011) and (Code < 3000); Code > 4999 -> websocket_close(State, Req, HandlerState, {error, badframe}); true -> websocket_payload(State, Req, HandlerState, Opcode, Len - 2, MaskKey, Unmasked, Rest, Rsv) end; %% Text frames and close control frames MUST have a payload that is valid UTF-8. websocket_payload(State=#state{utf8_state=Incomplete}, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv) when (byte_size(Data) < Len) andalso ((Opcode =:= 1) orelse ((Opcode =:= 8) andalso (Unmasked =/= <<>>))) -> Unmasked2 = websocket_unmask(Data, rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State), case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of false -> websocket_close(State2, Req, HandlerState, {error, badencoding}); Utf8State -> websocket_payload_loop(State2#state{utf8_state=Utf8State}, Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey, << Unmasked/binary, Unmasked3/binary >>, Rsv) end; websocket_payload(State=#state{utf8_state=Incomplete}, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv) when Opcode =:= 1; (Opcode =:= 8) and (Unmasked =/= <<>>) -> << End:Len/binary, Rest/bits >> = Data, Unmasked2 = websocket_unmask(End, rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State), case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of <<>> -> websocket_dispatch(State2#state{utf8_state= <<>>}, Req, HandlerState, Rest, Opcode, << Unmasked/binary, Unmasked3/binary >>); _ -> websocket_close(State2, Req, HandlerState, {error, badencoding}) end; %% Fragmented text frames may cut payload in the middle of UTF-8 codepoints. websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete}, Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data, Rsv) when byte_size(Data) < Len -> Unmasked2 = websocket_unmask(Data, rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State), case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of false -> websocket_close(State2, Req, HandlerState, {error, badencoding}); Utf8State -> websocket_payload_loop(State2#state{utf8_state=Utf8State}, Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey, << Unmasked/binary, Unmasked3/binary >>, Rsv) end; websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete}, Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data, Rsv) -> << End:Len/binary, Rest/bits >> = Data, Unmasked2 = websocket_unmask(End, rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State), case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of <<>> -> websocket_dispatch(State2#state{utf8_state= <<>>}, Req, HandlerState, Rest, Opcode, << Unmasked/binary, Unmasked3/binary >>); Utf8State when is_binary(Utf8State), Fin =:= nofin -> websocket_dispatch(State2#state{utf8_state=Utf8State}, Req, HandlerState, Rest, Opcode, << Unmasked/binary, Unmasked3/binary >>); _ -> websocket_close(State, Req, HandlerState, {error, badencoding}) end; %% Other frames have a binary payload. websocket_payload(State, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv) when byte_size(Data) < Len -> Unmasked2 = websocket_unmask(Data, rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State), websocket_payload_loop(State2, Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey, Unmasked3, Rsv); websocket_payload(State, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv) -> << End:Len/binary, Rest/bits >> = Data, Unmasked2 = websocket_unmask(End, rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State), websocket_dispatch(State2, Req, HandlerState, Rest, Opcode, Unmasked3). -spec websocket_inflate_frame(binary(), rsv(), boolean(), #state{}) -> {binary(), #state{}}. websocket_inflate_frame(Data, << Rsv1:1, _:2 >>, _, #state{deflate_frame = DeflateFrame} = State) when DeflateFrame =:= false orelse Rsv1 =:= 0 -> {Data, State}; websocket_inflate_frame(Data, << 1:1, _:2 >>, false, #state{inflate_buffer = Buffer} = State) -> {<<>>, State#state{inflate_buffer = << Buffer/binary, Data/binary >>}}; websocket_inflate_frame(Data, << 1:1, _:2 >>, true, #state{inflate_state = Inflate, inflate_buffer = Buffer} = State) -> Deflated = << Buffer/binary, Data/binary, 0:8, 0:8, 255:8, 255:8 >>, Result = zlib:inflate(Inflate, Deflated), {iolist_to_binary(Result), State#state{inflate_buffer = <<>>}}. -spec websocket_unmask(B, mask_key(), B) -> B when B::binary(). websocket_unmask(<<>>, _, Unmasked) -> Unmasked; websocket_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) -> T = O bxor MaskKey, websocket_unmask(Rest, MaskKey, << Acc/binary, T:32 >>); websocket_unmask(<< O:24 >>, MaskKey, Acc) -> << MaskKey2:24, _:8 >> = << MaskKey:32 >>, T = O bxor MaskKey2, << Acc/binary, T:24 >>; websocket_unmask(<< O:16 >>, MaskKey, Acc) -> << MaskKey2:16, _:16 >> = << MaskKey:32 >>, T = O bxor MaskKey2, << Acc/binary, T:16 >>; websocket_unmask(<< O:8 >>, MaskKey, Acc) -> << MaskKey2:8, _:24 >> = << MaskKey:32 >>, T = O bxor MaskKey2, << Acc/binary, T:8 >>. %% Because we unmask on the fly we need to continue from the right mask byte. -spec rotate_mask_key(mask_key(), non_neg_integer()) -> mask_key(). rotate_mask_key(MaskKey, UnmaskedLen) -> Left = UnmaskedLen rem 4, Right = 4 - Left, (MaskKey bsl (Left * 8)) + (MaskKey bsr (Right * 8)). %% Returns <<>> if the argument is valid UTF-8, false if not, %% or the incomplete part of the argument if we need more data. -spec is_utf8(binary()) -> false | binary(). is_utf8(Valid = <<>>) -> Valid; is_utf8(<< _/utf8, Rest/binary >>) -> is_utf8(Rest); %% 2 bytes. Codepages C0 and C1 are invalid; fail early. is_utf8(<< 2#1100000:7, _/bits >>) -> false; is_utf8(Incomplete = << 2#110:3, _:5 >>) -> Incomplete; %% 3 bytes. is_utf8(Incomplete = << 2#1110:4, _:4 >>) -> Incomplete; is_utf8(Incomplete = << 2#1110:4, _:4, 2#10:2, _:6 >>) -> Incomplete; %% 4 bytes. Codepage F4 may have invalid values greater than 0x10FFFF. is_utf8(<< 2#11110100:8, 2#10:2, High:6, _/bits >>) when High >= 2#10000 -> false; is_utf8(Incomplete = << 2#11110:5, _:3 >>) -> Incomplete; is_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6 >>) -> Incomplete; is_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6, 2#10:2, _:6 >>) -> Incomplete; %% Invalid. is_utf8(_) -> false. -spec websocket_payload_loop(#state{}, Req, any(), opcode(), non_neg_integer(), mask_key(), binary(), rsv()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). websocket_payload_loop(State=#state{socket=Socket, transport=Transport, messages={OK, Closed, Error}, timeout_ref=TRef}, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Rsv) -> Transport:setopts(Socket, [{active, once}]), receive {OK, Socket, Data} -> State2 = handler_loop_timeout(State), websocket_payload(State2, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv); {Closed, Socket} -> handler_terminate(State, Req, HandlerState, {error, closed}); {Error, Socket, Reason} -> handler_terminate(State, Req, HandlerState, {error, Reason}); {timeout, TRef, ?MODULE} -> websocket_close(State, Req, HandlerState, {normal, timeout}); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> websocket_payload_loop(State, Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Rsv); Message -> handler_call(State, Req, HandlerState, <<>>, websocket_info, Message, fun (State2, Req2, HandlerState2, _) -> websocket_payload_loop(State2, Req2, HandlerState2, Opcode, Len, MaskKey, Unmasked, Rsv) end) end. -spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). %% Continuation frame. websocket_dispatch(State=#state{frag_state={nofin, Opcode, SoFar}}, Req, HandlerState, RemainingData, 0, Payload) -> websocket_data(State#state{frag_state={nofin, Opcode, << SoFar/binary, Payload/binary >>}}, Req, HandlerState, RemainingData); %% Last continuation frame. websocket_dispatch(State=#state{frag_state={fin, Opcode, SoFar}}, Req, HandlerState, RemainingData, 0, Payload) -> websocket_dispatch(State#state{frag_state=undefined}, Req, HandlerState, RemainingData, Opcode, << SoFar/binary, Payload/binary >>); %% Text frame. websocket_dispatch(State, Req, HandlerState, RemainingData, 1, Payload) -> handler_call(State, Req, HandlerState, RemainingData, websocket_handle, {text, Payload}, fun websocket_data/4); %% Binary frame. websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) -> handler_call(State, Req, HandlerState, RemainingData, websocket_handle, {binary, Payload}, fun websocket_data/4); %% Close control frame. websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, <<>>) -> websocket_close(State, Req, HandlerState, {remote, closed}); websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, << Code:16, Payload/bits >>) -> websocket_close(State, Req, HandlerState, {remote, Code, Payload}); %% Ping control frame. Send a pong back and forward the ping to the handler. websocket_dispatch(State=#state{socket=Socket, transport=Transport}, Req, HandlerState, RemainingData, 9, Payload) -> Len = payload_length_to_binary(byte_size(Payload)), Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>), handler_call(State, Req, HandlerState, RemainingData, websocket_handle, {ping, Payload}, fun websocket_data/4); %% Pong control frame. websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) -> handler_call(State, Req, HandlerState, RemainingData, websocket_handle, {pong, Payload}, fun websocket_data/4). -spec handler_call(#state{}, Req, any(), binary(), atom(), any(), fun()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req, HandlerState, RemainingData, Callback, Message, NextState) -> try Handler:Callback(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> NextState(State, Req2, HandlerState2, RemainingData); {ok, Req2, HandlerState2, hibernate} -> NextState(State#state{hibernate=true}, Req2, HandlerState2, RemainingData); {reply, Payload, Req2, HandlerState2} when is_tuple(Payload) -> case websocket_send(Payload, State) of {ok, State2} -> NextState(State2, Req2, HandlerState2, RemainingData); {shutdown, State2} -> handler_terminate(State2, Req2, HandlerState2, {normal, shutdown}); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; {reply, Payload, Req2, HandlerState2, hibernate} when is_tuple(Payload) -> case websocket_send(Payload, State) of {ok, State2} -> NextState(State2#state{hibernate=true}, Req2, HandlerState2, RemainingData); {shutdown, State2} -> handler_terminate(State2, Req2, HandlerState2, {normal, shutdown}); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; {reply, Payload, Req2, HandlerState2} when is_list(Payload) -> case websocket_send_many(Payload, State) of {ok, State2} -> NextState(State2, Req2, HandlerState2, RemainingData); {shutdown, State2} -> handler_terminate(State2, Req2, HandlerState2, {normal, shutdown}); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; {reply, Payload, Req2, HandlerState2, hibernate} when is_list(Payload) -> case websocket_send_many(Payload, State) of {ok, State2} -> NextState(State2#state{hibernate=true}, Req2, HandlerState2, RemainingData); {shutdown, State2} -> handler_terminate(State2, Req2, HandlerState2, {normal, shutdown}); {{error, _} = Error, State2} -> handler_terminate(State2, Req2, HandlerState2, Error) end; {shutdown, Req2, HandlerState2} -> websocket_close(State, Req2, HandlerState2, {normal, shutdown}) catch Class:Reason -> PLReq = cowboy_req:to_list(Req), error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Message was ~p~n" "** Options were ~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, Callback, 3, Class, Reason, Message, HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()]), websocket_close(State, Req, HandlerState, {error, handler}) end. websocket_opcode(text) -> 1; websocket_opcode(binary) -> 2; websocket_opcode(close) -> 8; websocket_opcode(ping) -> 9; websocket_opcode(pong) -> 10. -spec websocket_deflate_frame(opcode(), binary(), #state{}) -> {binary(), <<_:3>>, #state{}}. websocket_deflate_frame(Opcode, Payload, State=#state{deflate_frame = DeflateFrame}) when DeflateFrame =:= false orelse Opcode >= 8 -> {Payload, <<0:3>>, State}; websocket_deflate_frame(_, Payload, State=#state{deflate_state = Deflate}) -> Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)), DeflatedBodyLength = erlang:size(Deflated) - 4, Deflated1 = case Deflated of <> -> Body; _ -> Deflated end, {Deflated1, <<1:1, 0:2>>, State}. -spec websocket_send(frame(), #state{}) -> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}. websocket_send(Type, State=#state{socket=Socket, transport=Transport}) when Type =:= close -> Opcode = websocket_opcode(Type), case Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>) of ok -> {shutdown, State}; Error -> {Error, State} end; websocket_send(Type, State=#state{socket=Socket, transport=Transport}) when Type =:= ping; Type =:= pong -> Opcode = websocket_opcode(Type), {Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>), State}; websocket_send({close, Payload}, State) -> websocket_send({close, 1000, Payload}, State); websocket_send({Type = close, StatusCode, Payload}, State=#state{ socket=Socket, transport=Transport}) -> Opcode = websocket_opcode(Type), Len = 2 + iolist_size(Payload), %% Control packets must not be > 125 in length. true = Len =< 125, BinLen = payload_length_to_binary(Len), Transport:send(Socket, [<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]), {shutdown, State}; websocket_send({Type, Payload0}, State=#state{socket=Socket, transport=Transport}) -> Opcode = websocket_opcode(Type), {Payload, Rsv, State2} = websocket_deflate_frame(Opcode, iolist_to_binary(Payload0), State), Len = iolist_size(Payload), %% Control packets must not be > 125 in length. true = if Type =:= ping; Type =:= pong -> Len =< 125; true -> true end, BinLen = payload_length_to_binary(Len), {Transport:send(Socket, [<< 1:1, Rsv/bits, Opcode:4, 0:1, BinLen/bits >>, Payload]), State2}. -spec websocket_send_many([frame()], #state{}) -> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}. websocket_send_many([], State) -> {ok, State}; websocket_send_many([Frame|Tail], State) -> case websocket_send(Frame, State) of {ok, State2} -> websocket_send_many(Tail, State2); {shutdown, State2} -> {shutdown, State2}; {Error, State2} -> {Error, State2} end. -spec websocket_close(#state{}, Req, any(), {atom(), atom()} | {remote, close_code(), binary()}) -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). websocket_close(State=#state{socket=Socket, transport=Transport}, Req, HandlerState, Reason) -> case Reason of {normal, _} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>); {error, badframe} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1002:16 >>); {error, badencoding} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1007:16 >>); {error, handler} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1011:16 >>); {remote, closed} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>); {remote, Code, _} -> Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, Code:16 >>) end, handler_terminate(State, Req, HandlerState, Reason). -spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()}) -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts}, Req, HandlerState, TerminateReason) -> try Handler:websocket_terminate(TerminateReason, Req, HandlerState) catch Class:Reason -> PLReq = cowboy_req:to_list(Req), error_logger:error_msg( "** Cowboy handler ~p terminating in ~p/~p~n" " for the reason ~p:~p~n** Initial reason was ~p~n" "** Options were ~p~n** Handler state was ~p~n" "** Request was ~p~n** Stacktrace: ~p~n~n", [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()]) end, {ok, Req, [{result, closed}|Env]}. -spec payload_length_to_binary(0..16#7fffffffffffffff) -> << _:7 >> | << _:23 >> | << _:71 >>. payload_length_to_binary(N) -> case N of N when N =< 125 -> << N:7 >>; N when N =< 16#ffff -> << 126:7, N:16 >>; N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> end. erlang-cowboy-0.8.6+dfsg1/src/cowboy_websocket_handler.erl000066400000000000000000000074741223335133400236500ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %% @doc Handler for HTTP WebSocket requests. %% %% WebSocket handlers must implement five callbacks: init/3, %% websocket_init/3, websocket_handle/3, %% websocket_info/3 and websocket_terminate/3. %% These callbacks will only be called if the connection is upgraded %% to WebSocket in the HTTP handler's init/3 callback. %% They are then called in that order, although websocket_handle/3 %% will be called for each packet received, and websocket_info %% for each message received. %% %% websocket_init/3 is meant for initialization. It receives %% information about the transport and protocol used, along with the handler %% options from the dispatch list. You can define a request-wide state here. %% If you are going to want to compact the request, you should probably do it %% here. %% %% websocket_handle/3 receives the data from the socket. It can reply %% something, do nothing or close the connection. %% %% websocket_info/3 receives messages sent to the process. It has %% the same reply format as websocket_handle/3 described above. Note %% that unlike in a gen_server, when websocket_info/3 %% replies something, it is always to the socket, not to the process that %% originated the message. %% %% websocket_terminate/3 is meant for cleaning up. It also receives %% the request and the state previously defined, along with a reason for %% termination. %% %% All of websocket_init/3, websocket_handle/3 and %% websocket_info/3 can decide to hibernate the process by adding %% an extra element to the returned tuple, containing the atom %% hibernate. Doing so helps save memory and improve CPU usage. -module(cowboy_websocket_handler). -type opts() :: any(). -type state() :: any(). -type terminate_reason() :: {normal, shutdown} | {normal, timeout} | {error, closed} | {remote, closed} | {remote, cowboy_websocket:close_code(), binary()} | {error, badencoding} | {error, badframe} | {error, atom()}. -callback websocket_init(atom(), Req, opts()) -> {ok, Req, state()} | {ok, Req, state(), hibernate} | {ok, Req, state(), timeout()} | {ok, Req, state(), timeout(), hibernate} | {shutdown, Req} when Req::cowboy_req:req(). -callback websocket_handle({text | binary | ping | pong, binary()}, Req, State) -> {ok, Req, State} | {ok, Req, State, hibernate} | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State} | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate} | {shutdown, Req, State} when Req::cowboy_req:req(), State::state(). -callback websocket_info(any(), Req, State) -> {ok, Req, State} | {ok, Req, State, hibernate} | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State} | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate} | {shutdown, Req, State} when Req::cowboy_req:req(), State::state(). -callback websocket_terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. erlang-cowboy-0.8.6+dfsg1/test/000077500000000000000000000000001223335133400162535ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/test/autobahn_SUITE.erl000066400000000000000000000064331223335133400215370ustar00rootroot00000000000000%% Copyright (c) 2011, Magnus Klaar %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(autobahn_SUITE). %% This CT suite reuses the websocket server test suite from the Autobahn %% project. The Autobahn project is a websocket implementation for Python. %% Given that we don't expect to find the packages and tools to properly %% set up and run such a test on a system used primarily for Erlang devlopment %% this test suite is not included in the default 'ct' target in the makefile. -include_lib("common_test/include/ct.hrl"). -export([all/0, groups/0, init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2]). %% ct. -export([run_tests/1]). %% autobahn. %% ct. all() -> [{group, autobahn}]. groups() -> BaseTests = [run_tests], [{autobahn, [], BaseTests}]. init_per_suite(Config) -> application:start(crypto), application:start(ranch), application:start(cowboy), %% /tmp must be used as the parent directory for the virtualenv because %% the directory names used in CT are so long that the interpreter path %% in the scripts generated by virtualenv get so long that the system %% refuses to execute them. EnvPath = "/tmp/cowboy_autobahn_env", os:putenv("AB_TESTS_ENV", EnvPath), os:putenv("AB_TESTS_PRIV", ?config(priv_dir, Config)), BinPath = filename:join(?config(data_dir, Config), "test.py"), Stdout = os:cmd(BinPath ++ " setup"), ct:log("~s~n", [Stdout]), case string:str(Stdout, "AB-TESTS-SETUP-OK") of 0 -> erlang:error(failed); _ -> [{env_path, EnvPath},{bin_path,BinPath}|Config] end. end_per_suite(_Config) -> os:cmd("deactivate"), application:stop(cowboy), application:stop(ranch), application:stop(crypto), ok. init_per_group(autobahn, Config) -> Port = 33080, cowboy:start_http(autobahn, 100, [{port, Port}], [ {env, [{dispatch, init_dispatch()}]} ]), [{port, Port}|Config]. end_per_group(Listener, _Config) -> cowboy:stop_listener(Listener), ok. %% Dispatch configuration. init_dispatch() -> cowboy_router:compile([{"localhost", [ {"/echo", autobahn_echo, []}]}]). %% autobahn cases run_tests(Config) -> PrivDir = ?config(priv_dir, Config), IndexFile = filename:join([PrivDir, "reports", "servers", "index.html"]), ct:log("

Full Test Results Report

~n", [IndexFile]), BinPath = ?config(bin_path, Config), Stdout = os:cmd(BinPath ++ " test"), ct:log("~s~n", [Stdout]), case string:str(Stdout, "AB-TESTS-TEST-OK") of 0 -> erlang:error(failed); _ -> ok end, {ok, IndexHTML} = file:read_file(IndexFile), case length(binary:matches(IndexHTML, <<"case_failed">>)) > 2 of true -> erlang:error(failed); false -> ok end. erlang-cowboy-0.8.6+dfsg1/test/autobahn_SUITE_data/000077500000000000000000000000001223335133400220165ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/test/autobahn_SUITE_data/autobahn_echo.erl000066400000000000000000000013041223335133400253170ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(autobahn_echo). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> {ok, Req, undefined}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; websocket_handle({binary, Data}, Req, State) -> {reply, {binary, Data}, Req, State}; websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/test/autobahn_SUITE_data/test.py000077500000000000000000000041731223335133400233570ustar00rootroot00000000000000#!/usr/bin/env python import os import os.path import sys import subprocess AB_TESTS_ENV = os.getenv("AB_TESTS_ENV") AB_TESTS_PRIV = os.getenv("AB_TESTS_PRIV") VIRTUALENV_URL = 'https://raw.github.com/pypa/virtualenv/master/virtualenv.py' VIRTUALENV_BIN = os.path.join(AB_TESTS_ENV, "virtualenv.py") INSTALL_BIN = os.path.join(AB_TESTS_ENV, "bin", "easy_install") def activate_env(env): """ See 'Using Virtualenv without bin/python' at http://www.virtualenv.org """ activate_this = os.path.join(env, 'bin', 'activate_this.py') exec(compile(open(activate_this).read(), activate_this, 'exec'), dict(__file__=activate_this)) def install_env(env): """ Install a new virtualenv at a path and also install the Autobahn package. """ os.makedirs(env) if not os.path.isdir(env) else None subprocess.check_call(["curl", "-sS", VIRTUALENV_URL, "-o", VIRTUALENV_BIN]) subprocess.check_call(["python", VIRTUALENV_BIN, env]) activate_env(env) subprocess.check_call([INSTALL_BIN, "http://pypi.python.org/packages/2.7/a/autobahntestsuite/autobahntestsuite-0.5.3-py2.7.egg"]) def client_config(): """ See comment on SUPPORTED_SPEC_VERSIONS in Autobahn/.../websocket.py """ base = { 'options': {'failByDrop': False}, 'enable-ssl': False, 'servers': [{ 'agent': 'Cowboy', 'url': 'ws://localhost:33080/echo', 'options': {'version': 18} # RFC6455 }], 'cases': ['*'], 'exclude-cases': [] } return base def run_test(env, config): activate_env(env) from twisted.python import log from twisted.internet import reactor from autobahntestsuite.fuzzing import FuzzingClientFactory os.chdir(AB_TESTS_PRIV) log.startLogging(sys.stdout) fuzzer = FuzzingClientFactory(config) return reactor.run() def main(): cmd = sys.argv[1] if cmd == 'setup': install_env(AB_TESTS_ENV) print('AB-TESTS-SETUP-OK') elif cmd == 'test': run_test(AB_TESTS_ENV, client_config()) print('AB-TESTS-TEST-OK') else: return 1 if __name__ == '__main__': main() erlang-cowboy-0.8.6+dfsg1/test/cover.spec000066400000000000000000000000351223335133400202430ustar00rootroot00000000000000{incl_app, cowboy, details}. erlang-cowboy-0.8.6+dfsg1/test/eunit_SUITE.erl000066400000000000000000000017441223335133400210620ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(eunit_SUITE). -include_lib("common_test/include/ct.hrl"). %% ct. -export([all/0]). %% Tests. -export([eunit/1]). %% ct. all() -> [eunit]. eunit(_) -> ok = eunit:test({application, cowboy}). erlang-cowboy-0.8.6+dfsg1/test/http_SUITE.erl000066400000000000000000001336251223335133400207210ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% Copyright (c) 2011, Anthony Ramine %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(http_SUITE). -include_lib("common_test/include/ct.hrl"). %% ct. -export([all/0]). -export([groups/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). -export([init_per_group/2]). -export([end_per_group/2]). %% Tests. -export([check_raw_status/1]). -export([check_status/1]). -export([chunked_response/1]). -export([echo_body/1]). -export([echo_body_max_length/1]). -export([echo_body_qs/1]). -export([echo_body_qs_max_length/1]). -export([error_chain_handle_after_reply/1]). -export([error_chain_handle_before_reply/1]). -export([error_handle_after_reply/1]). -export([error_init_after_reply/1]). -export([error_init_reply_handle_error/1]). -export([headers_dupe/1]). -export([http10_chunkless/1]). -export([http10_hostless/1]). -export([keepalive_max/1]). -export([keepalive_nl/1]). -export([multipart/1]). -export([nc_rand/1]). -export([nc_zero/1]). -export([onrequest/1]). -export([onrequest_reply/1]). -export([onresponse_capitalize/1]). -export([onresponse_crash/1]). -export([onresponse_reply/1]). -export([pipeline/1]). -export([pipeline_long_polling/1]). -export([rest_bad_accept/1]). -export([rest_bad_content_type/1]). -export([rest_expires/1]). -export([rest_keepalive/1]). -export([rest_keepalive_post/1]). -export([rest_missing_get_callbacks/1]). -export([rest_missing_put_callbacks/1]). -export([rest_nodelete/1]). -export([rest_options_default/1]). -export([rest_param_all/1]). -export([rest_patch/1]). -export([rest_post_charset/1]). -export([rest_postonly/1]). -export([rest_resource_etags/1]). -export([rest_resource_etags_if_none_match/1]). -export([set_env_dispatch/1]). -export([set_resp_body/1]). -export([set_resp_header/1]). -export([set_resp_overwrite/1]). -export([slowloris/1]). -export([slowloris2/1]). -export([static_attribute_etag/1]). -export([static_function_etag/1]). -export([static_mimetypes_function/1]). -export([static_specify_file/1]). -export([static_specify_file_catchall/1]). -export([static_test_file/1]). -export([static_test_file_css/1]). -export([stream_body_set_resp/1]). -export([stream_body_set_resp_close/1]). -export([stream_body_set_resp_chunked/1]). -export([stream_body_set_resp_chunked10/1]). -export([te_chunked/1]). -export([te_chunked_chopped/1]). -export([te_chunked_delayed/1]). -export([te_identity/1]). %% ct. all() -> [ {group, http}, {group, https}, {group, http_compress}, {group, https_compress}, {group, onrequest}, {group, onresponse}, {group, onresponse_capitalize}, {group, set_env} ]. groups() -> Tests = [ check_raw_status, check_status, chunked_response, echo_body, echo_body_max_length, echo_body_qs, echo_body_qs_max_length, error_chain_handle_after_reply, error_chain_handle_before_reply, error_handle_after_reply, error_init_after_reply, error_init_reply_handle_error, headers_dupe, http10_chunkless, http10_hostless, keepalive_max, keepalive_nl, multipart, nc_rand, nc_zero, pipeline, pipeline_long_polling, rest_bad_accept, rest_bad_content_type, rest_expires, rest_keepalive, rest_keepalive_post, rest_missing_get_callbacks, rest_missing_put_callbacks, rest_nodelete, rest_options_default, rest_param_all, rest_patch, rest_post_charset, rest_postonly, rest_resource_etags, rest_resource_etags_if_none_match, set_resp_body, set_resp_header, set_resp_overwrite, slowloris, slowloris2, static_attribute_etag, static_function_etag, static_mimetypes_function, static_specify_file, static_specify_file_catchall, static_test_file, static_test_file_css, stream_body_set_resp, stream_body_set_resp_close, stream_body_set_resp_chunked, stream_body_set_resp_chunked10, te_chunked, te_chunked_chopped, te_chunked_delayed, te_identity ], [ {http, [parallel], Tests}, {https, [parallel], Tests}, {http_compress, [parallel], Tests}, {https_compress, [parallel], Tests}, {onrequest, [parallel], [ onrequest, onrequest_reply ]}, {onresponse, [parallel], [ onresponse_crash, onresponse_reply ]}, {onresponse_capitalize, [parallel], [ onresponse_capitalize ]}, {set_env, [], [ set_env_dispatch ]} ]. init_per_suite(Config) -> application:start(crypto), application:start(ranch), application:start(cowboy), Dir = ?config(priv_dir, Config) ++ "/static", ct_helper:create_static_dir(Dir), [{static_dir, Dir}|Config]. end_per_suite(Config) -> Dir = ?config(static_dir, Config), ct_helper:delete_static_dir(Dir), application:stop(cowboy), application:stop(ranch), application:stop(crypto), ok. init_per_group(http, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(http, 100, [{port, 0}], [ {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(http), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]; init_per_group(https, Config) -> Transport = ranch_ssl, {_, Cert, Key} = ct_helper:make_certs(), Opts = [{cert, Cert}, {key, Key}], application:start(asn1), application:start(public_key), application:start(ssl), {ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, 0}], [ {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(https), {ok, Client} = cowboy_client:init(Opts), [{scheme, <<"https">>}, {port, Port}, {opts, Opts}, {transport, Transport}, {client, Client}|Config]; init_per_group(http_compress, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(http_compress, 100, [{port, 0}], [ {compress, true}, {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(http_compress), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]; init_per_group(https_compress, Config) -> Transport = ranch_ssl, {_, Cert, Key} = ct_helper:make_certs(), Opts = [{cert, Cert}, {key, Key}], application:start(asn1), application:start(public_key), application:start(ssl), {ok, _} = cowboy:start_https(https_compress, 100, Opts ++ [{port, 0}], [ {compress, true}, {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(https_compress), {ok, Client} = cowboy_client:init(Opts), [{scheme, <<"https">>}, {port, Port}, {opts, Opts}, {transport, Transport}, {client, Client}|Config]; init_per_group(onrequest, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(onrequest, 100, [{port, 0}], [ {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {onrequest, fun onrequest_hook/1}, {timeout, 500} ]), Port = ranch:get_port(onrequest), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]; init_per_group(onresponse, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(onresponse, 100, [{port, 0}], [ {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {onresponse, fun onresponse_hook/4}, {timeout, 500} ]), Port = ranch:get_port(onresponse), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]; init_per_group(onresponse_capitalize, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(onresponse_capitalize, 100, [{port, 0}], [ {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {onresponse, fun onresponse_capitalize_hook/4}, {timeout, 500} ]), Port = ranch:get_port(onresponse_capitalize), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]; init_per_group(set_env, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(set_env, 100, [{port, 0}], [ {env, [{dispatch, []}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(set_env), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]. end_per_group(Name, _) when Name =:= https; Name =:= https_compress -> cowboy:stop_listener(Name), application:stop(ssl), application:stop(public_key), application:stop(asn1), ok; end_per_group(Name, _) -> cowboy:stop_listener(Name), ok. %% Dispatch configuration. init_dispatch(Config) -> cowboy_router:compile([ {"localhost", [ {"/chunked_response", http_chunked, []}, {"/init_shutdown", http_init_shutdown, []}, {"/long_polling", http_long_polling, []}, {"/headers/dupe", http_handler, [{headers, [{<<"connection">>, <<"close">>}]}]}, {"/set_resp/header", http_set_resp, [{headers, [{<<"vary">>, <<"Accept">>}]}]}, {"/set_resp/overwrite", http_set_resp, [{headers, [{<<"server">>, <<"DesireDrive/1.0">>}]}]}, {"/set_resp/body", http_set_resp, [{body, <<"A flameless dance does not equal a cycle">>}]}, {"/stream_body/set_resp", http_stream_body, [{reply, set_resp}, {body, <<"stream_body_set_resp">>}]}, {"/stream_body/set_resp_close", http_stream_body, [ {reply, set_resp_close}, {body, <<"stream_body_set_resp_close">>}]}, {"/stream_body/set_resp_chunked", http_stream_body, [ {reply, set_resp_chunked}, {body, [<<"stream_body">>, <<"_set_resp_chunked">>]}]}, {"/static/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, [{<<".css">>, [<<"text/css">>]}]}]}, {"/static_mimetypes_function/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, {fun(Path, data) when is_binary(Path) -> [<<"text/html">>] end, data}}]}, {"/handler_errors", http_errors, []}, {"/static_attribute_etag/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {etag, {attributes, [filepath, filesize, inode, mtime]}}]}, {"/static_function_etag/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {etag, {fun static_function_etag/2, etag_data}}]}, {"/static_specify_file/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, [{<<".css">>, [<<"text/css">>]}]}, {file, <<"style.css">>}]}, {"/multipart", http_multipart, []}, {"/echo/body", http_echo_body, []}, {"/echo/body_qs", http_body_qs, []}, {"/param_all", rest_param_all, []}, {"/bad_accept", rest_simple_resource, []}, {"/bad_content_type", rest_patch_resource, []}, {"/simple", rest_simple_resource, []}, {"/forbidden_post", rest_forbidden_resource, [true]}, {"/simple_post", rest_forbidden_resource, [false]}, {"/missing_get_callbacks", rest_missing_callbacks, []}, {"/missing_put_callbacks", rest_missing_callbacks, []}, {"/nodelete", rest_nodelete_resource, []}, {"/post_charset", rest_post_charset_resource, []}, {"/postonly", rest_postonly_resource, []}, {"/patch", rest_patch_resource, []}, {"/resetags", rest_resource_etags, []}, {"/rest_expires", rest_expires, []}, {"/rest_empty_resource", rest_empty_resource, []}, {"/loop_recv", http_loop_recv, []}, {"/loop_timeout", http_loop_timeout, []}, {"/", http_handler, []} ]} ]). %% Convenience functions. quick_raw(Data, Config) -> Client = ?config(client, Config), Transport = ?config(transport, Config), {ok, Client2} = cowboy_client:connect( Transport, "localhost", ?config(port, Config), Client), {ok, Client3} = cowboy_client:raw_request(Data, Client2), case cowboy_client:response(Client3) of {ok, Status, _, _} -> Status; {error, _} -> closed end. build_url(Path, Config) -> {scheme, Scheme} = lists:keyfind(scheme, 1, Config), {port, Port} = lists:keyfind(port, 1, Config), PortBin = list_to_binary(integer_to_list(Port)), PathBin = list_to_binary(Path), << Scheme/binary, "://localhost:", PortBin/binary, PathBin/binary >>. quick_get(URL, Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url(URL, Config), Client), {ok, Status, _, _} = cowboy_client:response(Client2), Status. body_to_chunks(_, <<>>, Acc) -> lists:reverse([<<"0\r\n\r\n">>|Acc]); body_to_chunks(ChunkSize, Body, Acc) -> BodySize = byte_size(Body), ChunkSize2 = case BodySize < ChunkSize of true -> BodySize; false -> ChunkSize end, << Chunk:ChunkSize2/binary, Rest/binary >> = Body, ChunkSizeBin = list_to_binary(integer_to_list(ChunkSize2, 16)), body_to_chunks(ChunkSize, Rest, [<< ChunkSizeBin/binary, "\r\n", Chunk/binary, "\r\n" >>|Acc]). %% Tests. check_raw_status(Config) -> Huge = [$0 || _ <- lists:seq(1, 5000)], HugeCookie = lists:flatten(["whatever_man_biiiiiiiiiiiig_cookie_me_want_77=" "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _ <- lists:seq(1, 40)]), ResponsePacket = "HTTP/1.0 302 Found\r Location: http://www.google.co.il/\r Cache-Control: private\r Content-Type: text/html; charset=UTF-8\r Set-Cookie: PREF=ID=568f67013d4a7afa:FF=0:TM=1323014101:LM=1323014101:S=XqctDWC65MzKT0zC; expires=Tue, 03-Dec-2013 15:55:01 GMT; path=/; domain=.google.com\r Date: Sun, 04 Dec 2011 15:55:01 GMT\r Server: gws\r Content-Length: 221\r X-XSS-Protection: 1; mode=block\r X-Frame-Options: SAMEORIGIN\r \r 302 Moved

302 Moved

The document has moved here. ", Tests = [ {102, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n" "Content-Length: 5000\r\n\r\n", 0:5000/unit:8 >>}, {200, ["GET / HTTP/1.0\r\nHost: localhost\r\n" "Set-Cookie: ", HugeCookie, "\r\n\r\n"]}, {200, "\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n"}, {200, "GET http://proxy/ HTTP/1.1\r\nHost: localhost\r\n\r\n"}, {200, <<"POST /loop_recv HTTP/1.1\r\nHost: localhost\r\n" "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>}, {400, "\n"}, {400, "Garbage\r\n\r\n"}, {400, "\r\n\r\n\r\n\r\n\r\n\r\n"}, {400, "GET / HTTP/1.1\r\nHost: ninenines.eu\r\n\r\n"}, {400, "GET http://proxy/ HTTP/1.1\r\n\r\n"}, {400, "GET / HTTP/1.1\r\nHost: localhost:bad_port\r\n\r\n"}, {505, ResponsePacket}, {408, "GET / HTTP/1.1\r\n"}, {408, "GET / HTTP/1.1\r\nHost: localhost"}, {408, "GET / HTTP/1.1\r\nHost: localhost\r\n"}, {408, "GET / HTTP/1.1\r\nHost: localhost\r\n\r"}, {414, Huge}, {400, "GET / HTTP/1.1\r\n" ++ Huge}, {500, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n" "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>}, {505, "GET / HTTP/1.2\r\nHost: localhost\r\n\r\n"}, {closed, ""}, {closed, "\r\n"}, {closed, "\r\n\r\n"}, {closed, "GET / HTTP/1.1"} ], _ = [{Status, Packet} = begin Ret = quick_raw(Packet, Config), {Ret, Packet} end || {Status, Packet} <- Tests], ok. check_status(Config) -> Tests = [ {102, "/long_polling"}, {200, "/"}, {200, "/simple"}, {204, "/loop_timeout"}, {400, "/static/%2f"}, {400, "/static/%2e"}, {400, "/static/%2e%2e"}, {403, "/static/directory"}, {403, "/static/directory/"}, {403, "/static/unreadable"}, {404, "/not/found"}, {404, "/static/not_found"}, {500, "/handler_errors?case=handler_before_reply"}, {500, "/handler_errors?case=init_before_reply"}, {666, "/init_shutdown"} ], _ = [{Status, URL} = begin Ret = quick_get(URL, Config), {Ret, URL} end || {Status, URL} <- Tests]. chunked_response(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/chunked_response", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), true = lists:keymember(<<"transfer-encoding">>, 1, Headers), {ok, Transport, Socket} = cowboy_client:transport(Client3), {ok, <<"11\r\nchunked_handler\r\n\r\nB\r\nworks fine!\r\n0\r\n\r\n">>} = Transport:recv(Socket, 44, 1000), {error, closed} = cowboy_client:response(Client3). %% Check if sending requests whose size is around the MTU breaks something. echo_body(Config) -> Client = ?config(client, Config), {ok, [{mtu, MTU}]} = inet:ifget("lo", [mtu]), _ = [begin Body = list_to_binary(lists:duplicate(Size, $a)), {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/echo/body", Config), [{<<"connection">>, <<"close">>}], Body, Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Body, _} = cowboy_client:response_body(Client3) end || Size <- lists:seq(MTU - 500, MTU)], ok. %% Check if sending request whose size is bigger than 1000000 bytes causes 413 echo_body_max_length(Config) -> Client = ?config(client, Config), Body = <<$a:8000008>>, {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/echo/body", Config), [{<<"connection">>, <<"close">>}], Body, Client), {ok, 413, _, _} = cowboy_client:response(Client2). % check if body_qs echo's back results echo_body_qs(Config) -> Client = ?config(client, Config), Body = <<"echo=67890">>, {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/echo/body_qs", Config), [{<<"connection">>, <<"close">>}], Body, Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, <<"67890">>, _} = cowboy_client:response_body(Client3). %% Check if sending request whose size is bigger 16000 bytes causes 413 echo_body_qs_max_length(Config) -> Client = ?config(client, Config), DefaultMaxBodyQsLength = 16000, % subtract "echo=" minus 1 byte from max to hit the limit Bits = (DefaultMaxBodyQsLength - 4) * 8, AppendedBody = <<$a:Bits>>, Body = <<"echo=", AppendedBody/binary>>, {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/echo/body_qs", Config), [{<<"connection">>, <<"close">>}], Body, Client), {ok, 413, _, _} = cowboy_client:response(Client2). error_chain_handle_after_reply(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/handler_errors?case=handle_after_reply", Config), Client2), {ok, 200, _, Client4} = cowboy_client:response(Client3), {ok, 200, _, Client5} = cowboy_client:response(Client4), {error, closed} = cowboy_client:response(Client5). error_chain_handle_before_reply(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/handler_errors?case=handle_before_reply", Config), Client2), {ok, 200, _, Client4} = cowboy_client:response(Client3), {ok, 500, _, Client5} = cowboy_client:response(Client4), {error, closed} = cowboy_client:response(Client5). error_handle_after_reply(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/handler_errors?case=handle_after_reply", Config), Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {error, closed} = cowboy_client:response(Client3). error_init_after_reply(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/handler_errors?case=init_after_reply", Config), Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {error, closed} = cowboy_client:response(Client3). error_init_reply_handle_error(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/handler_errors?case=init_reply_handle_error", Config), Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {error, closed} = cowboy_client:response(Client3). headers_dupe(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/headers/dupe", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"connection">>, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers), Connections = [H || H = {Name, _} <- Headers, Name =:= <<"connection">>], 1 = length(Connections), {error, closed} = cowboy_client:response(Client3). http10_chunkless(Config) -> Client = ?config(client, Config), Transport = ?config(transport, Config), {ok, Client2} = cowboy_client:connect( Transport, "localhost", ?config(port, Config), Client), Data = "GET /chunked_response HTTP/1.0\r\nHost: localhost\r\n\r\n", {ok, Client3} = cowboy_client:raw_request(Data, Client2), {ok, 200, Headers, Client4} = cowboy_client:response(Client3), false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), %% Hack: we just try to get 28 bytes and compare. {ok, Transport, Socket} = cowboy_client:transport(Client4), Buffer = element(7, Client4), Buffer2 = case Transport:recv(Socket, 28 - byte_size(Buffer), 1000) of {ok, Recv} -> << Buffer/binary, Recv/binary >>; _ -> Buffer end, <<"chunked_handler\r\nworks fine!">> = Buffer2. http10_hostless(Config) -> Port10 = ?config(port, Config) + 10, Name = list_to_atom("http10_hostless_" ++ integer_to_list(Port10)), ranch:start_listener(Name, 5, ?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}], cowboy_protocol, [ {env, [{dispatch, cowboy_router:compile([ {'_', [{"/http1.0/hostless", http_handler, []}]}])}]}, {max_keepalive, 50}, {timeout, 500}] ), 200 = quick_raw("GET /http1.0/hostless HTTP/1.0\r\n\r\n", [{port, Port10}|Config]), cowboy:stop_listener(http10). keepalive_max(Config) -> Client = ?config(client, Config), URL = build_url("/", Config), ok = keepalive_max_loop(Client, URL, 50). keepalive_max_loop(Client, _, 0) -> {error, closed} = cowboy_client:response(Client), ok; keepalive_max_loop(Client, URL, N) -> Headers = [{<<"connection">>, <<"keep-alive">>}], {ok, Client2} = cowboy_client:request(<<"GET">>, URL, Headers, Client), {ok, 200, RespHeaders, Client3} = cowboy_client:response(Client2), Expected = case N of 1 -> <<"close">>; N -> <<"keep-alive">> end, {<<"connection">>, Expected} = lists:keyfind(<<"connection">>, 1, RespHeaders), keepalive_max_loop(Client3, URL, N - 1). keepalive_nl(Config) -> Client = ?config(client, Config), URL = build_url("/", Config), ok = keepalive_nl_loop(Client, URL, 10). keepalive_nl_loop(Client, _, 0) -> {error, closed} = cowboy_client:response(Client), ok; keepalive_nl_loop(Client, URL, N) -> Headers = [{<<"connection">>, <<"keep-alive">>}], {ok, Client2} = cowboy_client:request(<<"GET">>, URL, Headers, Client), {ok, 200, RespHeaders, Client3} = cowboy_client:response(Client2), {<<"connection">>, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, RespHeaders), {ok, Transport, Socket} = cowboy_client:transport(Client2), ok = Transport:send(Socket, <<"\r\n">>), %% empty line keepalive_nl_loop(Client3, URL, N - 1). multipart(Config) -> Client = ?config(client, Config), Body = << "This is a preamble." "\r\n--OHai\r\nX-Name:answer\r\n\r\n42" "\r\n--OHai\r\nServer:Cowboy\r\n\r\nIt rocks!\r\n" "\r\n--OHai--" "This is an epiloque." >>, {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/multipart", Config), [{<<"content-type">>, <<"multipart/x-makes-no-sense; boundary=OHai">>}], Body, Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, RespBody, _} = cowboy_client:response_body(Client3), Parts = binary_to_term(RespBody), Parts = [ {[{<<"x-name">>, <<"answer">>}], <<"42">>}, {[{<<"server">>, <<"Cowboy">>}], <<"It rocks!\r\n">>} ]. nc_reqs(Config, Input) -> Cat = os:find_executable("cat"), Nc = os:find_executable("nc"), case {Cat, Nc} of {false, _} -> {skip, {notfound, cat}}; {_, false} -> {skip, {notfound, nc}}; _Good -> %% Throw garbage at the server then check if it's still up. {port, Port} = lists:keyfind(port, 1, Config), StrPort = integer_to_list(Port), [os:cmd("cat " ++ Input ++ " | nc localhost " ++ StrPort) || _ <- lists:seq(1, 100)], 200 = quick_get("/", Config) end. nc_rand(Config) -> nc_reqs(Config, "/dev/urandom"). nc_zero(Config) -> nc_reqs(Config, "/dev/zero"). onrequest(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"server">>, <<"Serenity">>} = lists:keyfind(<<"server">>, 1, Headers), {ok, <<"http_handler">>, _} = cowboy_client:response_body(Client3). onrequest_reply(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/?reply=1", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"server">>, <<"Cowboy">>} = lists:keyfind(<<"server">>, 1, Headers), {ok, <<"replied!">>, _} = cowboy_client:response_body(Client3). %% Hook for the above onrequest tests. onrequest_hook(Req) -> case cowboy_req:qs_val(<<"reply">>, Req) of {undefined, Req2} -> cowboy_req:set_resp_header(<<"server">>, <<"Serenity">>, Req2); {_, Req2} -> {ok, Req3} = cowboy_req:reply( 200, [], <<"replied!">>, Req2), Req3 end. onresponse_capitalize(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, Transport, Socket} = cowboy_client:transport(Client2), {ok, Data} = Transport:recv(Socket, 0, 1000), false = nomatch =:= binary:match(Data, <<"Content-Length">>). %% Hook for the above onresponse_capitalize test. onresponse_capitalize_hook(Status, Headers, Body, Req) -> Headers2 = [{cowboy_bstr:capitalize_token(N), V} || {N, V} <- Headers], {ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req), Req2. onresponse_crash(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/handler_errors?case=init_before_reply", Config), Client), {ok, 777, Headers, _} = cowboy_client:response(Client2), {<<"x-hook">>, <<"onresponse">>} = lists:keyfind(<<"x-hook">>, 1, Headers). onresponse_reply(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, 777, Headers, Client3} = cowboy_client:response(Client2), {<<"x-hook">>, <<"onresponse">>} = lists:keyfind(<<"x-hook">>, 1, Headers), %% Make sure we don't get the body initially sent. {error, closed} = cowboy_client:response_body(Client3). %% Hook for the above onresponse tests. onresponse_hook(_, Headers, _, Req) -> {ok, Req2} = cowboy_req:reply( <<"777 Lucky">>, [{<<"x-hook">>, <<"onresponse">>}|Headers], Req), Req2. pipeline(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client2), {ok, Client4} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client3), {ok, Client5} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client4), {ok, Client6} = cowboy_client:request(<<"GET">>, build_url("/", Config), [{<<"connection">>, <<"close">>}], Client5), {ok, 200, _, Client7} = cowboy_client:response(Client6), {ok, 200, _, Client8} = cowboy_client:response(Client7), {ok, 200, _, Client9} = cowboy_client:response(Client8), {ok, 200, _, Client10} = cowboy_client:response(Client9), {ok, 200, _, Client11} = cowboy_client:response(Client10), {error, closed} = cowboy_client:response(Client11). pipeline_long_polling(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/long_polling", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/long_polling", Config), Client2), {ok, 102, _, Client4} = cowboy_client:response(Client3), {ok, 102, _, Client5} = cowboy_client:response(Client4), {error, closed} = cowboy_client:response(Client5). rest_param_all(Config) -> Client = ?config(client, Config), URL = build_url("/param_all", Config), % Accept without param {ok, Client2} = cowboy_client:request(<<"GET">>, URL, [{<<"accept">>, <<"text/plain">>}], Client), Client3 = check_response(Client2, <<"[]">>), % Accept with param {ok, Client4} = cowboy_client:request(<<"GET">>, URL, [{<<"accept">>, <<"text/plain;level=1">>}], Client3), Client5 = check_response(Client4, <<"level=1">>), % Accept with param and quality {ok, Client6} = cowboy_client:request(<<"GET">>, URL, [{<<"accept">>, <<"text/plain;level=1;q=0.8, text/plain;level=2;q=0.5">>}], Client5), Client7 = check_response(Client6, <<"level=1">>), {ok, Client8} = cowboy_client:request(<<"GET">>, URL, [{<<"accept">>, <<"text/plain;level=1;q=0.5, text/plain;level=2;q=0.8">>}], Client7), Client9 = check_response(Client8, <<"level=2">>), % Without Accept {ok, Client10} = cowboy_client:request(<<"GET">>, URL, [], Client9), Client11 = check_response(Client10, <<"'*'">>), % Content-Type without param {ok, Client12} = cowboy_client:request(<<"PUT">>, URL, [{<<"content-type">>, <<"text/plain">>}], Client11), {ok, 204, _, Client13} = cowboy_client:response(Client12), % Content-Type with param {ok, Client14} = cowboy_client:request(<<"PUT">>, URL, [{<<"content-type">>, <<"text/plain; charset=utf-8">>}], Client13), {ok, 204, _, _} = cowboy_client:response(Client14). check_response(Client, Body) -> {ok, 200, _, Client2} = cowboy_client:response(Client), {ok, Body, Client3} = cowboy_client:response_body(Client2), Client3. rest_bad_accept(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/bad_accept", Config), [{<<"accept">>, <<"1">>}], Client), {ok, 400, _, _} = cowboy_client:response(Client2). rest_bad_content_type(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"PATCH">>, build_url("/bad_content_type", Config), [{<<"content-type">>, <<"text/plain, text/html">>}], <<"Whatever">>, Client), {ok, 415, _, _} = cowboy_client:response(Client2). rest_expires(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/rest_expires", Config), Client), {ok, 200, RespHeaders, _} = cowboy_client:response(Client2), {_, Expires} = lists:keyfind(<<"expires">>, 1, RespHeaders), {_, LastModified} = lists:keyfind(<<"last-modified">>, 1, RespHeaders), Expires = LastModified = <<"Fri, 21 Sep 2012 22:36:14 GMT">>, ok. rest_keepalive(Config) -> Client = ?config(client, Config), URL = build_url("/simple", Config), ok = rest_keepalive_loop(Client, URL, 10). rest_keepalive_loop(_, _, 0) -> ok; rest_keepalive_loop(Client, URL, N) -> Headers = [{<<"connection">>, <<"keep-alive">>}], {ok, Client2} = cowboy_client:request(<<"GET">>, URL, Headers, Client), {ok, 200, RespHeaders, Client3} = cowboy_client:response(Client2), {<<"connection">>, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, RespHeaders), rest_keepalive_loop(Client3, URL, N - 1). rest_keepalive_post(Config) -> Client = ?config(client, Config), ok = rest_keepalive_post_loop(Config, Client, forbidden_post, 10). rest_keepalive_post_loop(_, _, _, 0) -> ok; rest_keepalive_post_loop(Config, Client, simple_post, N) -> Headers = [ {<<"connection">>, <<"keep-alive">>}, {<<"content-type">>, <<"text/plain">>} ], {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/simple_post", Config), Headers, "12345", Client), {ok, 303, RespHeaders, Client3} = cowboy_client:response(Client2), {<<"connection">>, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, RespHeaders), rest_keepalive_post_loop(Config, Client3, forbidden_post, N - 1); rest_keepalive_post_loop(Config, Client, forbidden_post, N) -> Headers = [ {<<"connection">>, <<"keep-alive">>}, {<<"content-type">>, <<"text/plain">>} ], {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/forbidden_post", Config), Headers, "12345", Client), {ok, 403, RespHeaders, Client3} = cowboy_client:response(Client2), {<<"connection">>, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, RespHeaders), rest_keepalive_post_loop(Config, Client3, simple_post, N - 1). rest_missing_get_callbacks(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/missing_get_callbacks", Config), Client), {ok, 500, _, _} = cowboy_client:response(Client2). rest_missing_put_callbacks(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"PUT">>, build_url("/missing_put_callbacks", Config), [{<<"content-type">>, <<"application/json">>}], <<"{}">>, Client), {ok, 500, _, _} = cowboy_client:response(Client2). rest_nodelete(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"DELETE">>, build_url("/nodelete", Config), Client), {ok, 500, _, _} = cowboy_client:response(Client2). rest_options_default(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"OPTIONS">>, build_url("/rest_empty_resource", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers). rest_patch(Config) -> Tests = [ {204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>}, {422, [{<<"content-type">>, <<"text/plain">>}], <<"false">>}, {400, [{<<"content-type">>, <<"text/plain">>}], <<"halt">>}, {415, [{<<"content-type">>, <<"application/json">>}], <<"bad_content_type">>} ], Client = ?config(client, Config), _ = [begin {ok, Client2} = cowboy_client:request(<<"PATCH">>, build_url("/patch", Config), Headers, Body, Client), {ok, Status, _, _} = cowboy_client:response(Client2), ok end || {Status, Headers, Body} <- Tests]. rest_post_charset(Config) -> Client = ?config(client, Config), Headers = [ {<<"content-type">>, <<"text/plain;charset=UTF-8">>} ], {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/post_charset", Config), Headers, "12345", Client), {ok, 204, _, _} = cowboy_client:response(Client2). rest_postonly(Config) -> Client = ?config(client, Config), Headers = [ {<<"content-type">>, <<"text/plain">>} ], {ok, Client2} = cowboy_client:request(<<"POST">>, build_url("/postonly", Config), Headers, "12345", Client), {ok, 204, _, _} = cowboy_client:response(Client2). rest_resource_get_etag(Config, Type) -> rest_resource_get_etag(Config, Type, []). rest_resource_get_etag(Config, Type, Headers) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/resetags?type=" ++ Type, Config), Headers, Client), {ok, Status, RespHeaders, _} = cowboy_client:response(Client2), case lists:keyfind(<<"etag">>, 1, RespHeaders) of false -> {Status, false}; {<<"etag">>, ETag} -> {Status, ETag} end. rest_resource_etags(Config) -> Tests = [ {200, <<"W/\"etag-header-value\"">>, "tuple-weak"}, {200, <<"\"etag-header-value\"">>, "tuple-strong"}, {200, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"}, {200, <<"\"etag-header-value\"">>, "binary-strong-quoted"}, {500, false, "binary-strong-unquoted"}, {500, false, "binary-weak-unquoted"} ], _ = [{Status, ETag, Type} = begin {Ret, RespETag} = rest_resource_get_etag(Config, Type), {Ret, RespETag, Type} end || {Status, ETag, Type} <- Tests]. rest_resource_etags_if_none_match(Config) -> Tests = [ {304, <<"W/\"etag-header-value\"">>, "tuple-weak"}, {304, <<"\"etag-header-value\"">>, "tuple-strong"}, {304, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"}, {304, <<"\"etag-header-value\"">>, "binary-strong-quoted"} ], _ = [{Status, Type} = begin {Ret, _} = rest_resource_get_etag(Config, Type, [{<<"if-none-match">>, ETag}]), {Ret, Type} end || {Status, ETag, Type} <- Tests]. set_env_dispatch(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, 400, _, _} = cowboy_client:response(Client2), ok = cowboy:set_env(set_env, dispatch, cowboy_router:compile([{'_', [{"/", http_handler, []}]}])), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/", Config), Client), {ok, 200, _, _} = cowboy_client:response(Client3). set_resp_body(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/set_resp/body", Config), Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, <<"A flameless dance does not equal a cycle">>, _} = cowboy_client:response_body(Client3). set_resp_header(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/set_resp/header", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"vary">>, <<"Accept">>} = lists:keyfind(<<"vary">>, 1, Headers), {<<"set-cookie">>, _} = lists:keyfind(<<"set-cookie">>, 1, Headers). set_resp_overwrite(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/set_resp/overwrite", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"server">>, <<"DesireDrive/1.0">>} = lists:keyfind(<<"server">>, 1, Headers). slowloris(Config) -> Client = ?config(client, Config), Transport = ?config(transport, Config), {ok, Client2} = cowboy_client:connect( Transport, "localhost", ?config(port, Config), Client), try [begin {ok, _} = cowboy_client:raw_request([C], Client2), receive after 25 -> ok end end || C <- "GET / HTTP/1.1\r\nHost: localhost\r\n" "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US)\r\n" "Cookie: name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n"], error(failure) catch error:{badmatch, _} -> ok end. slowloris2(Config) -> Client = ?config(client, Config), Transport = ?config(transport, Config), {ok, Client2} = cowboy_client:connect( Transport, "localhost", ?config(port, Config), Client), {ok, _} = cowboy_client:raw_request("GET / HTTP/1.1\r\n", Client2), receive after 300 -> ok end, {ok, _} = cowboy_client:raw_request("Host: localhost\r\n", Client2), receive after 300 -> ok end, {ok, 408, _, _} = cowboy_client:response(Client2). static_attribute_etag(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static_attribute_etag/index.html", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/static_attribute_etag/index.html", Config), Client2), {ok, 200, Headers1, Client4} = cowboy_client:response(Client3), {ok, 200, Headers2, _} = cowboy_client:response(Client4), {<<"etag">>, ETag1} = lists:keyfind(<<"etag">>, 1, Headers1), {<<"etag">>, ETag2} = lists:keyfind(<<"etag">>, 1, Headers2), false = ETag1 =:= undefined, ETag1 = ETag2. static_function_etag(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static_function_etag/index.html", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, build_url("/static_function_etag/index.html", Config), Client2), {ok, 200, Headers1, Client4} = cowboy_client:response(Client3), {ok, 200, Headers2, _} = cowboy_client:response(Client4), {<<"etag">>, ETag1} = lists:keyfind(<<"etag">>, 1, Headers1), {<<"etag">>, ETag2} = lists:keyfind(<<"etag">>, 1, Headers2), false = ETag1 =:= undefined, ETag1 = ETag2. %% Callback function for generating the ETag for the above test. static_function_etag(Arguments, etag_data) -> {_, Filepath} = lists:keyfind(filepath, 1, Arguments), {_, _Filesize} = lists:keyfind(filesize, 1, Arguments), {_, _INode} = lists:keyfind(inode, 1, Arguments), {_, _Modified} = lists:keyfind(mtime, 1, Arguments), ChecksumCommand = lists:flatten(io_lib:format("sha1sum ~s", [Filepath])), [Checksum|_] = string:tokens(os:cmd(ChecksumCommand), " "), {strong, iolist_to_binary(Checksum)}. static_mimetypes_function(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static_mimetypes_function/index.html", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/html">>} = lists:keyfind(<<"content-type">>, 1, Headers). static_specify_file(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static_specify_file", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers), {ok, <<"body{color:red}\n">>, _} = cowboy_client:response_body(Client3). static_specify_file_catchall(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static_specify_file/none", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers), {ok, <<"body{color:red}\n">>, _} = cowboy_client:response_body(Client3). static_test_file(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static/unknown", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"content-type">>, <<"application/octet-stream">>} = lists:keyfind(<<"content-type">>, 1, Headers). static_test_file_css(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/static/style.css", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers). stream_body_set_resp(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/stream_body/set_resp", Config), Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, <<"stream_body_set_resp">>, _} = cowboy_client:response_body(Client3). stream_body_set_resp_close(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/stream_body/set_resp_close", Config), Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Transport, Socket} = cowboy_client:transport(Client3), case element(7, Client3) of <<"stream_body_set_resp_close">> -> ok; Buffer -> {ok, Rest} = Transport:recv(Socket, 26 - byte_size(Buffer), 1000), <<"stream_body_set_resp_close">> = << Buffer/binary, Rest/binary >>, ok end, {error, closed} = Transport:recv(Socket, 0, 1000). stream_body_set_resp_chunked(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/stream_body/set_resp_chunked", Config), Client), {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {_, <<"chunked">>} = lists:keyfind(<<"transfer-encoding">>, 1, Headers), {ok, Transport, Socket} = cowboy_client:transport(Client3), case element(7, Client3) of <<"B\r\nstream_body\r\n11\r\n_set_resp_chunked\r\n0\r\n\r\n">> -> ok; Buffer -> {ok, Rest} = Transport:recv(Socket, 44 - byte_size(Buffer), 1000), <<"B\r\nstream_body\r\n11\r\n_set_resp_chunked\r\n0\r\n\r\n">> = <>, ok end. stream_body_set_resp_chunked10(Config) -> Client = ?config(client, Config), Transport = ?config(transport, Config), {ok, Client2} = cowboy_client:connect( Transport, "localhost", ?config(port, Config), Client), Data = ["GET /stream_body/set_resp_chunked HTTP/1.0\r\n", "Host: localhost\r\n\r\n"], {ok, Client3} = cowboy_client:raw_request(Data, Client2), {ok, 200, Headers, Client4} = cowboy_client:response(Client3), false = lists:keymember(<<"transfer-encoding">>, 1, Headers), {ok, Transport, Socket} = cowboy_client:transport(Client4), case element(7, Client4) of <<"stream_body_set_resp_chunked">> -> ok; Buffer -> {ok, Rest} = Transport:recv(Socket, 28 - byte_size(Buffer), 1000), <<"stream_body_set_resp_chunked">> = <>, ok end, {error, closed} = Transport:recv(Socket, 0, 1000). te_chunked(Config) -> Client = ?config(client, Config), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), Chunks = body_to_chunks(50, Body, []), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/echo/body", Config), [{<<"transfer-encoding">>, <<"chunked">>}], Chunks, Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Body, _} = cowboy_client:response_body(Client3). te_chunked_chopped(Config) -> Client = ?config(client, Config), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), Body2 = iolist_to_binary(body_to_chunks(50, Body, [])), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/echo/body", Config), [{<<"transfer-encoding">>, <<"chunked">>}], Client), {ok, Transport, Socket} = cowboy_client:transport(Client2), _ = [begin ok = Transport:send(Socket, << C >>), ok = timer:sleep(10) end || << C >> <= Body2], {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Body, _} = cowboy_client:response_body(Client3). te_chunked_delayed(Config) -> Client = ?config(client, Config), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), Chunks = body_to_chunks(50, Body, []), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/echo/body", Config), [{<<"transfer-encoding">>, <<"chunked">>}], Client), {ok, Transport, Socket} = cowboy_client:transport(Client2), _ = [begin ok = Transport:send(Socket, Chunk), ok = timer:sleep(10) end || Chunk <- Chunks], {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Body, _} = cowboy_client:response_body(Client3). te_identity(Config) -> Client = ?config(client, Config), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), {ok, Client2} = cowboy_client:request(<<"GET">>, build_url("/echo/body", Config), [], Body, Client), {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Body, _} = cowboy_client:response_body(Client3). erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/000077500000000000000000000000001223335133400211745ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_body_qs.erl000066400000000000000000000020611223335133400243760ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_body_qs). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_, http}, Req, _) -> {ok, Req, undefined}. handle(Req, State) -> {Method, Req2} = cowboy_req:method(Req), HasBody = cowboy_req:has_body(Req2), {ok, Req3} = maybe_echo(Method, HasBody, Req2), {ok, Req3, State}. maybe_echo(<<"POST">>, true, Req) -> case cowboy_req:body_qs(Req) of {error,badlength} -> echo(badlength, Req); {ok, PostVals, Req2} -> echo(proplists:get_value(<<"echo">>, PostVals), Req2) end; maybe_echo(<<"POST">>, false, Req) -> cowboy_req:reply(400, [], <<"Missing body.">>, Req); maybe_echo(_, _, Req) -> %% Method not allowed. cowboy_req:reply(405, Req). echo(badlength, Req) -> cowboy_req:reply(413, [], <<"POST body bigger than 16000 bytes">>, Req); echo(undefined, Req) -> cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req); echo(Echo, Req) -> cowboy_req:reply(200, [{<<"content-encoding">>, <<"utf-8">>}], Echo, Req). terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_chunked.erl000066400000000000000000000007261223335133400243650ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_chunked). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_Transport, http}, Req, _Opts) -> {ok, Req, undefined}. handle(Req, State) -> {ok, Req2} = cowboy_req:chunked_reply(200, Req), timer:sleep(100), cowboy_req:chunk("chunked_handler\r\n", Req2), timer:sleep(100), cowboy_req:chunk("works fine!", Req2), {ok, Req2, State}. terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_echo_body.erl000066400000000000000000000023221223335133400246710ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_echo_body). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_, http}, Req, _) -> {ok, Req, undefined}. handle(Req, State) -> true = cowboy_req:has_body(Req), {ok, Req3} = case cowboy_req:body(1000000, Req) of {error, chunked} -> handle_chunked(Req); {error, badlength} -> handle_badlength(Req); {ok, Body, Req2} -> handle_body(Req2, Body) end, {ok, Req3, State}. handle_chunked(Req) -> {ok, Data, Req2} = read_body(Req, <<>>, 1000000), {ok, Req3} = cowboy_req:reply(200, [], Data, Req2), {ok, Req3}. handle_badlength(Req) -> {ok, Req2} = cowboy_req:reply(413, [], <<"Request entity too large">>, Req), {ok, Req2}. handle_body(Req, Body) -> {Size, Req2} = cowboy_req:body_length(Req), Size = byte_size(Body), {ok, Req3} = cowboy_req:reply(200, [], Body, Req2), {ok, Req3}. terminate(_, _, _) -> ok. % Read chunked request content read_body(Req, Acc, BodyLengthRemaining) -> case cowboy_req:stream_body(Req) of {ok, Data, Req2} -> BodyLengthRem = BodyLengthRemaining - byte_size(Data), read_body(Req2, << Acc/binary, Data/binary >>, BodyLengthRem); {done, Req2} -> {ok, Acc, Req2} end. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_errors.erl000066400000000000000000000021561223335133400242570ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_errors). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_Transport, http}, Req, _Opts) -> {Case, Req1} = cowboy_req:qs_val(<<"case">>, Req), case_init(Case, Req1). case_init(<<"init_before_reply">> = Case, _Req) -> erlang:error(Case); case_init(<<"init_after_reply">> = Case, Req) -> {ok, _Req1} = cowboy_req:reply(200, [], "http_handler_crashes", Req), erlang:error(Case); case_init(<<"init_reply_handle_error">> = Case, Req) -> {ok, Req1} = cowboy_req:reply(200, [], "http_handler_crashes", Req), {ok, Req1, Case}; case_init(<<"handle_before_reply">> = Case, Req) -> {ok, Req, Case}; case_init(<<"handle_after_reply">> = Case, Req) -> {ok, Req, Case}. handle(_Req, <<"init_reply_handle_error">> = Case) -> erlang:error(Case); handle(_Req, <<"handle_before_reply">> = Case) -> erlang:error(Case); handle(Req, <<"handle_after_reply">> = Case) -> {ok, _Req1} = cowboy_req:reply(200, [], "http_handler_crashes", Req), erlang:error(Case). terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_handler.erl000066400000000000000000000010501223335133400243500ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_handler). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). -record(state, {headers, body}). init({_Transport, http}, Req, Opts) -> Headers = proplists:get_value(headers, Opts, []), Body = proplists:get_value(body, Opts, "http_handler"), {ok, Req, #state{headers=Headers, body=Body}}. handle(Req, State=#state{headers=Headers, body=Body}) -> {ok, Req2} = cowboy_req:reply(200, Headers, Body, Req), {ok, Req2, State}. terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_init_shutdown.erl000066400000000000000000000007341223335133400256410ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_init_shutdown). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_Transport, http}, Req, _Opts) -> {ok, Req2} = cowboy_req:reply(<<"666 Init Shutdown Testing">>, [{<<"connection">>, <<"close">>}], Req), {shutdown, Req2, undefined}. handle(Req, State) -> {ok, Req2} = cowboy_req:reply(200, [], "Hello world!", Req), {ok, Req2, State}. terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_long_polling.erl000066400000000000000000000011401223335133400254160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_long_polling). -behaviour(cowboy_http_handler). -export([init/3, handle/2, info/3, terminate/3]). init({_Transport, http}, Req, _Opts) -> erlang:send_after(500, self(), timeout), {loop, Req, 5, 5000, hibernate}. handle(_Req, _State) -> exit(badarg). info(timeout, Req, 0) -> {ok, Req2} = cowboy_req:reply(102, Req), {ok, Req2, 0}; info(timeout, Req, State) -> erlang:send_after(500, self(), timeout), {loop, Req, State - 1, hibernate}. terminate({normal, shutdown}, _, _) -> ok; terminate({error, overflow}, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_loop_recv.erl000066400000000000000000000007201223335133400247260ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_loop_recv). -behaviour(cowboy_loop_handler). -export([init/3, info/3, terminate/3]). init({_, http}, Req, _) -> self() ! recv_timeout, {loop, Req, undefined, 500, hibernate}. info(recv_timeout, Req, State) -> {ok, Body, Req1} = cowboy_req:body(Req), 100000 = byte_size(Body), {ok, Req2} = cowboy_req:reply(200, Req1), {ok, Req2, State}. terminate({normal, shutdown}, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_loop_timeout.erl000066400000000000000000000006461223335133400254640ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_loop_timeout). -behaviour(cowboy_loop_handler). -export([init/3, info/3, terminate/3]). init({_, http}, Req, _) -> erlang:send_after(1000, self(), error_timeout), {loop, Req, undefined, 500, hibernate}. info(error_timeout, Req, State) -> {ok, Req2} = cowboy_req:reply(500, Req), {ok, Req2, State}. terminate({normal, timeout}, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_multipart.erl000066400000000000000000000017021223335133400247600ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_multipart). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_Transport, http}, Req, []) -> {ok, Req, {}}. handle(Req, State) -> {Result, Req2} = acc_multipart(Req), {ok, Req3} = cowboy_req:reply(200, [], term_to_binary(Result), Req2), {ok, Req3, State}. terminate(_, _, _) -> ok. acc_multipart(Req) -> acc_multipart(cowboy_req:multipart_data(Req), []). acc_multipart({headers, Headers, Req}, Acc) -> acc_multipart(cowboy_req:multipart_data(Req), [{Headers, []}|Acc]); acc_multipart({body, Data, Req}, [{Headers, BodyAcc}|Acc]) -> acc_multipart(cowboy_req:multipart_data(Req), [{Headers, [Data|BodyAcc]}|Acc]); acc_multipart({end_of_part, Req}, [{Headers, BodyAcc}|Acc]) -> acc_multipart(cowboy_req:multipart_data(Req), [{Headers, list_to_binary(lists:reverse(BodyAcc))}|Acc]); acc_multipart({eof, Req}, Acc) -> {lists:reverse(Acc), Req}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_set_resp.erl000066400000000000000000000017021223335133400245630ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_set_resp). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). init({_Transport, http}, Req, Opts) -> Headers = proplists:get_value(headers, Opts, []), Body = proplists:get_value(body, Opts, <<"http_handler_set_resp">>), Req2 = lists:foldl(fun({Name, Value}, R) -> cowboy_req:set_resp_header(Name, Value, R) end, Req, Headers), Req3 = cowboy_req:set_resp_body(Body, Req2), Req4 = cowboy_req:set_resp_header(<<"x-cowboy-test">>, <<"ok">>, Req3), Req5 = cowboy_req:set_resp_cookie(<<"cake">>, <<"lie">>, [], Req4), {ok, Req5, undefined}. handle(Req, State) -> case cowboy_req:has_resp_header(<<"x-cowboy-test">>, Req) of false -> {ok, Req, State}; true -> case cowboy_req:has_resp_body(Req) of false -> {ok, Req, State}; true -> {ok, Req2} = cowboy_req:reply(200, Req), {ok, Req2, State} end end. terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/http_stream_body.erl000066400000000000000000000020661223335133400252530ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(http_stream_body). -behaviour(cowboy_http_handler). -export([init/3, handle/2, terminate/3]). -record(state, {headers, body, reply}). init({_Transport, http}, Req, Opts) -> Headers = proplists:get_value(headers, Opts, []), Body = proplists:get_value(body, Opts, "http_handler_stream_body"), Reply = proplists:get_value(reply, Opts), {ok, Req, #state{headers=Headers, body=Body, reply=Reply}}. handle(Req, State=#state{headers=_Headers, body=Body, reply=Reply}) -> SFun = fun(Socket, Transport) -> Transport:send(Socket, Body) end, Req2 = case Reply of set_resp -> SLen = iolist_size(Body), cowboy_req:set_resp_body_fun(SLen, SFun, Req); set_resp_close -> cowboy_req:set_resp_body_fun(SFun, Req); set_resp_chunked -> %% Here Body should be a list of chunks, not a binary. SFun2 = fun(SendFun) -> lists:foreach(SendFun, Body) end, cowboy_req:set_resp_body_fun(chunked, SFun2, Req) end, {ok, Req3} = cowboy_req:reply(200, Req2), {ok, Req3, State}. terminate(_, _, _) -> ok. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_empty_resource.erl000066400000000000000000000001661223335133400260050ustar00rootroot00000000000000-module(rest_empty_resource). -export([init/3]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_expires.erl000066400000000000000000000010421223335133400244110ustar00rootroot00000000000000-module(rest_expires). -export([init/3]). -export([content_types_provided/2]). -export([get_text_plain/2]). -export([expires/2]). -export([last_modified/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. get_text_plain(Req, State) -> {<<"This is REST!">>, Req, State}. expires(Req, State) -> {{{2012, 9, 21}, {22, 36, 14}}, Req, State}. last_modified(Req, State) -> {{{2012, 9, 21}, {22, 36, 14}}, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_forbidden_resource.erl000066400000000000000000000015131223335133400266000ustar00rootroot00000000000000-module(rest_forbidden_resource). -export([init/3, rest_init/2, allowed_methods/2, forbidden/2, content_types_provided/2, content_types_accepted/2, to_text/2, from_text/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. rest_init(Req, [Forbidden]) -> {ok, Req, Forbidden}. allowed_methods(Req, State) -> {[<<"GET">>, <<"HEAD">>, <<"POST">>], Req, State}. forbidden(Req, State=true) -> {true, Req, State}; forbidden(Req, State=false) -> {false, Req, State}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, to_text}], Req, State}. content_types_accepted(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, from_text}], Req, State}. to_text(Req, State) -> {<<"This is REST!">>, Req, State}. from_text(Req, State) -> {Path, Req2} = cowboy_req:path(Req), {{true, Path}, Req2, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_missing_callbacks.erl000066400000000000000000000007661223335133400264160ustar00rootroot00000000000000-module(rest_missing_callbacks). -export([init/3]). -export([allowed_methods/2]). -export([content_types_accepted/2]). -export([content_types_provided/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"GET">>, <<"PUT">>], Req, State}. content_types_accepted(Req, State) -> {[ {<<"application/json">>, put_application_json} ], Req, State}. content_types_provided(Req, State) -> {[ {<<"text/plain">>, get_text_plain} ], Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_nodelete_resource.erl000066400000000000000000000006761223335133400264540ustar00rootroot00000000000000-module(rest_nodelete_resource). -export([init/3, allowed_methods/2, content_types_provided/2, get_text_plain/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"GET">>, <<"HEAD">>, <<"DELETE">>], Req, State}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. get_text_plain(Req, State) -> {<<"This is REST!">>, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_param_all.erl000066400000000000000000000016211223335133400246650ustar00rootroot00000000000000-module(rest_param_all). -export([init/3]). -export([allowed_methods/2]). -export([content_types_provided/2]). -export([get_text_plain/2]). -export([content_types_accepted/2]). -export([put_text_plain/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"GET">>, <<"PUT">>], Req, State}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}. get_text_plain(Req, State) -> {{_, _, Param}, Req2} = cowboy_req:meta(media_type, Req, {{<<"text">>, <<"plain">>}, []}), Body = if Param == '*' -> <<"'*'">>; Param == [] -> <<"[]">>; Param /= [] -> iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param]) end, {Body, Req2, State}. content_types_accepted(Req, State) -> {[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}. put_text_plain(Req, State) -> {true, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_patch_resource.erl000066400000000000000000000017001223335133400257410ustar00rootroot00000000000000-module(rest_patch_resource). -export([init/3, allowed_methods/2, content_types_provided/2, get_text_plain/2, content_types_accepted/2, patch_text_plain/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"HEAD">>, <<"GET">>, <<"PATCH">>], Req, State}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. get_text_plain(Req, State) -> {<<"This is REST!">>, Req, State}. content_types_accepted(Req, State) -> case cowboy_req:method(Req) of {<<"PATCH">>, Req0} -> {[{{<<"text">>, <<"plain">>, []}, patch_text_plain}], Req0, State}; {_, Req0} -> {[], Req0, State} end. patch_text_plain(Req, State) -> case cowboy_req:body(Req) of {ok, <<"halt">>, Req0} -> {ok, Req1} = cowboy_req:reply(400, Req0), {halt, Req1, State}; {ok, <<"false">>, Req0} -> {false, Req0, State}; {ok, _Body, Req0} -> {true, Req0, State} end. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_post_charset_resource.erl000066400000000000000000000006451223335133400273470ustar00rootroot00000000000000-module(rest_post_charset_resource). -export([init/3, allowed_methods/2, content_types_accepted/2, from_text/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"POST">>], Req, State}. content_types_accepted(Req, State) -> {[{{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]}, from_text}], Req, State}. from_text(Req, State) -> {true, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_postonly_resource.erl000066400000000000000000000006041223335133400265330ustar00rootroot00000000000000-module(rest_postonly_resource). -export([init/3, allowed_methods/2, content_types_accepted/2, from_text/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. allowed_methods(Req, State) -> {[<<"POST">>], Req, State}. content_types_accepted(Req, State) -> {[{{<<"text">>, <<"plain">>, '*'}, from_text}], Req, State}. from_text(Req, State) -> {true, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_resource_etags.erl000066400000000000000000000021141223335133400257450ustar00rootroot00000000000000-module(rest_resource_etags). -export([init/3, generate_etag/2, content_types_provided/2, get_text_plain/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. generate_etag(Req, State) -> case cowboy_req:qs_val(<<"type">>, Req) of %% Correct return values from generate_etag/2. {<<"tuple-weak">>, Req2} -> {{weak, <<"etag-header-value">>}, Req2, State}; {<<"tuple-strong">>, Req2} -> {{strong, <<"etag-header-value">>}, Req2, State}; %% Backwards compatible return values from generate_etag/2. {<<"binary-weak-quoted">>, Req2} -> {<<"W/\"etag-header-value\"">>, Req2, State}; {<<"binary-strong-quoted">>, Req2} -> {<<"\"etag-header-value\"">>, Req2, State}; %% Invalid return values from generate_etag/2. {<<"binary-strong-unquoted">>, Req2} -> {<<"etag-header-value">>, Req2, State}; {<<"binary-weak-unquoted">>, Req2} -> {<<"W/etag-header-value">>, Req2, State} end. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. get_text_plain(Req, State) -> {<<"This is REST!">>, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/http_SUITE_data/rest_simple_resource.erl000066400000000000000000000005201223335133400261320ustar00rootroot00000000000000-module(rest_simple_resource). -export([init/3, content_types_provided/2, get_text_plain/2]). init(_Transport, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}. get_text_plain(Req, State) -> {<<"This is REST!">>, Req, State}. erlang-cowboy-0.8.6+dfsg1/test/spdy_SUITE.erl000066400000000000000000000120001223335133400207000ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(spdy_SUITE). -include_lib("common_test/include/ct.hrl"). -include("../src/cowboy_spdy.hrl"). %% ct. -export([all/0]). -export([groups/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). -export([init_per_group/2]). -export([end_per_group/2]). %% Tests. -export([check_status/1]). %% ct. all() -> [{group, spdy}]. groups() -> [{spdy, [], [ check_status ]}]. init_per_suite(Config) -> application:start(crypto), application:start(ranch), application:start(cowboy), application:start(asn1), application:start(public_key), application:start(ssl), Dir = ?config(priv_dir, Config) ++ "/static", ct_helper:create_static_dir(Dir), [{static_dir, Dir}|Config]. end_per_suite(Config) -> Dir = ?config(static_dir, Config), ct_helper:delete_static_dir(Dir), application:stop(ssl), application:stop(public_key), application:stop(asn1), application:stop(cowboy), application:stop(ranch), application:stop(crypto), ok. init_per_group(Name, Config) -> {_, Cert, Key} = ct_helper:make_certs(), Opts = [{cert, Cert}, {key, Key}], {ok, _} = cowboy:start_spdy(Name, 100, Opts ++ [{port, 0}], [ {env, [{dispatch, init_dispatch(Config)}]} ]), Port = ranch:get_port(Name), [{port, Port}|Config]. end_per_group(Name, _) -> cowboy:stop_listener(Name), ok. %% Dispatch configuration. init_dispatch(Config) -> cowboy_router:compile([ {"localhost", [ {"/static/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, [{<<".css">>, [<<"text/css">>]}]}]}, {"/chunked", http_chunked, []}, {"/", http_handler, []} ]} ]). %% Convenience functions. quick_get(Host, Path, ExpectedFlags, Config) -> {_, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = ssl:connect("localhost", Port, [ binary, {active, false}, {client_preferred_next_protocols, client, [<<"spdy/3">>]} ]), {Zdef, Zinf} = zlib_init(), ReqHeaders = headers_encode(Zdef, [ {<<":method">>, <<"GET">>}, {<<":path">>, list_to_binary(Path)}, {<<":version">>, <<"HTTP/1.1">>}, {<<":host">>, list_to_binary(Host)}, {<<":scheme">>, <<"https">>} ]), ReqLength = 10 + byte_size(ReqHeaders), StreamID = 1, ok = ssl:send(Socket, << 1:1, 3:15, 1:16, 0:8, ReqLength:24, 0:1, StreamID:31, 0:1, 0:31, 0:3, 0:5, 0:8, ReqHeaders/binary >>), {ok, Packet} = ssl:recv(Socket, 0, 1000), << 1:1, 3:15, 2:16, Flags:8, RespLength:24, _:1, StreamID:31, RespHeaders/bits >> = Packet, Flags = ExpectedFlags, RespLength = 4 + byte_size(RespHeaders), [<< NbHeaders:32, Rest/bits >>] = try zlib:inflate(Zinf, RespHeaders) catch _:_ -> ok = zlib:inflateSetDictionary(Zinf, ?ZDICT), zlib:inflate(Zinf, <<>>) end, RespHeaders2 = headers_decode(Zinf, Rest, []), NbHeaders = length(RespHeaders2), {_, << Status:3/binary, _/bits >>} = lists:keyfind(<<":status">>, 1, RespHeaders2), StatusCode = list_to_integer(binary_to_list(Status)), ok = ssl:close(Socket), zlib_terminate(Zdef, Zinf), {StatusCode, RespHeaders2}. zlib_init() -> Zdef = zlib:open(), ok = zlib:deflateInit(Zdef), _ = zlib:deflateSetDictionary(Zdef, ?ZDICT), Zinf = zlib:open(), ok = zlib:inflateInit(Zinf), {Zdef, Zinf}. zlib_terminate(Zdef, Zinf) -> zlib:close(Zdef), zlib:close(Zinf). headers_encode(Zdef, Headers) -> NbHeaders = length(Headers), Headers2 = << << (begin SizeN = byte_size(N), SizeV = byte_size(V), << SizeN:32, N/binary, SizeV:32, V/binary >> end)/binary >> || {N, V} <- Headers >>, Headers3 = << NbHeaders:32, Headers2/binary >>, iolist_to_binary(zlib:deflate(Zdef, Headers3, full)). headers_decode(_, <<>>, Acc) -> lists:reverse(Acc); headers_decode(Zinf, << SizeN:32, Rest/bits >>, Acc) -> << Name:SizeN/binary, SizeV:32, Rest2/bits >> = Rest, << Value:SizeV/binary, Rest3/bits >> = Rest2, headers_decode(Zinf, Rest3, [{Name, Value}|Acc]). %% Tests. check_status(Config) -> Tests = [ {200, nofin, "localhost", "/"}, {200, nofin, "localhost", "/chunked"}, {200, nofin, "localhost", "/static/style.css"}, {400, fin, "bad-host", "/"}, {400, fin, "localhost", "bad-path"}, {404, fin, "localhost", "/this/path/does/not/exist"} ], _ = [{Status, Fin, Host, Path} = begin RespFlags = case Fin of fin -> 1; nofin -> 0 end, {Ret, _} = quick_get(Host, Path, RespFlags, Config), {Ret, Fin, Host, Path} end || {Status, Fin, Host, Path} <- Tests]. erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE.erl000066400000000000000000000576461223335133400204030ustar00rootroot00000000000000%% Copyright (c) 2011-2013, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -module(ws_SUITE). -include_lib("common_test/include/ct.hrl"). %% ct. -export([all/0]). -export([groups/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). -export([init_per_group/2]). -export([end_per_group/2]). %% Tests. -export([ws0/1]). -export([ws8/1]). -export([ws8_init_shutdown/1]). -export([ws8_single_bytes/1]). -export([ws13/1]). -export([ws_deflate/1]). -export([ws_send_close/1]). -export([ws_send_close_payload/1]). -export([ws_send_many/1]). -export([ws_text_fragments/1]). -export([ws_timeout_hibernate/1]). -export([ws_timeout_cancel/1]). -export([ws_timeout_reset/1]). -export([ws_upgrade_with_opts/1]). %% ct. all() -> [{group, ws}]. groups() -> BaseTests = [ ws0, ws8, ws8_init_shutdown, ws8_single_bytes, ws13, ws_deflate, ws_send_close, ws_send_close_payload, ws_send_many, ws_text_fragments, ws_timeout_hibernate, ws_timeout_cancel, ws_timeout_reset, ws_upgrade_with_opts ], [{ws, [parallel], BaseTests}]. init_per_suite(Config) -> application:start(crypto), application:start(ranch), application:start(cowboy), Config. end_per_suite(_Config) -> application:stop(cowboy), application:stop(ranch), application:stop(crypto), ok. init_per_group(ws, Config) -> cowboy:start_http(ws, 100, [{port, 0}], [ {env, [{dispatch, init_dispatch()}]}, {compress, true} ]), Port = ranch:get_port(ws), [{port, Port}|Config]. end_per_group(Listener, _Config) -> cowboy:stop_listener(Listener), ok. %% Dispatch configuration. init_dispatch() -> cowboy_router:compile([ {"localhost", [ {"/ws_echo_timer", ws_echo_timer, []}, {"/ws_echo", ws_echo, []}, {"/ws_init_shutdown", ws_init_shutdown, []}, {"/ws_send_many", ws_send_many, [ {sequence, [ {text, <<"one">>}, {text, <<"two">>}, {text, <<"seven!">>}]} ]}, {"/ws_send_close", ws_send_many, [ {sequence, [ {text, <<"send">>}, close, {text, <<"won't be received">>}]} ]}, {"/ws_send_close_payload", ws_send_many, [ {sequence, [ {text, <<"send">>}, {close, 1001, <<"some text!">>}, {text, <<"won't be received">>}]} ]}, {"/ws_timeout_hibernate", ws_timeout_hibernate, []}, {"/ws_timeout_cancel", ws_timeout_cancel, []}, {"/ws_upgrade_with_opts", ws_upgrade_with_opts, <<"failure">>} ]} ]). %% ws and wss. %% We do not support hixie76 anymore. ws0(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, "GET /ws_echo_timer HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: WebSocket\r\n" "Origin: http://localhost\r\n" "Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n" "Sec-Websocket-Key2: 1711 M;4\\74 80<6\r\n" "\r\n"), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 400, _}, _} = erlang:decode_packet(http, Handshake, []). ws8(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_echo_timer HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws8_init_shutdown(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_init_shutdown HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest} = erlang:decode_packet(http, Handshake, []), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws8_single_bytes(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_echo_timer HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte ok = timer:sleep(100), %% sleep for a period ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#37 >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#fa >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#21 >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#3d >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#7f >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#9f >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#4d >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#51 >>), ok = timer:sleep(100), ok = gen_tcp:send(Socket, << 16#58 >>), {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws13(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_echo_timer HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Origin: http://localhost\r\n" "Sec-WebSocket-Version: 13\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Upgrade: websocket\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), %% text ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000), %% binary (empty) ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>), {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% binary ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d, 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), {ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000), %% Receives. {ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000), ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_deflate(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_echo HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), {"sec-websocket-extensions", "x-webkit-deflate-frame"} = lists:keyfind("sec-websocket-extensions", 1, Headers), % send uncompressed text frame containing the Hello string ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), % receive compressed text frame containing the Hello string {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, 242, 72, 205, 201, 201, 7, 0 >>} = gen_tcp:recv(Socket, 0, 6000), % send uncompressed text frame containing the HelloHello string % as 2 separate fragments ok = gen_tcp:send(Socket, [ << 0:1, 0:3, 1:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), ok = gen_tcp:send(Socket, [ << 1:1, 0:3, 0:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), % receive compressed text frame containing the HelloHello string {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 5:7, 242, 128, 19, 0, 0 >>} = gen_tcp:recv(Socket, 0, 6000), ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_send_close(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_send_close HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), %% We catch all frames at once and check them directly. {ok, Many} = gen_tcp:recv(Socket, 8, 6000), << 1:1, 0:3, 1:4, 0:1, 4:7, "send", 1:1, 0:3, 8:4, 0:8 >> = Many, {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_send_close_payload(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_send_close_payload HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), %% We catch all frames at once and check them directly. {ok, Many} = gen_tcp:recv(Socket, 20, 6000), << 1:1, 0:3, 1:4, 0:1, 4:7, "send", 1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many, {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_send_many(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_send_many HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), %% We catch all frames at once and check them directly. {ok, Many} = gen_tcp:recv(Socket, 18, 6000), << 1:1, 0:3, 1:4, 0:1, 3:7, "one", 1:1, 0:3, 1:4, 0:1, 3:7, "two", 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many, ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_text_fragments(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_echo HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), ok = gen_tcp:send(Socket, [ << 0:1, 0:3, 1:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), ok = gen_tcp:send(Socket, [ << 1:1, 0:3, 0:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), {ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>} = gen_tcp:recv(Socket, 0, 6000), ok = gen_tcp:send(Socket, [ %% #1 << 0:1, 0:3, 1:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>, %% #2 << 0:1, 0:3, 0:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>, %% #3 << 1:1, 0:3, 0:4, 1:1, 5:7 >>, << 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, << 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), {ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>} = gen_tcp:recv(Socket, 0, 6000), ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_timeout_hibernate(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_timeout_hibernate HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_timeout_cancel(Config) -> %% Erlang messages to a socket should not cancel the timeout {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_timeout_cancel HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_timeout_reset(Config) -> %% Erlang messages across a socket should reset the timeout {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_timeout_cancel HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-Websocket-Version: 13\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), [begin ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, 16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000), ok = timer:sleep(500) end || _ <- [1, 2, 3, 4]], {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. ws_upgrade_with_opts(Config) -> {port, Port} = lists:keyfind(port, 1, Config), {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]), ok = gen_tcp:send(Socket, [ "GET /ws_upgrade_with_opts HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: Upgrade\r\n" "Upgrade: websocket\r\n" "Sec-WebSocket-Origin: http://localhost\r\n" "Sec-WebSocket-Version: 8\r\n" "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" "\r\n"]), {ok, Handshake} = gen_tcp:recv(Socket, 0, 6000), {ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest} = erlang:decode_packet(http, Handshake, []), [Headers, <<>>] = websocket_headers( erlang:decode_packet(httph, Rest, []), []), {'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers), {'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers), {"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers), {ok, Response} = gen_tcp:recv(Socket, 9, 6000), << 1:1, 0:3, 1:4, 0:1, 7:7, "success" >> = Response, ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close {ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {error, closed} = gen_tcp:recv(Socket, 0, 6000), ok. %% Internal. websocket_headers({ok, http_eoh, Rest}, Acc) -> [Acc, Rest]; websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) -> F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end, websocket_headers(erlang:decode_packet(httph, Rest, []), [{F(Key), Value}|Acc]). erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/000077500000000000000000000000001223335133400206465ustar00rootroot00000000000000erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_echo.erl000066400000000000000000000013401223335133400227770ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_echo). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> Req2 = cowboy_req:compact(Req), {ok, Req2, undefined}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; websocket_handle({binary, Data}, Req, State) -> {reply, {binary, Data}, Req, State}; websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_echo_timer.erl000066400000000000000000000016611223335133400242050ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_echo_timer). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> erlang:start_timer(1000, self(), <<"websocket_init">>), Req2 = cowboy_req:compact(Req), {ok, Req2, undefined}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; websocket_handle({binary, Data}, Req, State) -> {reply, {binary, Data}, Req, State}; websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info({timeout, _Ref, Msg}, Req, State) -> erlang:start_timer(1000, self(), <<"websocket_handle">>), {reply, {text, Msg}, Req, State}; websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_init_shutdown.erl000066400000000000000000000011131223335133400247550ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_init_shutdown). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> {ok, Req2} = cowboy_req:reply(403, Req), {shutdown, Req2}. websocket_handle(_Frame, _Req, _State) -> exit(badarg). websocket_info(_Info, _Req, _State) -> exit(badarg). websocket_terminate(_Reason, _Req, _State) -> exit(badarg). erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_send_many.erl000066400000000000000000000012651223335133400240440ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_send_many). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, Sequence) -> Req2 = cowboy_req:compact(Req), erlang:send_after(10, self(), send_many), {ok, Req2, Sequence}. websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info(send_many, Req, State = [{sequence, Sequence}]) -> {reply, Sequence, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_timeout_cancel.erl000066400000000000000000000014111223335133400250530ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_timeout_cancel). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> erlang:start_timer(500, self(), should_not_cancel_timer), {ok, Req, undefined, 1000}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; websocket_handle({binary, Data}, Req, State) -> {reply, {binary, Data}, Req, State}. websocket_info(_Info, Req, State) -> erlang:start_timer(500, self(), should_not_cancel_timer), {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_timeout_hibernate.erl000066400000000000000000000011121223335133400255650ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_timeout_hibernate). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). init(_Any, _Req, _Opts) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_TransportName, Req, _Opts) -> {ok, Req, undefined, 1000, hibernate}. websocket_handle(_Frame, Req, State) -> {ok, Req, State, hibernate}. websocket_info(_Info, Req, State) -> {ok, Req, State, hibernate}. websocket_terminate(_Reason, _Req, _State) -> ok. erlang-cowboy-0.8.6+dfsg1/test/ws_SUITE_data/ws_upgrade_with_opts.erl000066400000000000000000000013551223335133400256160ustar00rootroot00000000000000%% Feel free to use, reuse and abuse the code in this file. -module(ws_upgrade_with_opts). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). init(_Any, Req, _Opts) -> {upgrade, protocol, cowboy_websocket, Req, <<"success">>}. websocket_init(_TransportName, Req, Response) -> Req2 = cowboy_req:compact(Req), erlang:send_after(10, self(), send_response), {ok, Req2, Response}. websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info(send_response, Req, State = Response) when is_binary(Response) -> {reply, {text, Response}, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok.