├── .github └── workflows │ └── test.yml ├── .gitignore ├── Changes ├── LICENSE ├── META6.json ├── README.md ├── dist.ini ├── examples └── test.raku ├── lib └── HTTP │ ├── Easy.rakumod │ └── Easy │ └── PSGI.rakumod └── t └── 01-load.rakutest /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macos-latest 18 | - windows-latest 19 | raku-version: 20 | - 'latest' 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: Raku/setup-raku@v1 25 | with: 26 | raku-version: ${{ matrix.raku-version }} 27 | - name: Install Dependencies 28 | run: zef install --/test --test-depends --deps-only . 29 | - name: Install App::Prove6 30 | run: zef install --/test App::Prove6 31 | - name: Run Tests 32 | run: prove6 -I. t 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .precomp/ 2 | /HTTP-Easy-* 3 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for HTTP-Easy 2 | 3 | {{$NEXT}} 4 | 5 | 1.2 2022-05-29T20:31:53+02:00 6 | - First version in the zef ecosystem 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": "zef:raku-community-modules", 3 | "authors": [ 4 | "Timothy Totten" 5 | ], 6 | "build-depends": [ 7 | ], 8 | "depends": [ 9 | "HTTP::Status", 10 | "PSGI" 11 | ], 12 | "description": "HTTP servers made easy, including PSGI", 13 | "license": "Artistic-2.0", 14 | "name": "HTTP::Easy", 15 | "perl": "6.*", 16 | "provides": { 17 | "HTTP::Easy": "lib/HTTP/Easy.rakumod", 18 | "HTTP::Easy::PSGI": "lib/HTTP/Easy/PSGI.rakumod" 19 | }, 20 | "resources": [ 21 | ], 22 | "source-url": "git://github.com/raku-community-modules/HTTP-Easy.git", 23 | "tags": [ 24 | ], 25 | "test-depends": [ 26 | ], 27 | "version": "1.2" 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/raku-community-modules/HTTP-Easy/actions/workflows/test.yml/badge.svg)](https://github.com/raku-community-modules/HTTP-Easy/actions) 2 | 3 | NAME 4 | ==== 5 | 6 | HTTP::Easy - HTTP servers made easy, including PSGI 7 | 8 | SYNOPSIS 9 | ======== 10 | 11 | ```raku 12 | use HTTP::Easy; 13 | ``` 14 | 15 | DESCRIPTION 16 | =========== 17 | 18 | Raku libraries to make HTTP servers easily. 19 | 20 | This was inspired by `HTTP::Server::Simple`, but has a very different internal API, and extended functionality. It's been designed to work well with the `Web::App` and `SCGI` libraries. 21 | 22 | HTTP::Easy 23 | ---------- 24 | 25 | A role to build HTTP daemon classes with. This provides the framework for parsing HTTP connections. 26 | 27 | HTTP::Easy::PSGI 28 | ---------------- 29 | 30 | A class consuming the `HTTP::Easy` role. This builds a PSGI environment, and passes it onto a handler. The handler must return a `PSGI` response: 31 | 32 | ```raku 33 | [ $status, @headers, @body ] 34 | ``` 35 | 36 | This can be used as an engine in the `Web::App` library. 37 | 38 | Example 39 | ------- 40 | 41 | ```raku 42 | use HTTP::Easy::PSGI; 43 | my $http = HTTP::Easy::PSGI.new(:port(8080)); 44 | 45 | my $app = sub (%env) { 46 | my $name = %env || "World"; 47 | [ 200, [ 'Content-Type' => 'text/plain' ], [ "Hello $name" ] ] 48 | } 49 | 50 | $http.handle($app); 51 | ``` 52 | 53 | TODO 54 | ==== 55 | 56 | Implement HTTP/1.1 features such as Transfer-Encoding, etc. 57 | 58 | AUTHOR 59 | ====== 60 | 61 | Timothy Totten 62 | 63 | COPYRIGHT AND LICENSE 64 | ===================== 65 | 66 | Copyright 2011 - 2017 Timothy Totten 67 | 68 | Copyright 2018 - 2022 Raku Community 69 | 70 | This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0. 71 | 72 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = HTTP-Easy 2 | 3 | [ReadmeFromPod] 4 | ; enabled = false 5 | filename = lib/HTTP/Easy.rakumod 6 | 7 | [UploadToZef] 8 | 9 | [PruneFiles] 10 | ; match = ^ 'xt/' 11 | 12 | [Badges] 13 | provider = github-actions/test.yml 14 | -------------------------------------------------------------------------------- /examples/test.raku: -------------------------------------------------------------------------------- 1 | use HTTP::Easy::PSGI; 2 | 3 | my $app = sub (%env) { 4 | my $name = %env || "World"; 5 | start { 6 | [ 200, [ 'Content-Type' => 'text/plain' ], [ "Hello $name" ] ]; 7 | } 8 | } 9 | 10 | ## Add :debug for more detailed output to STDERR. 11 | my $server = HTTP::Easy::PSGI.new(); # :debug 12 | $server.app($app); 13 | $server.run; 14 | 15 | # vim: expandtab shiftwidth=4 16 | -------------------------------------------------------------------------------- /lib/HTTP/Easy.rakumod: -------------------------------------------------------------------------------- 1 | ## A simple HTTP Daemon role. Inspired by HTTP::Server::Simple 2 | ## See HTTP::Easy::PSGI as the default daemon class implementation. 3 | 4 | use HTTP::Status; 5 | 6 | unit role HTTP::Easy; 7 | 8 | has Int:D $.port = 8080; 9 | has Str:D $.host = '0.0.0.0'; 10 | has Bool:D $.debug = False; 11 | has Bool:D $.silent = False; 12 | has $!listener; 13 | has $.connection; # To be populated by accept(). 14 | has %.env; # The environment, generated by run(). 15 | has $.http-protocol; # The HTTP version being used. 16 | has $.body; # Any request body, populated by run(). 17 | 18 | ## If set to true, we will read the body even if there is no CONTENT_LENGTH. 19 | has Bool $.always-get-body = False; 20 | 21 | my constant CRLF is export = "\x0D\x0A"; 22 | my constant DEFAULT_PROTOCOL is export = 'HTTP/1.0'; 23 | 24 | ## We're using DateTime.new(time) instead of DateTime.now() 25 | ## Because the current DateTime messes up the user's local timezone 26 | ## if they are in a negative offset, which totally screws up the reported 27 | ## time, so we are forcing UTC instead. 28 | method message($message --> Nil) { 29 | note "[{DateTime.now}] $message" unless $.silent; 30 | } 31 | 32 | method connect(:port($localport) = $.port, :host($localhost) = $.host) { 33 | $!listener := IO::Socket::INET.new: :$localhost, :$localport, :listen 34 | } 35 | 36 | method run() { 37 | $!debug = ?%*ENV; 38 | 39 | self.connect unless $!listener; 40 | self.message('Started HTTP server.'); 41 | self.pre-connection; 42 | while $!connection = $!listener.accept { 43 | self.message("Client connection received.") 44 | if $!debug; 45 | 46 | self.on-connection; 47 | 48 | my $first-chunk; 49 | my $msg-body-pos; 50 | 51 | while $!connection.recv(:bin) -> $t { 52 | self.message("Received a chunk of $t.elems() bytes length") 53 | if $!debug; 54 | 55 | if $first-chunk.defined { 56 | $first-chunk = $first-chunk ~ $t; 57 | } 58 | else { 59 | # overwhelmingly often (for simple GET requests, for example) we'll 60 | # get all data in one run through this loop. 61 | $first-chunk = $t; 62 | } 63 | 64 | # Find the header/body separator in the chunk, which means we can parse 65 | # the header seperately and are able to figure out the 66 | # correct encoding of the body. 67 | 68 | my int $look_position = 0; 69 | my int $end_of_buffer = $first-chunk.elems; 70 | 71 | while $look_position < $end_of_buffer - 3 { 72 | if $first-chunk.AT-POS($look_position) == 13 73 | && $first-chunk.AT-POS($look_position + 1) == 10 74 | && $first-chunk.AT-POS($look_position + 2) == 13 75 | && $first-chunk.AT-POS($look_position + 3) == 10 { 76 | $msg-body-pos = $look_position + 2; 77 | last; 78 | } 79 | else { 80 | $look_position = $look_position + 1; 81 | } 82 | } 83 | 84 | last if $msg-body-pos; 85 | } 86 | 87 | without $first-chunk { 88 | # if we're here, that means our recv timed out. 89 | # browsers will sometimes open a connection even though there is no 90 | # request to send yet, to make the next request faster. 91 | # since we have to be parrot-compatible, we can't use async 92 | # features, so we'll have to do it the nasty, time-out way. 93 | self.message("thrown out a connection that sent no data.") 94 | if $!debug; 95 | $!connection.close; 96 | next; 97 | } 98 | 99 | $!body = $first-chunk.subbuf($msg-body-pos + 2); 100 | 101 | my $preamble = $first-chunk.subbuf(0, $msg-body-pos).decode('ascii'); 102 | 103 | self.message("Read preamble:\n$preamble\n--- End of preamble.") 104 | if $!debug; 105 | 106 | ## End of work around. 107 | my @headers = $preamble.split("\r\n"); 108 | my $request = @headers.shift; 109 | without $request { 110 | self.message("Client connection lost.") 111 | if $!debug; 112 | $!connection.close; 113 | next; 114 | } 115 | self.message($request); 116 | 117 | self.message("Finished parsing headers: @headers.raku()") 118 | if $!debug; 119 | 120 | my ($method, $uri, $protocol) = $request.words; 121 | $protocol = DEFAULT_PROTOCOL without $protocol; 122 | 123 | unless $method eq 'GET' | 'POST' | 'HEAD' | 'PUT' | 'DELETE' | 'PATCH' { 124 | $!connection.print(self.unhandled-method); 125 | $!connection.close; 126 | next; 127 | } 128 | $!http-protocol = $protocol; 129 | 130 | %!env = (); ## Delete the previous hash. 131 | my ($path, $query) = $uri.split('?', 2); 132 | $query //= ''; 133 | 134 | ## First, let's add our "known" headers. 135 | %.env = $protocol; 136 | %.env = $method; 137 | %.env = $query; 138 | %.env = $path; 139 | %.env = $uri; 140 | %.env = $.host; 141 | %.env = $.port; 142 | ## Next, let's add HTTP request headers. 143 | for @headers -> $header { 144 | my ($key, $value) = $header.split(': '); 145 | if $key.defined and $value.defined { 146 | $key ~~ s:g/\-/_/; 147 | $key .= uc; 148 | $key = 'HTTP_' ~ $key unless $key eq 'CONTENT_LENGTH' | 'CONTENT_TYPE'; 149 | if %!env{$key} :exists { 150 | %!env{$key} ~= ", $value"; 151 | } 152 | else { 153 | %!env{$key} = $value; 154 | } 155 | } 156 | } 157 | 158 | # Use CONTENT_LENGTH to determine the length of data to read. 159 | if %.env:exists { 160 | if %.env { 161 | while %.env > $!body.bytes { 162 | $!body ~= $!connection.recv(%.env - $!body.bytes, :bin); 163 | } 164 | # self.message("Got body: "~$!body.decode) if $!debug; 165 | } 166 | } 167 | elsif $.always-get-body { 168 | ## No content length. Keep reading until no data is sent. 169 | while my $read = $!connection.recv(:bin) { 170 | $!body ~= $read; 171 | } 172 | } 173 | 174 | ## Call the handler. 175 | ## 176 | ## If it returns a defined value, it is assumed to be a valid HTTP 177 | ## response, in the form of a Str(ing), a Buf, or an object that 178 | ## can be stringified. 179 | ## 180 | ## If it returns an undefined value, we assume the handler 181 | ## sent the response to the client directly, and end the session. 182 | with self.handler -> $res { 183 | $res ~~ Buf 184 | ?? $!connection.write($res) 185 | !! $!connection.print($res.Str); 186 | } 187 | $!connection.close; 188 | self.closed-connection; 189 | self.message("Client connection closed.") 190 | if $!debug; 191 | } 192 | self.finish-connection; 193 | self.message("Connection finished. Server closed.") 194 | if $!debug; 195 | } 196 | 197 | ## Stub methods. Replace with your own. 198 | method pre-connection {}; ## Runs prior to waiting for connection. 199 | method on-connection {}; ## Runs at the beginning of each connection. 200 | method closed-connection {}; ## Runs after closing each connection. 201 | method finished-connection {}; ## Runs when the wait loop is ended. 202 | 203 | ## The handler method, this MUST be defined in your class. 204 | method handler {...}; 205 | 206 | ## Feel free to override this in your class. 207 | method unhandled-method() { 208 | my $status := 501; 209 | my $message := get_http_status_msg($status); 210 | "$.http-protocol $status $message"; 211 | } 212 | 213 | =begin pod 214 | 215 | =head1 NAME 216 | 217 | HTTP::Easy - HTTP servers made easy, including PSGI 218 | 219 | =head1 SYNOPSIS 220 | 221 | =begin code :lang 222 | 223 | use HTTP::Easy; 224 | 225 | =end code 226 | 227 | =head1 DESCRIPTION 228 | 229 | Raku libraries to make HTTP servers easily. 230 | 231 | This was inspired by C, but has a very different 232 | internal API, and extended functionality. It's been designed to work well 233 | with the C and C libraries. 234 | 235 | =head2 HTTP::Easy 236 | 237 | A role to build HTTP daemon classes with. This provides the framework 238 | for parsing HTTP connections. 239 | 240 | =head2 HTTP::Easy::PSGI 241 | 242 | A class consuming the C role. This builds a PSGI environment, 243 | and passes it onto a handler. The handler must return a C response: 244 | 245 | =begin code :lang 246 | 247 | [ $status, @headers, @body ] 248 | 249 | =end code 250 | 251 | This can be used as an engine in the C library. 252 | 253 | =head2 Example 254 | 255 | =begin code :lang 256 | 257 | use HTTP::Easy::PSGI; 258 | my $http = HTTP::Easy::PSGI.new(:port(8080)); 259 | 260 | my $app = sub (%env) { 261 | my $name = %env || "World"; 262 | [ 200, [ 'Content-Type' => 'text/plain' ], [ "Hello $name" ] ] 263 | } 264 | 265 | $http.handle($app); 266 | 267 | =end code 268 | 269 | =head1 TODO 270 | 271 | Implement HTTP/1.1 features such as Transfer-Encoding, etc. 272 | 273 | =head1 AUTHOR 274 | 275 | Timothy Totten 276 | 277 | =head1 COPYRIGHT AND LICENSE 278 | 279 | Copyright 2011 - 2017 Timothy Totten 280 | 281 | Copyright 2018 - 2022 Raku Community 282 | 283 | This library is free software; you can redistribute it and/or modify 284 | it under the Artistic License 2.0. 285 | 286 | =end pod 287 | -------------------------------------------------------------------------------- /lib/HTTP/Easy/PSGI.rakumod: -------------------------------------------------------------------------------- 1 | # HTTP::Easy::PSGI 2 | # A PSGI application HTTP Server 3 | 4 | use HTTP::Easy; 5 | use HTTP::Status; 6 | use PSGI; 7 | 8 | unit class HTTP::Easy::PSGI does HTTP::Easy; 9 | 10 | has $.p6sgi = True; 11 | has $.psgi-classic = False; 12 | has $.errors = $*ERR; 13 | has $!app; 14 | 15 | method app($app) { 16 | $!app = $app; 17 | } 18 | 19 | method handler() { 20 | ## First, let's add any necessary PSGI variables. 21 | populate-psgi-env( 22 | %.env, 23 | :input($.body), 24 | :errors($.errors), 25 | :errors-buffered, 26 | :p6sgi($.p6sgi), 27 | :psgi-classic($.psgi-classic), 28 | ); 29 | 30 | my $result; 31 | if $!app ~~ Callable { 32 | $result = $!app(%.env); 33 | } 34 | elsif $!app.can('handle') { 35 | $result = $!app.handle(%.env); 36 | } 37 | else { 38 | die "Invalid {self.WHAT} application."; 39 | } 40 | my $protocol := $.http-protocol; 41 | encode-psgi-response($result, :$protocol, :nph); 42 | } 43 | 44 | method handle($app) { 45 | self.app($app); 46 | self.run 47 | } 48 | 49 | # vim: expandtab shiftwidth=4 50 | -------------------------------------------------------------------------------- /t/01-load.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | 3 | plan 2; 4 | 5 | use-ok 'HTTP::Easy'; 6 | use-ok 'HTTP::Easy::PSGI'; 7 | 8 | # vim: expandtab shiftwidth=4 9 | --------------------------------------------------------------------------------