├── .github └── workflows │ └── linux.yml ├── .gitignore ├── .mailmap ├── .travis.yml ├── Build.PL ├── Changes ├── LICENSE ├── META.json ├── README.md ├── cpanfile ├── dist.ini ├── lib ├── HTTP │ └── Server │ │ └── PSGI │ │ └── Net │ │ └── Server │ │ └── PreFork.pm ├── Plack │ └── Handler │ │ └── Starman.pm ├── Starman.pm └── Starman │ └── Server.pm ├── script └── starman ├── t ├── 00_compile.t ├── chunked_req.t ├── chunked_termination.t ├── chunked_zero_length.t ├── early-hints.t ├── eintr.t ├── expect.t ├── findbin.psgi ├── harakiri.t ├── lf_only_request.t ├── no_chunked_head.t ├── rand.psgi ├── single_zero.t ├── ssl.t ├── ssl_ca.pem ├── ssl_key.pem ├── ssl_largebody.t └── suite.t └── xt └── release ├── findbin.t └── rand.t /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: linux 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | perl: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | perl-version: 12 | - '5.14' 13 | - '5.16' 14 | - '5.18' 15 | - '5.20' 16 | - '5.22' 17 | - '5.24' 18 | - '5.26' 19 | - '5.28' 20 | - '5.30' 21 | container: 22 | image: perl:${{ matrix.perl-version }} 23 | steps: 24 | - uses: actions/checkout@v1 25 | - name: Install Dependencies 26 | run: | 27 | curl -sL https://cpanmin.us/ | perl - -nq --with-develop --installdeps . 28 | - name: Run Tests 29 | run: prove -lr t 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | MYMETA.* 3 | !META.json 4 | /Starman-* 5 | /.build 6 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 3 | - '5.22' 4 | - '5.24' 5 | - '5.26' 6 | - '5.28' 7 | - '5.30' 8 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # This Build.PL for Starman was generated by Dist::Zilla::Plugin::ModuleBuildTiny 0.015. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.008001; 6 | use Module::Build::Tiny 0.034; 7 | Build_PL(); 8 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension Starman 2 | 3 | {{$NEXT}} 4 | 5 | 0.4017 2023-09-13 13:27:02 PDT 6 | - Handle EINTR when doing sysread calls (Rob Mueller) #148 7 | - Requires perl 5.14 8 | 9 | 0.4016 2022-09-13 10:11:34 PDT 10 | - Add psgix.informational callback #146 11 | 12 | 0.4015 2019-05-20 18:43:46 PDT 13 | - Fixed a bug incorrectly handling content body of '0' (olsonanl) #133 14 | 15 | 0.4014 2015-06-03 12:01:00 PDT 16 | - Treat ECONNRESET like EPIPE (i.e. ignore), not as a fatal error #114 (Tim Bunce) 17 | 18 | 0.4013 2015-05-14 15:01:20 PDT 19 | - Fixed some bad git merges. 20 | 21 | 0.4012 2015-05-14 14:59:48 PDT 22 | - Add --net_server-* options to pass directly to Net::Server backend (#109) 23 | - Updated documentation 24 | 25 | 0.4011 2014-11-11 08:07:43 PST 26 | - Move the app dispatch into a method #107 27 | 28 | 0.4010 2014-08-22 09:37:22 PDT 29 | - Support --read-timeout #103 (slobo) 30 | - Handle Expect header case insensitively #101 (oschwald) 31 | 32 | 0.4009 2014-04-03 14:39:27 PDT 33 | - Do not send chunked body for HEAD requests #87 (therigu) 34 | - Added --disable-proctitle option to disable the proctitle change #97 35 | 36 | 0.4008 2013-09-08 21:09:22 PDT 37 | - Make response write loop a zero-copy (ap) 38 | 39 | 0.4007 2013-09-02 17:11:38 PDT 40 | - Handle EPIPE and stops writing to the socket #84 (ap) 41 | 42 | 0.4006 2013-08-16 12:43:19 PDT 43 | - Same as 0.4005, non-devel 44 | 45 | 0.4005 2013-08-13 22:12:11 PDT 46 | - Fix SSL implementation bug where body longer than 16K doesn't get written correctly #78 (kazeburo, siracusa) 47 | 48 | 0.4004 2013-08-12 11:41:13 PDT 49 | - Note that SSL support is experimental 50 | 51 | 0.4003 2013-08-08 14:32:24 PDT 52 | - Fix ssl condition for banner 53 | 54 | 0.4002 2013-08-08 14:20:45 PDT 55 | - Fix banner message for host/port broken in 0.4 (siracusa) 56 | 57 | 0.4001 2013-07-29 23:14:10 PDT 58 | - Skip ssl tests if LWP doesn't support HTTPS 59 | 60 | 0.4000 2013-07-28 23:53:55 PDT 61 | - support SSL (aristotle) 62 | 63 | 0.3014 2013-06-16 01:07:52 PDT 64 | - bump Test::TCP dep, not really necessary but avoid 1.27 bug 65 | 66 | 0.3013 2013-06-12 22:52:54 PDT 67 | - Fix writer tests for Plack update 68 | 69 | 0.3012 2013-06-12 12:31:11 PDT 70 | - typo fixes 71 | - Fix dependencies for LWP::UserAgent 72 | 73 | 0.3011 2013-04-24 17:39:31 PDT 74 | - Disabled flock serialization when it's unnecessary. This will improve the performance 75 | when you have many Starman worker processes on Linux systems (kazeburo) #69 76 | 77 | 0.3010 2013-04-24 07:04:02 PDT 78 | - Switch back to Module::Build::Tiny with fixed #!perl shebang 79 | 80 | 0.3009 2013-04-23 17:34:57 PDT 81 | - Switch to MakeMaker because of shebang bug 82 | https://github.com/Leont/module-build-tiny/issues/3 83 | 84 | 0.3008 2013-04-06 22:04:53 PDT 85 | - switch to Module::Build::Tiny with Milla. Might not install bat correctly but we don't 86 | support Win32 anyway. 87 | 88 | 0.3007 2013-03-28 12:55:45 PDT 89 | - Accept HTTP requests with LF termination per HTTP spec recommendation (oschwald) #56 90 | - Documentation fix (oalders) 91 | 92 | 0.3006 Wed Dec 19 09:55:05 JST 2012 93 | - Clear out @ARGV, rather than restoring it, to avoid messing with Net::Server internals 94 | 95 | 0.3005 Wed Nov 14 19:46:31 PST 2012 96 | - Added a warning in runtime/documentation to NOT use -r/-R with Starman 97 | 98 | 0.3004 Thu Nov 8 19:40:45 PST 2012 99 | - Added --interval option to the sample start_server command 100 | - Makefile.PL fix 101 | 102 | 0.3003 Thu Sep 27 09:39:56 JST 2012 103 | - Fixed the test hang in some environments, introduced in 0.3002 [RT:79865] 104 | 105 | 0.3002 Tue Sep 25 15:26:43 JST 2012 106 | - Added a documentation for --signal-on-term for Server::Starter 0.12 (kazuho, ether) 107 | - Set REMOTE_PORT PSGI environment variable #50 (dex4er) 108 | - Fix a test failure with a directory containing whitespace (clkao) 109 | 110 | 0.3001 Mon Jun 25 10:57:20 PDT 2012 111 | - Fix SERVER_NAME and SERVER_PORT not exist on UNIX socket mode #24 112 | - Improved documentation 113 | - Ensure that chunk buffer contains terminating HTTP newline (Peter Makholm) 114 | 115 | 0.3000 Mon Feb 20 16:31:44 PST 2012 116 | - This be a 0.3 release 117 | 118 | 0.29_90 Thu Dec 1 19:40:52 PST 2011 119 | - Changed the way server handles HUP and QUIT signals 120 | HUP will just restart all the workers gracefully 121 | QUIT will gracefully shutdown workers and the master 122 | See `man 1 starman` and look for SIGNALS section. 123 | 124 | 0.2014 Sun Sep 18 12:43:06 PDT 2011 125 | - Fixed broken PSGI response headers after the output (cho45) 126 | 127 | 0.2013 Sat Jun 25 11:51:47 PDT 2011 128 | - Relaxed the harakiri tests (audreyt) 129 | 130 | 0.2012 Wed Jun 22 13:51:59 PDT 2011 131 | - Implemented psgix.harakiri mode (audreyt) 132 | - Added --error-log option (Paulo E. Castro) 133 | 134 | 0.2011 Tue May 24 09:41:52 PDT 2011 135 | - Fix chunked response with 0-length PSGI array elements (chmrr) 136 | 137 | 0.2010 Mon Mar 28 16:23:23 PDT 2011 138 | - Fixed packaging. No changes. 139 | 140 | 0.2009 Fri Mar 25 19:15:23 PDT 2011 141 | - Requires Plack 0.9971 to support localizing $0 to fix the FindBin issues #7, #15, #18, #19 142 | - Calls srand() automatically in the child init hook to avoid a fixed random seed #20 143 | - Implemented --keepalive-timeout which defaults to 1 (acme) 144 | 145 | 0.2008 Mon Feb 14 17:19:20 PST 2011 146 | - Documented that -E is automatically set to 'deployment' RT:61517 (timbunce) 147 | - Check the defined-ness of the input buffer to suppress warnings RT:60007 148 | 149 | 0.2007 Thu Sep 30 14:09:00 PDT 2010 150 | - Fixed a bug where Date header can be duplicate if the app generates one (spleenjack) 151 | 152 | 0.2006 Fri Jul 2 17:21:22 PDT 2010 153 | - Fixed a bug in chunked response when Content-Length is 0. #8 (chiba) 154 | - Documented --pid and --daemonize 155 | 156 | 0.2005 Fri Jul 2 17:02:16 PDT 2010 157 | - Don't use lib 'lib' 158 | - Documentation updates (miyagawa, grantm) 159 | 160 | 0.2004 Tue Apr 20 21:22:31 JST 2010 161 | - Delay set $0 in master so FindBin works. #7 162 | 163 | 0.2003 Mon Apr 19 15:19:06 JST 2010 164 | - Upped Plack dependency 165 | 166 | 0.2002 Sat Apr 17 18:44:24 PDT 2010 167 | - Switch kyoto.jpg to use baybridge.jpg for testing 168 | 169 | 0.2001 Tue Apr 13 21:45:15 PDT 2010 170 | - Fixed the way to set the default Delayed loader 171 | 172 | 0.2000 Tue Apr 13 20:22:24 PDT 2010 173 | - INCOMPATIBLE: starman executable by default loads the application with Delayed to be safer. 174 | Use --preload-app command line option to preload the application in the master process. 175 | See `starman --help` for details. 176 | 177 | 0.1007 Tue Apr 13 19:45:59 PDT 2010 178 | - Fixed a bug where Content-Length less response are sent in Keep-Alive without chunked, 179 | choking HTTP/1.0 clients (patspam) #6 180 | 181 | 0.1006 Tue Apr 13 00:01:23 CEST 2010 182 | - Fixed 100% CPU loop when an unexpected EOF happens (Graham Barr) 183 | 184 | 0.1005 Sun Mar 28 14:37:03 PDT 2010 185 | - Implemented starman -v 186 | 187 | 0.1004 Sat Mar 27 19:10:06 PDT 2010 188 | - Implemented --disable-keepalive for broken frontend proxy such as mod_proxy + mpm_prefork 189 | - Documented --backlog 190 | 191 | 0.1003 Sun Mar 21 21:08:39 PDT 2010 192 | - Fixed SERVER_PORT when used with Server::Starter (Reported by ronsavage) 193 | 194 | 0.1002 Wed Mar 10 12:10:46 JST 2010 195 | - Officially do not support Win32 196 | 197 | 0.1001 Sat Feb 27 05:03:18 PST 2010 198 | - Fix documentations 199 | - Set 'deployment' PLACK_ENV by default 200 | - Do not reopen stdio for possibly faster operations 201 | - require Net::Server 0.91 for new() (sekimura) 202 | 203 | 0.1000 Mon Feb 15 17:56:33 PST 2010 204 | - original version 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2010- by Tatsuhiko Miyagawa . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2010- by Tatsuhiko Miyagawa . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2010- by Tatsuhiko Miyagawa . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | 380 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "High-performance preforking PSGI/Plack web server", 3 | "author" : [ 4 | "Tatsuhiko Miyagawa " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Dist::Milla version v1.0.22, Dist::Zilla version 6.025, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "Starman", 16 | "no_index" : { 17 | "directory" : [ 18 | "eg", 19 | "examples", 20 | "inc", 21 | "share", 22 | "t", 23 | "xt" 24 | ] 25 | }, 26 | "prereqs" : { 27 | "configure" : { 28 | "requires" : { 29 | "Module::Build::Tiny" : "0.034" 30 | }, 31 | "suggests" : { 32 | "JSON::PP" : "2.27300" 33 | } 34 | }, 35 | "develop" : { 36 | "requires" : { 37 | "Dist::Milla" : "v1.0.22", 38 | "LWP::Protocol::https" : "0", 39 | "Test::Pod" : "1.41" 40 | } 41 | }, 42 | "runtime" : { 43 | "requires" : { 44 | "Data::Dump" : "0", 45 | "HTTP::Date" : "0", 46 | "HTTP::Parser::XS" : "0", 47 | "HTTP::Status" : "0", 48 | "Net::Server" : "2.007", 49 | "Plack" : "0.9971", 50 | "Test::TCP" : "2.00", 51 | "parent" : "0", 52 | "perl" : "5.008001" 53 | }, 54 | "suggests" : { 55 | "Net::Server::SS::PreFork" : "0", 56 | "Server::Starter" : "0" 57 | } 58 | }, 59 | "test" : { 60 | "requires" : { 61 | "LWP::UserAgent" : "0", 62 | "Test::More" : "0", 63 | "Test::Requires" : "0" 64 | } 65 | } 66 | }, 67 | "release_status" : "stable", 68 | "resources" : { 69 | "bugtracker" : { 70 | "web" : "https://github.com/miyagawa/Starman/issues" 71 | }, 72 | "homepage" : "https://github.com/miyagawa/Starman", 73 | "repository" : { 74 | "type" : "git", 75 | "url" : "https://github.com/miyagawa/Starman.git", 76 | "web" : "https://github.com/miyagawa/Starman" 77 | } 78 | }, 79 | "version" : "0.4017", 80 | "x_contributors" : [ 81 | "Adam Guthrie ", 82 | "Alex Vandiver ", 83 | "Andrew Nelson ", 84 | "Aristotle Pagaltzis ", 85 | "Audrey Tang ", 86 | "Chia-liang Kao ", 87 | "cho45 ", 88 | "Damyan Ivanov ", 89 | "David Steinbrunner ", 90 | "Graham Barr ", 91 | "Grant McLean ", 92 | "Gregory Oschwald ", 93 | "Jeremy Krieg ", 94 | "John Siracusa ", 95 | "Leon Brocard ", 96 | "Masahiro Nagano ", 97 | "Olaf Alders ", 98 | "Paulo E. Castro ", 99 | "Perlover ", 100 | "Peter Makholm ", 101 | "Piotr Roszatycki ", 102 | "podoleanuciprian <52755740+podoleanuciprian@users.noreply.github.com>", 103 | "Robert Olson ", 104 | "Robert Rothenberg ", 105 | "Robert Sedlacek ", 106 | "Rob Mueller ", 107 | "Slobodan Mi\u0161kovi\u0107 ", 108 | "spleenjack ", 109 | "Tatsuhiko Miyagawa ", 110 | "Tim Bunce " 111 | ], 112 | "x_generated_by_perl" : "v5.34.1", 113 | "x_serialization_backend" : "Cpanel::JSON::XS version 4.27", 114 | "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", 115 | "x_static_install" : 1 116 | } 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | Starman - High-performance preforking PSGI/Plack web server 4 | 5 | # SYNOPSIS 6 | 7 | # Run app.psgi with the default settings 8 | > starman 9 | 10 | # run with Server::Starter 11 | > start_server --port 127.0.0.1:80 -- starman --workers 32 myapp.psgi 12 | 13 | # UNIX domain sockets 14 | > starman --listen /tmp/starman.sock 15 | 16 | Read more options and configurations by running \`perldoc starman\` (lower-case s). 17 | 18 | # DESCRIPTION 19 | 20 | Starman is a PSGI perl web server that has unique features such as: 21 | 22 | - High Performance 23 | 24 | Uses the fast XS/C HTTP header parser 25 | 26 | - Preforking 27 | 28 | Spawns workers preforked like most high performance UNIX servers 29 | do. Starman also reaps dead children and automatically restarts the 30 | worker pool. 31 | 32 | - Signals 33 | 34 | Supports `HUP` for graceful worker restarts, and `TTIN`/`TTOU` to 35 | dynamically increase or decrease the number of worker processes, as 36 | well as `QUIT` to gracefully shutdown the worker processes. 37 | 38 | - Superdaemon aware 39 | 40 | Supports [Server::Starter](https://metacpan.org/pod/Server%3A%3AStarter) for hot deploy and graceful restarts. 41 | 42 | - Multiple interfaces and UNIX Domain Socket support 43 | 44 | Able to listen on multiple interfaces including UNIX sockets. 45 | 46 | - Small memory footprint 47 | 48 | Preloading the applications with `--preload-app` command line option 49 | enables copy-on-write friendly memory management. Also, the minimum 50 | memory usage Starman requires for the master process is 7MB and 51 | children (workers) is less than 3.0MB. 52 | 53 | - PSGI compatible 54 | 55 | Can run any PSGI applications and frameworks 56 | 57 | - HTTP/1.1 support 58 | 59 | Supports chunked requests and responses, keep-alive and pipeline requests. 60 | 61 | - UNIX only 62 | 63 | This server does not support Win32. 64 | 65 | # PERFORMANCE 66 | 67 | Here's a simple benchmark using `Hello.psgi`. 68 | 69 | -- server: Starman (workers=10) 70 | Requests per second: 6849.16 [#/sec] (mean) 71 | -- server: Twiggy 72 | Requests per second: 3911.78 [#/sec] (mean) 73 | -- server: AnyEvent::HTTPD 74 | Requests per second: 2738.49 [#/sec] (mean) 75 | -- server: HTTP::Server::PSGI 76 | Requests per second: 2218.16 [#/sec] (mean) 77 | -- server: HTTP::Server::PSGI (workers=10) 78 | Requests per second: 2792.99 [#/sec] (mean) 79 | -- server: HTTP::Server::Simple 80 | Requests per second: 1435.50 [#/sec] (mean) 81 | -- server: Corona 82 | Requests per second: 2332.00 [#/sec] (mean) 83 | -- server: POE 84 | Requests per second: 503.59 [#/sec] (mean) 85 | 86 | This benchmark was processed with `ab -c 10 -t 1 -k` on MacBook Pro 87 | 13" late 2009 model on Mac OS X 10.6.2 with perl 5.10.0. YMMV. 88 | 89 | # NOTES 90 | 91 | Because Starman runs as a preforking model, it is not recommended to 92 | serve the requests directly from the internet, especially when slow 93 | requesting clients are taken into consideration. It is suggested to 94 | put Starman workers behind the frontend servers such as nginx, and use 95 | HTTP proxy with TCP or UNIX sockets. 96 | 97 | # PSGI EXTENSIONS 98 | 99 | ## psgix.informational 100 | 101 | Starman exposes a callback named `psgix.informational` that can be 102 | used for sending an informational response. The callback accepts two 103 | arguments, the first argument being the status code and the second 104 | being an arrayref of the headers to be sent. Example below sends an 105 | 103 Early Hints response before processing the request to build a 106 | final response. 107 | 108 | sub { 109 | my $env = shift; 110 | 111 | $env->{'psgix.informational'}->( 103, [ 112 | "Link" => "; rel=preload" 113 | ] ); 114 | 115 | my $rest = ... 116 | $resp; 117 | } 118 | 119 | # AUTHOR 120 | 121 | Tatsuhiko Miyagawa 122 | 123 | Andy Grundman wrote [Catalyst::Engine::HTTP::Prefork](https://metacpan.org/pod/Catalyst%3A%3AEngine%3A%3AHTTP%3A%3APrefork), which this module 124 | is heavily based on. 125 | 126 | Kazuho Oku wrote [Net::Server::SS::PreFork](https://metacpan.org/pod/Net%3A%3AServer%3A%3ASS%3A%3APreFork) that makes it easy to add 127 | [Server::Starter](https://metacpan.org/pod/Server%3A%3AStarter) support to this software. 128 | 129 | The `psgix.informational` callback comes from [Starlet](https://metacpan.org/pod/Starlet) by Kazuho Oku. 130 | 131 | # COPYRIGHT 132 | 133 | Tatsuhiko Miyagawa, 2010- 134 | 135 | # LICENSE 136 | 137 | This library is free software; you can redistribute it and/or modify 138 | it under the same terms as Perl itself. 139 | 140 | # SEE ALSO 141 | 142 | [Plack](https://metacpan.org/pod/Plack) [Catalyst::Engine::HTTP::Prefork](https://metacpan.org/pod/Catalyst%3A%3AEngine%3A%3AHTTP%3A%3APrefork) [Net::Server::PreFork](https://metacpan.org/pod/Net%3A%3AServer%3A%3APreFork) 143 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Data::Dump'; 2 | requires 'HTTP::Date'; 3 | requires 'HTTP::Parser::XS'; 4 | requires 'HTTP::Status'; 5 | requires 'Net::Server', '2.007'; 6 | requires 'Plack', '0.9971'; 7 | requires 'Test::TCP', '2.00'; 8 | requires 'parent'; 9 | requires 'perl', '5.008001'; 10 | 11 | suggests 'Server::Starter'; 12 | suggests 'Net::Server::SS::PreFork'; 13 | 14 | on test => sub { 15 | requires 'Test::More'; 16 | requires 'Test::Requires'; 17 | requires 'LWP::UserAgent'; 18 | }; 19 | 20 | on develop => sub { 21 | requires 'LWP::Protocol::https'; 22 | }; 23 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | [@Milla] 2 | 3 | -------------------------------------------------------------------------------- /lib/HTTP/Server/PSGI/Net/Server/PreFork.pm: -------------------------------------------------------------------------------- 1 | package HTTP::Server::PSGI::Net::Server::PreFork; 2 | use parent qw(Starman::Server); 3 | 4 | 1; 5 | -------------------------------------------------------------------------------- /lib/Plack/Handler/Starman.pm: -------------------------------------------------------------------------------- 1 | package Plack::Handler::Starman; 2 | use strict; 3 | use Starman::Server; 4 | 5 | sub new { 6 | my $class = shift; 7 | bless { @_ }, $class; 8 | } 9 | 10 | sub run { 11 | my($self, $app) = @_; 12 | 13 | if ($ENV{SERVER_STARTER_PORT}) { 14 | require Net::Server::SS::PreFork; 15 | @Starman::Server::ISA = qw(Net::Server::SS::PreFork); # Yikes. 16 | } 17 | 18 | my %nsa; 19 | while (my($key, $value) = each %$self) { 20 | $key =~ s/^net_server_// or next; 21 | $nsa{$key} = $value; 22 | } 23 | $self->{net_server_args} = \%nsa if %nsa; 24 | 25 | Starman::Server->new->run($app, {%$self}); 26 | } 27 | 28 | 1; 29 | 30 | __END__ 31 | 32 | =head1 NAME 33 | 34 | Plack::Handler::Starman - Plack adapter for Starman 35 | 36 | =head1 SYNOPSIS 37 | 38 | plackup -s Starman 39 | 40 | =head1 DESCRIPTION 41 | 42 | This handler exists for the C compatibility. Essentially, 43 | C is equivalent to C, 44 | because the C executable delay loads the application by 45 | default. See L for more details. 46 | 47 | =head1 AUTHOR 48 | 49 | Tatsuhiko Miyagawa 50 | 51 | =head1 SEE ALSO 52 | 53 | L 54 | 55 | =cut 56 | 57 | 58 | -------------------------------------------------------------------------------- /lib/Starman.pm: -------------------------------------------------------------------------------- 1 | package Starman; 2 | 3 | use strict; 4 | use 5.008_001; 5 | our $VERSION = '0.4017'; 6 | 7 | 1; 8 | __END__ 9 | 10 | =encoding utf-8 11 | 12 | =for stopwords 13 | 14 | =head1 NAME 15 | 16 | Starman - High-performance preforking PSGI/Plack web server 17 | 18 | =head1 SYNOPSIS 19 | 20 | # Run app.psgi with the default settings 21 | > starman 22 | 23 | # run with Server::Starter 24 | > start_server --port 127.0.0.1:80 -- starman --workers 32 myapp.psgi 25 | 26 | # UNIX domain sockets 27 | > starman --listen /tmp/starman.sock 28 | 29 | Read more options and configurations by running `perldoc starman` (lower-case s). 30 | 31 | =head1 DESCRIPTION 32 | 33 | Starman is a PSGI perl web server that has unique features such as: 34 | 35 | =over 4 36 | 37 | =item High Performance 38 | 39 | Uses the fast XS/C HTTP header parser 40 | 41 | =item Preforking 42 | 43 | Spawns workers preforked like most high performance UNIX servers 44 | do. Starman also reaps dead children and automatically restarts the 45 | worker pool. 46 | 47 | =item Signals 48 | 49 | Supports C for graceful worker restarts, and C/C to 50 | dynamically increase or decrease the number of worker processes, as 51 | well as C to gracefully shutdown the worker processes. 52 | 53 | =item Superdaemon aware 54 | 55 | Supports L for hot deploy and graceful restarts. 56 | 57 | =item Multiple interfaces and UNIX Domain Socket support 58 | 59 | Able to listen on multiple interfaces including UNIX sockets. 60 | 61 | =item Small memory footprint 62 | 63 | Preloading the applications with C<--preload-app> command line option 64 | enables copy-on-write friendly memory management. Also, the minimum 65 | memory usage Starman requires for the master process is 7MB and 66 | children (workers) is less than 3.0MB. 67 | 68 | =item PSGI compatible 69 | 70 | Can run any PSGI applications and frameworks 71 | 72 | =item HTTP/1.1 support 73 | 74 | Supports chunked requests and responses, keep-alive and pipeline requests. 75 | 76 | =item UNIX only 77 | 78 | This server does not support Win32. 79 | 80 | =back 81 | 82 | =head1 PERFORMANCE 83 | 84 | Here's a simple benchmark using C. 85 | 86 | -- server: Starman (workers=10) 87 | Requests per second: 6849.16 [#/sec] (mean) 88 | -- server: Twiggy 89 | Requests per second: 3911.78 [#/sec] (mean) 90 | -- server: AnyEvent::HTTPD 91 | Requests per second: 2738.49 [#/sec] (mean) 92 | -- server: HTTP::Server::PSGI 93 | Requests per second: 2218.16 [#/sec] (mean) 94 | -- server: HTTP::Server::PSGI (workers=10) 95 | Requests per second: 2792.99 [#/sec] (mean) 96 | -- server: HTTP::Server::Simple 97 | Requests per second: 1435.50 [#/sec] (mean) 98 | -- server: Corona 99 | Requests per second: 2332.00 [#/sec] (mean) 100 | -- server: POE 101 | Requests per second: 503.59 [#/sec] (mean) 102 | 103 | This benchmark was processed with C on MacBook Pro 104 | 13" late 2009 model on Mac OS X 10.6.2 with perl 5.10.0. YMMV. 105 | 106 | =head1 NOTES 107 | 108 | Because Starman runs as a preforking model, it is not recommended to 109 | serve the requests directly from the internet, especially when slow 110 | requesting clients are taken into consideration. It is suggested to 111 | put Starman workers behind the frontend servers such as nginx, and use 112 | HTTP proxy with TCP or UNIX sockets. 113 | 114 | =head1 PSGI EXTENSIONS 115 | 116 | =head2 psgix.informational 117 | 118 | Starman exposes a callback named C that can be 119 | used for sending an informational response. The callback accepts two 120 | arguments, the first argument being the status code and the second 121 | being an arrayref of the headers to be sent. Example below sends an 122 | 103 Early Hints response before processing the request to build a 123 | final response. 124 | 125 | sub { 126 | my $env = shift; 127 | 128 | $env->{'psgix.informational'}->( 103, [ 129 | "Link" => "; rel=preload" 130 | ] ); 131 | 132 | my $rest = ... 133 | $resp; 134 | } 135 | 136 | =head1 AUTHOR 137 | 138 | Tatsuhiko Miyagawa Emiyagawa@bulknews.netE 139 | 140 | Andy Grundman wrote L, which this module 141 | is heavily based on. 142 | 143 | Kazuho Oku wrote L that makes it easy to add 144 | L support to this software. 145 | 146 | The C callback comes from L by Kazuho Oku. 147 | 148 | =head1 COPYRIGHT 149 | 150 | Tatsuhiko Miyagawa, 2010- 151 | 152 | =head1 LICENSE 153 | 154 | This library is free software; you can redistribute it and/or modify 155 | it under the same terms as Perl itself. 156 | 157 | =head1 SEE ALSO 158 | 159 | L L L 160 | 161 | =cut 162 | -------------------------------------------------------------------------------- /lib/Starman/Server.pm: -------------------------------------------------------------------------------- 1 | package Starman::Server; 2 | use strict; 3 | use base 'Net::Server::PreFork'; 4 | 5 | use Data::Dump qw(dump); 6 | use Socket qw(IPPROTO_TCP TCP_NODELAY); 7 | use IO::Socket qw(:crlf); 8 | use HTTP::Parser::XS qw(parse_http_request); 9 | use HTTP::Status qw(status_message); 10 | use HTTP::Date qw(time2str); 11 | use POSIX qw(EINTR EPIPE ECONNRESET); 12 | use Symbol; 13 | 14 | use Plack::Util; 15 | use Plack::TempBuffer; 16 | 17 | use constant DEBUG => $ENV{STARMAN_DEBUG} || 0; 18 | use constant CHUNKSIZE => 64 * 1024; 19 | 20 | my $null_io = do { open my $io, "<", \""; $io }; 21 | 22 | use Net::Server::SIG qw(register_sig); 23 | 24 | # Override Net::Server's HUP handling - just restart all the workers and that's about it 25 | sub sig_hup { 26 | my $self = shift; 27 | $self->hup_children; 28 | } 29 | 30 | sub run { 31 | my($self, $app, $options) = @_; 32 | 33 | $self->{app} = $app; 34 | $self->{options} = $options; 35 | 36 | my %extra = (); 37 | 38 | if ($options->{net_server_args}) { 39 | %extra = %{ $options->{net_server_args} }; 40 | } 41 | 42 | if ( $options->{pid} ) { 43 | $extra{pid_file} = $options->{pid}; 44 | } 45 | if ( $options->{daemonize} ) { 46 | $extra{setsid} = $extra{background} = 1; 47 | } 48 | if ( $options->{error_log} ) { 49 | $extra{log_file} = $options->{error_log}; 50 | } 51 | if ( DEBUG ) { 52 | $extra{log_level} = 4; 53 | } 54 | if ( $options->{ssl_cert} ) { 55 | $extra{SSL_cert_file} = $options->{ssl_cert}; 56 | } 57 | if ( $options->{ssl_key} ) { 58 | $extra{SSL_key_file} = $options->{ssl_key}; 59 | } 60 | if (! exists $options->{keepalive}) { 61 | $options->{keepalive} = 1; 62 | } 63 | if (! exists $options->{keepalive_timeout}) { 64 | $options->{keepalive_timeout} = 1; 65 | } 66 | if (! exists $options->{read_timeout}) { 67 | $options->{read_timeout} = 5; 68 | } 69 | if (! exists $options->{proctitle}) { 70 | $options->{proctitle} = 1; 71 | } 72 | 73 | my @port; 74 | for my $listen (@{$options->{listen} || [ "$options->{host}:$options->{port}" ]}) { 75 | my %listen; 76 | if ($listen =~ /:/) { 77 | my($h, $p, $opt) = split /:/, $listen, 3; 78 | $listen{host} = $h if $h; 79 | $listen{port} = $p; 80 | $listen{proto} = 'ssl' if defined $opt && lc $opt eq 'ssl'; 81 | } else { 82 | %listen = ( 83 | host => 'localhost', 84 | port => $listen, 85 | proto => 'unix', 86 | ); 87 | } 88 | push @port, \%listen; 89 | } 90 | 91 | my $workers = $options->{workers} || 5; 92 | local @ARGV = (); 93 | 94 | $self->SUPER::run( 95 | port => \@port, 96 | host => '*', # default host 97 | proto => $options->{ssl} ? 'ssl' : 'tcp', # default proto 98 | serialize => ( $^O =~ m!(linux|darwin|bsd|cygwin)$! ) ? 'none' : 'flock', 99 | min_servers => $options->{min_servers} || $workers, 100 | min_spare_servers => $options->{min_spare_servers} || $workers - 1, 101 | max_spare_servers => $options->{max_spare_servers} || $workers - 1, 102 | max_servers => $options->{max_servers} || $workers, 103 | max_requests => $options->{max_requests} || 1000, 104 | user => $options->{user} || $>, 105 | group => $options->{group} || $), 106 | listen => $options->{backlog} || 1024, 107 | check_for_waiting => 1, 108 | no_client_stdout => 1, 109 | %extra 110 | ); 111 | } 112 | 113 | sub pre_loop_hook { 114 | my $self = shift; 115 | 116 | my $port = $self->{server}->{port}->[0]; 117 | my $proto = $port->{proto} eq 'ssl' ? 'https' : 118 | $port->{proto} eq 'unix' ? 'unix' : 119 | 'http'; 120 | 121 | $self->{options}{server_ready}->({ 122 | host => $port->{host}, 123 | port => $port->{port}, 124 | proto => $proto, 125 | server_software => 'Starman', 126 | }) if $self->{options}{server_ready}; 127 | 128 | register_sig( 129 | TTIN => sub { $self->{server}->{$_}++ for qw( min_servers max_servers ) }, 130 | TTOU => sub { $self->{server}->{$_}-- for qw( min_servers max_servers ) }, 131 | QUIT => sub { $self->server_close(1) }, 132 | ); 133 | } 134 | 135 | sub server_close { 136 | my($self, $quit) = @_; 137 | 138 | if ($quit) { 139 | $self->log(2, $self->log_time . " Received QUIT. Running a graceful shutdown\n"); 140 | $self->{server}->{$_} = 0 for qw( min_servers max_servers ); 141 | $self->hup_children; 142 | while (1) { 143 | Net::Server::SIG::check_sigs(); 144 | $self->coordinate_children; 145 | last if !keys %{$self->{server}{children}}; 146 | sleep 1; 147 | } 148 | $self->log(2, $self->log_time . " Worker processes cleaned up\n"); 149 | } 150 | 151 | $self->SUPER::server_close(); 152 | } 153 | 154 | sub run_parent { 155 | my $self = shift; 156 | $0 = "starman master " . join(" ", @{$self->{options}{argv} || []}) 157 | if $self->{options}{proctitle}; 158 | no warnings 'redefine'; 159 | local *Net::Server::PreFork::register_sig = sub { 160 | my %args = @_; 161 | delete $args{QUIT}; 162 | Net::Server::SIG::register_sig(%args); 163 | }; 164 | $self->SUPER::run_parent(@_); 165 | } 166 | 167 | # The below methods run in the child process 168 | 169 | sub child_init_hook { 170 | my $self = shift; 171 | srand(); 172 | if ($self->{options}->{psgi_app_builder}) { 173 | DEBUG && warn "[$$] Initializing the PSGI app\n"; 174 | $self->{app} = $self->{options}->{psgi_app_builder}->(); 175 | } 176 | $0 = "starman worker " . join(" ", @{$self->{options}{argv} || []}) 177 | if $self->{options}{proctitle}; 178 | 179 | } 180 | 181 | sub post_accept_hook { 182 | my $self = shift; 183 | 184 | $self->{client} = { 185 | headerbuf => '', 186 | inputbuf => '', 187 | keepalive => 1, 188 | }; 189 | } 190 | 191 | sub dispatch_request { 192 | my ($self, $env) = @_; 193 | 194 | # Run PSGI apps 195 | my $res = Plack::Util::run_app($self->{app}, $env); 196 | 197 | if (ref $res eq 'CODE') { 198 | $res->(sub { $self->_finalize_response($env, $_[0]) }); 199 | } else { 200 | $self->_finalize_response($env, $res); 201 | } 202 | } 203 | 204 | sub process_request { 205 | my $self = shift; 206 | my $conn = $self->{server}->{client}; 207 | 208 | if ($conn->NS_proto eq 'TCP') { 209 | setsockopt($conn, IPPROTO_TCP, TCP_NODELAY, 1) 210 | or die $!; 211 | } 212 | 213 | while ( $self->{client}->{keepalive} ) { 214 | last if !$conn->connected; 215 | 216 | # Read until we see all headers 217 | last if !$self->_read_headers; 218 | 219 | my $env = { 220 | REMOTE_ADDR => $self->{server}->{peeraddr}, 221 | REMOTE_HOST => $self->{server}->{peerhost} || $self->{server}->{peeraddr}, 222 | REMOTE_PORT => $self->{server}->{peerport} || 0, 223 | SERVER_NAME => $self->{server}->{sockaddr} || 0, # XXX: needs to be resolved? 224 | SERVER_PORT => $self->{server}->{sockport} || 0, 225 | SCRIPT_NAME => '', 226 | 'psgi.version' => [ 1, 1 ], 227 | 'psgi.errors' => *STDERR, 228 | 'psgi.url_scheme' => ($conn->NS_proto eq 'SSL' ? 'https' : 'http'), 229 | 'psgi.nonblocking' => Plack::Util::FALSE, 230 | 'psgi.streaming' => Plack::Util::TRUE, 231 | 'psgi.run_once' => Plack::Util::FALSE, 232 | 'psgi.multithread' => Plack::Util::FALSE, 233 | 'psgi.multiprocess' => Plack::Util::TRUE, 234 | 'psgix.io' => $conn, 235 | 'psgix.input.buffered' => Plack::Util::TRUE, 236 | 'psgix.harakiri' => Plack::Util::TRUE, 237 | 'psgix.informational' => sub { _write_informational($conn, @_) }, 238 | }; 239 | 240 | # Parse headers 241 | my $reqlen = parse_http_request(delete $self->{client}->{headerbuf}, $env); 242 | if ( $reqlen == -1 ) { 243 | # Bad request 244 | DEBUG && warn "[$$] Bad request\n"; 245 | $self->_http_error(400, { SERVER_PROTOCOL => "HTTP/1.0" }); 246 | last; 247 | } 248 | 249 | # Initialize PSGI environment 250 | # Determine whether we will keep the connection open after the request 251 | my $connection = delete $env->{HTTP_CONNECTION}; 252 | my $proto = $env->{SERVER_PROTOCOL}; 253 | if ( $proto && $proto eq 'HTTP/1.0' ) { 254 | if ( $connection && $connection =~ /^keep-alive$/i ) { 255 | # Keep-alive only with explicit header in HTTP/1.0 256 | $self->{client}->{keepalive} = 1; 257 | } 258 | else { 259 | $self->{client}->{keepalive} = 0; 260 | } 261 | } 262 | elsif ( $proto && $proto eq 'HTTP/1.1' ) { 263 | if ( $connection && $connection =~ /^close$/i ) { 264 | $self->{client}->{keepalive} = 0; 265 | } 266 | else { 267 | # Keep-alive assumed in HTTP/1.1 268 | $self->{client}->{keepalive} = 1; 269 | } 270 | 271 | # Do we need to send 100 Continue? 272 | if ( $env->{HTTP_EXPECT} ) { 273 | if ( lc $env->{HTTP_EXPECT} eq '100-continue' ) { 274 | _syswrite($conn, \('HTTP/1.1 100 Continue' . $CRLF . $CRLF)); 275 | DEBUG && warn "[$$] Sent 100 Continue response\n"; 276 | } 277 | else { 278 | DEBUG && warn "[$$] Invalid Expect header, returning 417\n"; 279 | $self->_http_error( 417, $env ); 280 | last; 281 | } 282 | } 283 | 284 | unless ($env->{HTTP_HOST}) { 285 | # No host, bad request 286 | DEBUG && warn "[$$] Bad request, HTTP/1.1 without Host header\n"; 287 | $self->_http_error( 400, $env ); 288 | last; 289 | } 290 | } 291 | 292 | unless ($self->{options}->{keepalive}) { 293 | DEBUG && warn "[$$] keep-alive is disabled. Closing the connection after this request\n"; 294 | $self->{client}->{keepalive} = 0; 295 | } 296 | 297 | $self->_prepare_env($env); 298 | 299 | $self->dispatch_request($env); 300 | 301 | DEBUG && warn "[$$] Request done\n"; 302 | 303 | if ( $self->{client}->{keepalive} ) { 304 | # If we still have data in the input buffer it may be a pipelined request 305 | if ( $self->{client}->{inputbuf} ne '' ) { 306 | if ( $self->{client}->{inputbuf} =~ /^(?:GET|HEAD)/ ) { 307 | if ( DEBUG ) { 308 | warn "Pipelined GET/HEAD request in input buffer: " 309 | . dump( $self->{client}->{inputbuf} ) . "\n"; 310 | } 311 | 312 | # Continue processing the input buffer 313 | next; 314 | } 315 | else { 316 | # Input buffer just has junk, clear it 317 | if ( DEBUG ) { 318 | warn "Clearing junk from input buffer: " 319 | . dump( $self->{client}->{inputbuf} ) . "\n"; 320 | } 321 | 322 | $self->{client}->{inputbuf} = ''; 323 | } 324 | } 325 | 326 | DEBUG && warn "[$$] Waiting on previous connection for keep-alive request...\n"; 327 | 328 | my $sel = IO::Select->new($conn); 329 | last unless $sel->can_read($self->{options}->{keepalive_timeout}); 330 | } 331 | } 332 | 333 | DEBUG && warn "[$$] Closing connection\n"; 334 | } 335 | 336 | sub _read_headers { 337 | my $self = shift; 338 | 339 | eval { 340 | local $SIG{ALRM} = sub { die "Timed out\n"; }; 341 | 342 | alarm( $self->{options}->{read_timeout} ); 343 | 344 | while (1) { 345 | # Do we have a full header in the buffer? 346 | # This is before sysread so we don't read if we have a pipelined request 347 | # waiting in the buffer 348 | last if $self->{client}->{inputbuf} ne '' && $self->{client}->{inputbuf} =~ /$CR?$LF$CR?$LF/s; 349 | 350 | # If not, read some data 351 | my $read = _sysread($self->{server}->{client}, my $buf, CHUNKSIZE); 352 | 353 | if ( !defined $read || $read == 0 ) { 354 | die "Read error: $!\n"; 355 | } 356 | 357 | if ( DEBUG ) { 358 | warn "[$$] Read $read bytes: " . dump($buf) . "\n"; 359 | } 360 | 361 | $self->{client}->{inputbuf} .= $buf; 362 | } 363 | }; 364 | 365 | alarm(0); 366 | 367 | if ( $@ ) { 368 | if ( $@ =~ /Timed out/ ) { 369 | DEBUG && warn "[$$] Client connection timed out\n"; 370 | return; 371 | } 372 | 373 | if ( $@ =~ /Read error/ ) { 374 | DEBUG && warn "[$$] Read error: $!\n"; 375 | return; 376 | } 377 | } 378 | 379 | # Pull out the complete header into a new buffer 380 | $self->{client}->{headerbuf} = $self->{client}->{inputbuf}; 381 | 382 | # Save any left-over data, possibly body data or pipelined requests 383 | $self->{client}->{inputbuf} =~ s/.*?$CR?$LF$CR?$LF//s; 384 | 385 | return 1; 386 | } 387 | 388 | sub _http_error { 389 | my ( $self, $code, $env ) = @_; 390 | 391 | my $status = $code || 500; 392 | my $msg = status_message($status); 393 | 394 | my $res = [ 395 | $status, 396 | [ 'Content-Type' => 'text/plain', 'Content-Length' => length($msg) ], 397 | [ $msg ], 398 | ]; 399 | 400 | $self->{client}->{keepalive} = 0; 401 | $self->_finalize_response($env, $res); 402 | } 403 | 404 | sub _prepare_env { 405 | my($self, $env) = @_; 406 | 407 | my $get_chunk = sub { 408 | if ($self->{client}->{inputbuf} ne '') { 409 | my $chunk = delete $self->{client}->{inputbuf}; 410 | return ($chunk, length $chunk); 411 | } 412 | my $read = _sysread($self->{server}->{client}, my($chunk), CHUNKSIZE); 413 | return ($chunk, $read); 414 | }; 415 | 416 | my $chunked = do { no warnings; lc delete $env->{HTTP_TRANSFER_ENCODING} eq 'chunked' }; 417 | 418 | if (my $cl = $env->{CONTENT_LENGTH}) { 419 | my $buf = Plack::TempBuffer->new($cl); 420 | while ($cl > 0) { 421 | my($chunk, $read) = $get_chunk->(); 422 | 423 | if ( !defined $read || $read == 0 ) { 424 | die "Read error: $!\n"; 425 | } 426 | 427 | $cl -= $read; 428 | $buf->print($chunk); 429 | } 430 | $env->{'psgi.input'} = $buf->rewind; 431 | } elsif ($chunked) { 432 | my $buf = Plack::TempBuffer->new; 433 | my $chunk_buffer = ''; 434 | my $length; 435 | 436 | DECHUNK: 437 | while (1) { 438 | my($chunk, $read) = $get_chunk->(); 439 | $chunk_buffer .= $chunk; 440 | 441 | while ( $chunk_buffer =~ s/^(([0-9a-fA-F]+).*\015\012)// ) { 442 | my $trailer = $1; 443 | my $chunk_len = hex $2; 444 | 445 | if ($chunk_len == 0) { 446 | last DECHUNK; 447 | } elsif (length $chunk_buffer < $chunk_len + 2) { 448 | $chunk_buffer = $trailer . $chunk_buffer; 449 | last; 450 | } 451 | 452 | $buf->print(substr $chunk_buffer, 0, $chunk_len, ''); 453 | $chunk_buffer =~ s/^\015\012//; 454 | 455 | $length += $chunk_len; 456 | } 457 | 458 | last unless $read && $read > 0; 459 | } 460 | 461 | $env->{CONTENT_LENGTH} = $length; 462 | $env->{'psgi.input'} = $buf->rewind; 463 | } else { 464 | $env->{'psgi.input'} = $null_io; 465 | } 466 | } 467 | 468 | sub _finalize_response { 469 | my($self, $env, $res) = @_; 470 | 471 | if ($env->{'psgix.harakiri.commit'}) { 472 | $self->{client}->{keepalive} = 0; 473 | $self->{client}->{harakiri} = 1; 474 | } 475 | 476 | my $protocol = $env->{SERVER_PROTOCOL}; 477 | my $status = $res->[0]; 478 | my $message = status_message($status); 479 | 480 | my(@headers, %headers); 481 | push @headers, "$protocol $status $message"; 482 | 483 | # Switch on Transfer-Encoding: chunked if we don't know Content-Length. 484 | my $chunked; 485 | my $headers = $res->[1]; 486 | for (my $i = 0; $i < @$headers; $i += 2) { 487 | my $k = $headers->[$i]; 488 | my $v = $headers->[$i + 1]; 489 | next if $k eq 'Connection'; 490 | push @headers, "$k: $v"; 491 | $headers{lc $k} = $v; 492 | } 493 | 494 | if ( $protocol eq 'HTTP/1.1' ) { 495 | if ( !exists $headers{'content-length'} ) { 496 | if ( $status !~ /^1\d\d|[23]04$/ && $env->{REQUEST_METHOD} ne 'HEAD' ) { 497 | DEBUG && warn "[$$] Using chunked transfer-encoding to send unknown length body\n"; 498 | push @headers, 'Transfer-Encoding: chunked'; 499 | $chunked = 1; 500 | } 501 | } 502 | elsif ( my $te = $headers{'transfer-encoding'} ) { 503 | if ( $te eq 'chunked' ) { 504 | DEBUG && warn "[$$] Chunked transfer-encoding set for response\n"; 505 | $chunked = 1; 506 | } 507 | } 508 | } else { 509 | if ( !exists $headers{'content-length'} ) { 510 | DEBUG && warn "[$$] Disabling keep-alive after sending unknown length body on $protocol\n"; 511 | $self->{client}->{keepalive} = 0; 512 | } 513 | } 514 | 515 | if ( ! $headers{date} ) { 516 | push @headers, "Date: " . time2str( time() ); 517 | } 518 | 519 | # Should we keep the connection open? 520 | if ( $self->{client}->{keepalive} ) { 521 | push @headers, 'Connection: keep-alive'; 522 | } else { 523 | push @headers, 'Connection: close'; 524 | } 525 | 526 | my $conn = $self->{server}->{client}; 527 | 528 | # Buffer the headers so they are sent with the first write() call 529 | # This reduces the number of TCP packets we are sending 530 | _syswrite($conn, \(join( $CRLF, @headers, '' ) . $CRLF)); 531 | 532 | if (defined $res->[2]) { 533 | Plack::Util::foreach($res->[2], sub { 534 | my $buffer = $_[0]; 535 | if ($chunked) { 536 | my $len = length $buffer; 537 | return unless $len; 538 | $buffer = sprintf( "%x", $len ) . $CRLF . $buffer . $CRLF; 539 | } 540 | _syswrite($conn, \$buffer); 541 | }); 542 | _syswrite($conn, \"0$CRLF$CRLF") if $chunked; 543 | } else { 544 | return Plack::Util::inline_object 545 | write => sub { 546 | my $buffer = $_[0]; 547 | if ($chunked) { 548 | my $len = length $buffer; 549 | return unless $len; 550 | $buffer = sprintf( "%x", $len ) . $CRLF . $buffer . $CRLF; 551 | } 552 | _syswrite($conn, \$buffer); 553 | }, 554 | close => sub { 555 | _syswrite($conn, \"0$CRLF$CRLF") if $chunked; 556 | }; 557 | } 558 | } 559 | 560 | sub _syswrite { 561 | my ($conn, $buffer_ref) = @_; 562 | 563 | my $amount = length $$buffer_ref; 564 | my $offset = 0; 565 | 566 | while ($amount > 0) { 567 | my $len = syswrite($conn, $$buffer_ref, $amount, $offset); 568 | 569 | if (not defined $len) { 570 | return if $! == EPIPE; 571 | return if $! == ECONNRESET; 572 | redo if $! == EINTR; 573 | die "write error: $!"; 574 | } 575 | 576 | $amount -= $len; 577 | $offset += $len; 578 | 579 | DEBUG && warn "[$$] Wrote $len byte", ($len == 1 ? '' : 's'), "\n"; 580 | } 581 | } 582 | 583 | sub _sysread { 584 | while (1) { 585 | my $len = sysread $_[0], $_[1], $_[2]; 586 | return $len if defined $len or $! != EINTR; 587 | } 588 | } 589 | 590 | sub _write_informational { 591 | my ($conn, $code, $headers) = @_; 592 | my $message = HTTP::Status::status_message($code); 593 | my @lines = "HTTP/1.1 $code $message"; 594 | for (my $i = 0; $i < @$headers; $i += 2) { 595 | my $k = $headers->[$i]; 596 | my $v = $headers->[$i + 1]; 597 | push @lines, "$k: $v" ; 598 | } 599 | _syswrite($conn, \join($CRLF, @lines, $CRLF)); 600 | 601 | DEBUG && warn "[$$] Sent $code $message response\n"; 602 | } 603 | 604 | sub post_client_connection_hook { 605 | my $self = shift; 606 | if ($self->{client}->{harakiri}) { 607 | exit; 608 | } 609 | } 610 | 611 | 1; 612 | -------------------------------------------------------------------------------- /script/starman: -------------------------------------------------------------------------------- 1 | #!perl 2 | use strict; 3 | use Plack::Runner; 4 | 5 | sub version { 6 | require Starman; 7 | print "Starman $Starman::VERSION\n"; 8 | } 9 | 10 | my $preload_app; 11 | 12 | require Getopt::Long; 13 | Getopt::Long::Configure("no_ignore_case", "no_auto_abbrev", "pass_through"); 14 | Getopt::Long::GetOptions( 15 | "preload-app" => \$preload_app, 16 | ); 17 | 18 | my @args = (server => 'Starman', env => 'deployment', version_cb => \&version); 19 | if (!$preload_app) { 20 | push @args, 'loader' => 'Delayed'; 21 | } 22 | 23 | my @argv = @ARGV; 24 | 25 | my $runner = Plack::Runner->new(@args); 26 | $runner->parse_options(@argv); 27 | 28 | if ($runner->{loader} eq 'Restarter') { 29 | warn <set_options(argv => \@argv); 38 | $runner->run; 39 | 40 | __END__ 41 | 42 | =head1 NAME 43 | 44 | starman - Starman launcher 45 | 46 | =head1 SYNOPSIS 47 | 48 | starman --listen :5001 --listen /tmp/starman.sock 49 | starman --workers 32 --port 8080 50 | 51 | =head1 OPTIONS 52 | 53 | =over 4 54 | 55 | =item -l, --listen 56 | 57 | --listen HOST:PORT --listen :PORT --listen UNIX_SOCKET 58 | --listen HOST:PORT:ssl 59 | 60 | Specifies the TCP address, ports and UNIX domain sockets to bind to 61 | wait for requests. You can repeat as many times as you want and mix 62 | TCP and UNIX domain sockets. 63 | 64 | For TCP sockets you can append C<:ssl> after the port to specify that 65 | connections on that port should use SSL. Note that the SSL support is 66 | experimental and hasn't been widely tested. 67 | 68 | Defaults to any IP address and port 5000. 69 | 70 | =item --host 71 | 72 | --host 127.0.0.1 73 | 74 | Specifies the address to bind. 75 | 76 | This option is for compatibility with L and you're 77 | recommended to use C<--listen> instead. 78 | 79 | =item --port 80 | 81 | --port 8080 82 | 83 | Specifies the port to bind. 84 | 85 | This option is for compatibility with L and you're 86 | recommended to use C<--listen> instead. 87 | 88 | =item -S, --socket 89 | 90 | -S /tmp/starman.sock 91 | 92 | Specifies the path to UNIX domain socket to bind. 93 | 94 | This option is for compatibility with L and you're 95 | recommended to use C<--listen> instead. 96 | 97 | =item --workers 98 | 99 | Specifies the size of the worker pool. Defaults to 5. 100 | 101 | Starman by default sets up other spare server configuration based on this 102 | workers value, making sure there are B C worker 103 | processes running. So even if there're no idle workers, Starman won't 104 | spawn off spare processes since that's mostly what you want to do by 105 | fine tuning the memory usage etc. in the production environment. 106 | 107 | =item --backlog 108 | 109 | Specifies the backlog size (listen queue size) of listener sockets. Defaults to 1024. 110 | 111 | On production systems, setting a very low value can allow failover on 112 | frontend proxy (like nginx) to happen more quickly, if you have 113 | multiple Starman clusters. 114 | 115 | If you're doing simple benchmarks and getting connection errors, 116 | increasing this parameter can help avoid them. You should also 117 | consider increasing C. Note that this is not 118 | recommended for real production systems if you have another cluster to 119 | failover (see above). 120 | 121 | =item --max-requests 122 | 123 | Number of requests to process per one worker process. Defaults to 1000. 124 | 125 | =item --preload-app 126 | 127 | This option lets Starman preload the specified PSGI application in the 128 | master parent process before preforking children. This allows memory 129 | savings with copy-on-write memory management. When not set (default), 130 | forked children load the application in the initialization hook. 131 | 132 | Enabling this option can cause bad things to happen when resources like 133 | sockets or database connections are opened at load time by the master 134 | process and shared by multiple children. 135 | 136 | Since Starman 0.2000, this option defaults to false, and you should 137 | explicitly set this option to preload the application in the master 138 | process. 139 | 140 | Alternatively, you can use the C<-M> command line option (plackup's common 141 | option) to preload the I rather than the 142 | itself. 143 | 144 | starman -MCatalyst -MDBIx::Class myapp.psgi 145 | 146 | will load the modules in the master process for memory savings with 147 | CoW, but the actual loading of C is done per child, 148 | making management of resources such as database connections safer. 149 | 150 | If you enable this option, sending a C signal to the master process 151 | I pick up any code changes you make. See L for 152 | details. 153 | 154 | =item --disable-keepalive 155 | 156 | Disable Keep-alive persistent connections. It is a useful workaround 157 | if you run Starman behind a broken frontend proxy that tries to pool 158 | more connections than there are backend workers (i.e. Apache 159 | mpm_prefork + mod_proxy). 160 | 161 | =item --keepalive-timeout 162 | 163 | The number of seconds Starman will wait for a subsequent request 164 | before closing the connection if Keep-alive persistent connections 165 | are enabled. Setting this to a high value may cause performance 166 | problems in heavily loaded servers. The higher the timeout, the 167 | more backend workers will be kept occupied waiting on connections 168 | with idle clients. 169 | 170 | Defaults to 1. 171 | 172 | =item --read-timeout 173 | 174 | The number of seconds Starman will wait for a request on a new connection 175 | before closing it. Setting this to a high value may cause performance 176 | problems in heavily loaded servers. The higher the timeout, the 177 | more backend workers will be kept occupied waiting on connections 178 | with idle clients. You may need this if your proxy / load balancer likes to 179 | keep a pool of open connections while waiting for clients (eg. Amazon ELB). 180 | 181 | Defaults to 5. 182 | 183 | =item --user 184 | 185 | To listen on a low-numbered (E1024) port, it will be necessary to 186 | start the server as root. Use the C<--user> option to specify a userid 187 | or username that the server process should switch to after binding to 188 | the port. 189 | 190 | Defaults to the current userid. 191 | 192 | =item --group 193 | 194 | Specify the group id or group name that the server should switch to after 195 | binding to the port. This option is usually used with C<--user>. 196 | 197 | Defaults to the current group id. 198 | 199 | =item --pid 200 | 201 | Specify the pid file path. Use it with C<-D|--daemonize> option, 202 | described in C. 203 | 204 | =item --error-log 205 | 206 | Specify the pathname of a file where the error log should be written. 207 | This enables you to still have access to the errors when using C<--daemonize>. 208 | 209 | =item --ssl-cert 210 | 211 | Specify the path to the SSL certificate file. 212 | 213 | =item --ssl-key 214 | 215 | Specify the path to the SSL key file. 216 | 217 | =item --enable-ssl 218 | 219 | Enable SSL on I TCP sockets. This is an experimental feature. 220 | 221 | =item --disable-proctitle 222 | 223 | Disable the behavior to set proctitle to "starman (master)" and 224 | "starman (worker)" respectively on master and workers. 225 | 226 | =back 227 | 228 | Starman passes through other options given to L, the 229 | common backend that L uses, so most options explained in 230 | C (such as C<--access-log> or C<--daemonize>) work fine in 231 | starman, too. 232 | 233 | Setting the environment variable C to 1 makes the 234 | Starman server run in debug mode. 235 | 236 | =cut 237 | 238 | =head1 SIGNALS 239 | 240 | =over 4 241 | 242 | =item HUP 243 | 244 | Sending C signal to the master process will restart all the workers 245 | gracefully (meaning the currently running requests will shut down once 246 | the request is complete), and by default, the workers will pick up the 247 | code changes you make by reloading the application. 248 | 249 | If you enable C<--preload-app> option, however, the code will be only 250 | loaded in the startup process and will not pick up the code changes 251 | you made. If you want to preload the app I do graceful restarts 252 | by reloading the code changes, you're recommended to use 253 | L, configured to send C signal when superdaemon 254 | received C, i.e: 255 | 256 | start_server --interval 5 --port 8080 --signal-on-hup=QUIT -- \ 257 | starman --preload-app myapp.psgi 258 | 259 | You will then send the HUP signal to C process to 260 | gracefully reload the starman cluster (master and workers). 261 | 262 | With Server::Starter 0.12 or later, you should also be able to set 263 | C<--signal-on-term> to QUIT so that you can safely shutdown Starman 264 | first and then stop the C daemon process as well. 265 | 266 | =item TTIN, TTOU 267 | 268 | Sending C signal to the master process will dynamically increase 269 | the number of workers, and C signal will decrease it. 270 | 271 | =item INT, TERM 272 | 273 | Sending C or C signal to the master process will kill all 274 | the workers immediately and shut down the server. 275 | 276 | =item QUIT 277 | 278 | Sending C signal to the master process will gracefully shutdown 279 | the workers (meaning the currently running requests will shut down 280 | once the request is complete). 281 | 282 | =back 283 | 284 | =head1 RELOADING THE APPLICATION 285 | 286 | You're recommended to use signals (see above) to reload the 287 | application, and are strongly discouraged to use C<-r> or C<-R> 288 | (reloading flag) from plackup. These options will make a separate 289 | directory watcher process, and makes your life difficult if you want to 290 | combine with other process daemon tools such as L. 291 | 292 | =head1 DIFFERENCES WITH PLACKUP 293 | 294 | C executable is basically the equivalent of using C 295 | with C server handler i.e. C, except that 296 | C delay loads the application with the Delayed loader by 297 | default, which can be disabled with C<--preload-app>. 298 | 299 | C command also automatically sets the environment (C<-E>) to 300 | the value of I. 301 | 302 | You're recommended to use C unless there's a reason to stick to 303 | C for compatibility. 304 | 305 | =head1 SEE ALSO 306 | 307 | L 308 | 309 | =cut 310 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More tests => 1; 3 | 4 | BEGIN { use_ok 'Starman' } 5 | -------------------------------------------------------------------------------- /t/chunked_req.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Plack::Test; 3 | use File::ShareDir; 4 | use HTTP::Request; 5 | use Test::More; 6 | use Digest::MD5; 7 | 8 | $Plack::Test::Impl = "Server"; 9 | $ENV{PLACK_SERVER} = 'Starman'; 10 | 11 | my $file = File::ShareDir::dist_dir('Plack') . "/baybridge.jpg"; 12 | 13 | open my $fh, "<", $file or die $!; 14 | my $md5 = Digest::MD5->new; 15 | $md5->addfile($fh); 16 | my $hex = $md5->hexdigest; 17 | 18 | my $app = sub { 19 | my $env = shift; 20 | my $body; 21 | my $clen = $env->{CONTENT_LENGTH}; 22 | while ($clen > 0) { 23 | $env->{'psgi.input'}->read(my $buf, $clen) or last; 24 | $clen -= length $buf; 25 | $body .= $buf; 26 | } 27 | return [ 200, [ 'Content-Type', 'text/plain', 'X-Content-Length', $env->{CONTENT_LENGTH} ], [ $body ] ]; 28 | }; 29 | 30 | test_psgi $app, sub { 31 | my $cb = shift; 32 | 33 | open my $fh, "<:raw", $file; 34 | local $/ = \1024; 35 | 36 | my $req = HTTP::Request->new(POST => "http://localhost/"); 37 | $req->content(sub { scalar <$fh> }); 38 | 39 | my $res = $cb->($req); 40 | 41 | is $res->header('X-Content-Length'), -s $file; 42 | is Digest::MD5::md5_hex($res->content), $hex; 43 | }; 44 | 45 | done_testing; 46 | -------------------------------------------------------------------------------- /t/chunked_termination.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | 3 | use Test::More; 4 | 5 | { 6 | package Starman::Server; 7 | 8 | # Override the sysread method enabling it to read a stream of packages 9 | # from an arrayref instead of an file handle: 10 | use subs 'sysread'; 11 | 12 | *Starman::Server::sysread = sub { 13 | if (ref $_[0] eq "ARRAY") { 14 | die "EWOULDBLOCK\n" unless @{ $_[0] }; 15 | 16 | $_[1] = shift @{ $_[0] }; 17 | return length $_[1]; 18 | } 19 | 20 | return CORE::sysread($_[0], $_[1], $_[2]); 21 | }; 22 | 23 | } 24 | 25 | use Starman::Server; 26 | 27 | my $server = { 28 | server => { 29 | client => [ 30 | "3\015\012foo\015\012", # Full chunk 31 | "3\015\012bar", # Chunk missing terminating HTTP newline 32 | "\015\012", # ... and then the termination 33 | "0\015\012", # Empty chunk to mark end of stream 34 | ], 35 | } 36 | }; 37 | 38 | my $env = { 39 | HTTP_TRANSFER_ENCODING => 'chunked', 40 | }; 41 | 42 | my $blocked; 43 | eval { 44 | Starman::Server::_prepare_env( $server, $env ); 45 | 1; 46 | } or do { 47 | $blocked = 1 if $@ =~ /^EWOULDBLOCK$/; 48 | }; 49 | 50 | ok( !$blocked, "Reading chunked encoding does not block on well-placed package borders" ); 51 | 52 | done_testing; 53 | -------------------------------------------------------------------------------- /t/chunked_zero_length.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Plack::Test; 3 | use HTTP::Request; 4 | use Test::More; 5 | 6 | $Plack::Test::Impl = "Server"; 7 | $ENV{PLACK_SERVER} = 'Starman'; 8 | 9 | my $app = sub { 10 | my $env = shift; 11 | return sub { 12 | my $response = shift; 13 | my $writer = $response->([ 200, [ 'Content-Type', 'text/plain' ]]); 14 | $writer->write("Content"); 15 | $writer->write(""); 16 | $writer->write("Again"); 17 | $writer->close; 18 | } 19 | }; 20 | 21 | test_psgi $app, sub { 22 | my $cb = shift; 23 | 24 | my $req = HTTP::Request->new(GET => "http://localhost/"); 25 | my $res = $cb->($req); 26 | 27 | is $res->content, "ContentAgain"; 28 | }; 29 | 30 | done_testing; 31 | -------------------------------------------------------------------------------- /t/early-hints.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::TCP; 3 | use IO::Socket::INET qw/ SHUT_WR /; 4 | use HTTP::Request; 5 | use HTTP::Response; 6 | use Plack::Loader; 7 | use Test::More; 8 | 9 | $ENV{PLACK_SERVER} = 'Starman'; 10 | 11 | test_tcp( 12 | client => sub { 13 | my $port = shift; 14 | 15 | my $socket = IO::Socket::INET->new( 16 | PeerAddr => 'localhost', 17 | PeerPort => $port, 18 | Proto => 'tcp' 19 | ) or die "Failed to connect to server: $!"; 20 | 21 | my $req_string = join("\r\n", "GET / HTTP/1.1", "Host: localhost", "", ""); 22 | 23 | $socket->send($req_string); 24 | $socket->shutdown(SHUT_WR); 25 | 26 | my $data = ""; 27 | while ($socket->connected) { 28 | my $buf; 29 | $socket->recv($buf, 1024); 30 | $data .= $buf; 31 | } 32 | 33 | my @lines = split /\r\n/, $data; 34 | 35 | is $lines[0], "HTTP/1.1 103 Early Hints"; 36 | is $lines[1], "Link: ; rel=preload"; 37 | is $lines[2], ""; 38 | is $lines[3], "HTTP/1.1 200 OK"; 39 | 40 | }, 41 | server => sub { 42 | my $port = shift; 43 | my $server = Plack::Loader->auto(port => $port, host => '127.0.0.1'); 44 | 45 | $server->run(sub { 46 | my $env = shift; 47 | $env->{'psgix.informational'}->( 103, [ 48 | "Link" => "; rel=preload" 49 | ] ); 50 | return [ 200, [ 'Content-Type', 'text/plain' ], ["ok"] ] 51 | }); 52 | } 53 | ); 54 | 55 | done_testing; 56 | -------------------------------------------------------------------------------- /t/eintr.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Starman::Server; 3 | use Plack::Test; 4 | use HTTP::Request; 5 | use Test::More; 6 | use File::Temp qw(tempfile); 7 | use Time::HiRes qw(sleep); 8 | 9 | my $fh = tempfile; 10 | $fh->autoflush(1); 11 | 12 | # When a child handles our request, write it's pid to the temp file 13 | { 14 | no warnings 'redefine'; 15 | my $old_process_request = \&Starman::Server::process_request; 16 | *Starman::Server::process_request = sub { 17 | seek $fh, 0, 0; 18 | print $fh $$; 19 | goto &$old_process_request; 20 | }; 21 | } 22 | 23 | $Plack::Test::Impl = "Server"; 24 | $ENV{PLACK_SERVER} = 'Starman'; 25 | 26 | my $app = sub { 27 | my $env = shift; 28 | my $body; 29 | my $clen = $env->{CONTENT_LENGTH}; 30 | while ($clen > 0) { 31 | $env->{'psgi.input'}->read(my $buf, $clen) or last; 32 | $clen -= length $buf; 33 | $body .= $buf; 34 | } 35 | return [ 200, [ 'Content-Type', 'text/plain', 'X-Content-Length', $env->{CONTENT_LENGTH} ], [ $body ] ]; 36 | }; 37 | 38 | test_psgi $app, sub { 39 | my $cb = shift; 40 | 41 | my $c = 0; 42 | 43 | my $req = HTTP::Request->new(POST => "http://localhost/"); 44 | $req->content(sub { 45 | $c++; 46 | 47 | # Send some chunked content 48 | return "abcde" if $c == 1; 49 | 50 | # Child should be processing request, get pid 51 | seek $fh, 0, 0; 52 | sysread $fh, my $pid, 100; 53 | 54 | # Ensure child is waiting on a sysread 55 | sleep 0.1; 56 | 57 | kill 'HUP', $pid if $pid; 58 | 59 | # Ensure child received HUP before sending more data 60 | sleep 0.1; 61 | 62 | # Now send it some more content 63 | return "abcde" if $c <= 5; 64 | 65 | return undef; 66 | }); 67 | 68 | my $res = $cb->($req); 69 | 70 | # We should have got 5 x 5 bytes or 25 bytes total 71 | is $res->header('X-Content-Length'), 25; 72 | }; 73 | 74 | done_testing; 75 | -------------------------------------------------------------------------------- /t/expect.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::TCP; 3 | use IO::Socket::INET qw/ SHUT_WR /; 4 | use HTTP::Request; 5 | use HTTP::Response; 6 | use Plack::Loader; 7 | use Test::More; 8 | 9 | $ENV{PLACK_SERVER} = 'Starman'; 10 | 11 | test_tcp( 12 | client => sub { 13 | my $port = shift; 14 | 15 | my $socket = IO::Socket::INET->new( 16 | PeerAddr => 'localhost', 17 | PeerPort => $port, 18 | Proto => 'tcp' 19 | ) or die "Failed to connect to server: $!"; 20 | 21 | my $req_string = join("\r\n", "POST / HTTP/1.1", "Host: localhost", "Expect: 100-CONTINUE", "Content-Length: 0", "", ""); 22 | 23 | $socket->send($req_string); 24 | $socket->shutdown(SHUT_WR); 25 | 26 | my $data = ""; 27 | while ($socket->connected) { 28 | my $buf; 29 | $socket->recv($buf, 1024); 30 | $data .= $buf; 31 | } 32 | 33 | my @lines = split /\r\n/, $data; 34 | 35 | is $lines[0], "HTTP/1.1 100 Continue"; 36 | is $lines[1], ""; 37 | is $lines[2], "HTTP/1.1 200 OK"; 38 | 39 | }, 40 | server => sub { 41 | my $port = shift; 42 | my $server = Plack::Loader->auto(port => $port, host => '127.0.0.1'); 43 | 44 | $server->run(sub { return [ 200, [ 'Content-Type', 'text/plain' ], ["ok"] ] }); 45 | } 46 | ); 47 | 48 | done_testing; 49 | -------------------------------------------------------------------------------- /t/findbin.psgi: -------------------------------------------------------------------------------- 1 | use strict; 2 | use FindBin; 3 | sub { 4 | my $env = shift; 5 | return [ 200, [ "Content-Type", "text/plain" ], [ $FindBin::Bin ] ]; 6 | }; 7 | -------------------------------------------------------------------------------- /t/harakiri.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use HTTP::Request::Common; 5 | use Plack::Test; 6 | use Test::More; 7 | 8 | $Plack::Test::Impl = 'Server'; 9 | $ENV{PLACK_SERVER} = 'Starman'; 10 | 11 | test_psgi 12 | app => sub { 13 | my $env = shift; 14 | return [ 200, [ 'Content-Type' => 'text/plain' ], [$$] ]; 15 | }, 16 | client => sub { 17 | my %seen_pid; 18 | my $cb = shift; 19 | for (1..23) { 20 | my $res = $cb->(GET "/"); 21 | $seen_pid{$res->content}++; 22 | } 23 | cmp_ok(keys(%seen_pid), '<=', 5, 'In non-harakiri mode, pid is reused'); 24 | }; 25 | 26 | test_psgi 27 | app => sub { 28 | my $env = shift; 29 | $env->{'psgix.harakiri.commit'} = 1; 30 | return [ 200, [ 'Content-Type' => 'text/plain' ], [$$] ]; 31 | }, 32 | client => sub { 33 | my %seen_pid; 34 | my $cb = shift; 35 | for (1..23) { 36 | my $res = $cb->(GET "/"); 37 | $seen_pid{$res->content}++; 38 | } 39 | is keys(%seen_pid), 23, 'In Harakiri mode, each pid only used once'; 40 | }; 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/lf_only_request.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Plack::Test; 5 | use HTTP::Request; 6 | use Test::More; 7 | 8 | { 9 | 10 | package Starman::Server; 11 | 12 | # override so we can mangle the HTTP request 13 | use subs 'sysread'; 14 | 15 | *Starman::Server::sysread = sub { 16 | my $read = CORE::sysread( $_[0], $_[1], $_[2] ); 17 | $_[1] =~ s/\r\n/\n/g; 18 | 19 | return $read; 20 | }; 21 | 22 | } 23 | 24 | $Plack::Test::Impl = "Server"; 25 | $ENV{PLACK_SERVER} = 'Starman'; 26 | 27 | my $app = sub { 28 | my $env = shift; 29 | return sub { 30 | my $response = shift; 31 | my $writer = $response->( [ 200, [ 'Content-Type', 'text/plain' ] ] ); 32 | $writer->close; 33 | } 34 | }; 35 | 36 | test_psgi $app, sub { 37 | my $cb = shift; 38 | 39 | my $req = HTTP::Request->new( GET => "http://localhost/" ); 40 | 41 | my $res = $cb->($req); 42 | is $res->code, 200; 43 | 44 | }; 45 | 46 | done_testing; 47 | -------------------------------------------------------------------------------- /t/no_chunked_head.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::TCP; 3 | use IO::Socket::INET; 4 | use HTTP::Request; 5 | use HTTP::Response; 6 | use Plack::Loader; 7 | use Test::More; 8 | 9 | $ENV{PLACK_SERVER} = 'Starman'; 10 | 11 | test_tcp( 12 | client => sub { 13 | my $port = shift; 14 | 15 | my $socket = IO::Socket::INET->new( 16 | PeerAddr => 'localhost', 17 | PeerPort => $port, 18 | Proto => 'tcp' 19 | ) or die "Failed to connect to server: $!"; 20 | 21 | my $request = HTTP::Request->new( 22 | HEAD => '/', [ Host => 'localhost' ] 23 | ); 24 | $request->protocol('HTTP/1.1'); 25 | 26 | $socket->send($request->as_string("\r\n")); 27 | $socket->shutdown(1); 28 | 29 | my $data; 30 | while ($socket->connected) { 31 | my $buf; 32 | $socket->recv($buf, 1024); 33 | $data .= $buf; 34 | } 35 | 36 | my $res = HTTP::Response->parse($data); 37 | 38 | is $res->content, ''; 39 | }, 40 | server => sub { 41 | my $port = shift; 42 | my $server = Plack::Loader->auto(port => $port, host => '127.0.0.1'); 43 | 44 | $server->run(sub { return [ 200, [], [] ] }); 45 | } 46 | ); 47 | 48 | done_testing; 49 | -------------------------------------------------------------------------------- /t/rand.psgi: -------------------------------------------------------------------------------- 1 | rand(); # this initializes the random seed 2 | 3 | sub { 4 | return [ 200, ["Content-Type", "text/plain"], [ rand(100) ] ]; 5 | }; 6 | -------------------------------------------------------------------------------- /t/single_zero.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Plack::Test; 3 | use Plack::Request; 4 | use HTTP::Request; 5 | use Test::More; 6 | 7 | $Plack::Test::Impl = "Server"; 8 | $ENV{PLACK_SERVER} = 'Starman'; 9 | 10 | my $app = sub { 11 | my $env = shift; 12 | my $req = Plack::Request->new($env); 13 | is $req->content, "0"; 14 | return sub { 15 | my $response = shift; 16 | my $writer = $response->([ 200, [ 'Content-Type', 'text/plain' ]]); 17 | $writer->write("ok"); 18 | $writer->close; 19 | } 20 | }; 21 | 22 | test_psgi $app, sub { 23 | my $cb = shift; 24 | 25 | my $req = HTTP::Request->new(POST => "http://localhost/"); 26 | $req->content('0'); 27 | my $res = $cb->($req); 28 | 29 | is $res->content, "ok"; 30 | }; 31 | 32 | done_testing; 33 | -------------------------------------------------------------------------------- /t/ssl.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | use Test::Requires { 'LWP::Protocol::https' => 6 }; 4 | use Test::TCP; 5 | use LWP::UserAgent; 6 | use FindBin '$Bin'; 7 | use Starman::Server; 8 | 9 | my $host = 'localhost'; 10 | my $ca_cert = "$Bin/ssl_ca.pem"; 11 | my $server_pem = "$Bin/ssl_key.pem"; 12 | 13 | my ($success, $status, $content); 14 | 15 | test_tcp( 16 | client => sub { 17 | my $port = shift; 18 | 19 | my $ua = LWP::UserAgent->new( 20 | timeout => 2, 21 | ssl_opts => { 22 | verify_hostname => 1, 23 | SSL_ca_file => $ca_cert, 24 | }, 25 | ); 26 | 27 | my $res = $ua->get("https://$host:$port"); 28 | $success = $res->is_success; 29 | $status = $res->status_line; 30 | $content = $res->decoded_content; 31 | }, 32 | server => sub { 33 | my $port = shift; 34 | Starman::Server->new->run( 35 | sub { [ 200, [], [$_[0]{'psgi.url_scheme'}] ] }, 36 | { 37 | host => $host, 38 | port => $port, 39 | ssl => 1, 40 | ssl_key => $server_pem, 41 | ssl_cert => $server_pem, 42 | }, 43 | ); 44 | } 45 | ); 46 | 47 | ok $success, 'HTTPS connection succeeded'; 48 | diag $status if not $success; 49 | is $content, 'https', '... and URL scheme is reported correctly'; 50 | 51 | done_testing; 52 | -------------------------------------------------------------------------------- /t/ssl_ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDazCCAlOgAwIBAgIUIiI/iRVTq/tVgh0aRA5NdV00hWIwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM 4 | BENQQU4xEjAQBgNVBAMMCVR3aWdneSBDQTAeFw0xODEwMjkyMDAyMDJaFw0yOTEw 5 | MTEyMDAyMDJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMQ0w 6 | CwYDVQQKDARDUEFOMRIwEAYDVQQDDAlUd2lnZ3kgQ0EwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQDBFop2+rRjBb5ljDQf9Nf6RLuGblKkQHlTX0IyWjB2 8 | LMOs8hjvQTcGcj1F4K1C6R6enapyo3oHy3VRXwncFcuIkGsblCiQe7N5eDxXDm3S 9 | /OnGit7e4wWXT//Jz4UPqb5E2egE2/UD9vNiMgpLlBtcpocs4ftG9zcAfc30Iti+ 10 | +0cY9Xj+2PidGNLGTnLVxBHgcMAuVl56Bln++t4zhzMULnmDLZRn7A4Y2hgASwgi 11 | /pEOzhDaotUA3UWxHT5ji3zowYFPEaeCrLaVIhLB03t1kyKI37gyzdQLvfb8zgSV 12 | dQfTuUJg3xALQ5SwkJ3YkkB88kzx1a5WmznRU+B0hX8PAgMBAAGjUzBRMB0GA1Ud 13 | DgQWBBR6irOqu7DaNMbdFDxkwJbO/ghaoTAfBgNVHSMEGDAWgBR6irOqu7DaNMbd 14 | FDxkwJbO/ghaoTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBD 15 | 3G4sUS6Oves6Ov/l1qPri02dTF5KZIvnn7PX4vTGq022/q1tRd5tL9uwttSutb6i 16 | 5/iqL9JMz1/ExJk4LN5y3Zl7jf5a8wg8Tm/7Wu8gfbSn6ChgbfWcwcrje5ImgsHN 17 | evwgQxm2WgXkiqvpnvOramCYSwEyhEBX89hJWaBkuSKuDa/rPpjQrce3DbzzQT3C 18 | LOiHppZk5rOyOLLGoZYwWbS2+MfQKz6dQmSrMWwwk5CamPGCDvj1Cwmh/hMY2dNq 19 | lzSeT7YFThqDv0XhQxTFhkvXOK8xrQuGLvCFIYGZYbNAd2gCguY5lBELJFJHfBFD 20 | 3hn7L7wx4M++rOqlCLPm 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /t/ssl_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDqNpja2Nj0nyhL 3 | LIxILjRoLt7Z8rp96/G7pxgoQ5USJr5y2gx7FfKnalF+UEJdZgjNcIkWWm8aybBI 4 | tKXcmFH0axWMGZXsQdNdw2tzWzj8dkiWGpdyMLMBFLlihofyXApHl7oeg+kFCVpA 5 | 3ov11Z6p10vVKTd7Do+AAcfsVv/RUJWPS4a+ETQMRzk9xYO6GKt80f0RRkv/VAu1 6 | yvmEfaZAtKhUFrqbKQmISdIjkDbjoWgXc0Lg7NCcf1p7mNBh6UjxCqxo0M++EtPD 7 | 7adO+5DsI7LuoLF5Gsj1DBRITkJvGNBROz0S982XROebyDo5NOcIIjipns1A2wUb 8 | QrPD83ifAgMBAAECggEAEHxZVAYxdz6AspHnKel+rNqnuwWX1Oc3s7K2Q5dGNene 9 | O+1XNY687sXQCKkTksls0zEZpSYmbedGbkew6Q8bra2f7aWbZO7ZNt9bf9oq77JP 10 | t6kVgeclomzYA7Ree1O5MYLfiehVDZKz9J71kQ9BRMkvwEhPbG0A8sytSthw10XH 11 | UTcr+WBs2MhHTzPHkNon/lea8543aq+ccHwx69E0GKnixIE+wVPYBgwf1ZSdOiHh 12 | iPEknoYgzLUbq2etcWfGf9OzEulPsLYvHf/FsU85DmHr78LuBaJJHnw/gD4G/uCk 13 | U9iW0K9pnB8dQVdjIB4Qms1yPkdnzSlZYjKZpDhAWQKBgQD/Co0iZpizUkwB4WjJ 14 | yboM7CmW/lxoQQ0OgIwFP2Bqy5GK42//MT50z14V7LMkod1ZjYF7DGuWzhZ8mzAH 15 | OqUiYVj3vd61J6to4aZ3YGYy9Wn2DQE0YCv0tNQ2Hy5H5X0my55kVhBKGuWuz8gV 16 | bcLzGcCgxTh+g4QVRCrnbgWybQKBgQDrGABPVYGXd9Lr6nsYW2FKrxdtuEfJKppH 17 | 2Ai2co7WBPtpcm0rQrQQpkQlVr+dLQEs9couZ2o7l10GeIC2cmUQjT3IDqZDirOR 18 | DfWD5FJO8kmCL1Lqx8gbaJk1GDmas838LtKVvWQjyD/YmSGU3gaHgCompDYFrSBp 19 | Av2dAhTPuwKBgDgM1pWf3KFUTdX/9uqaBeR/JmjDwAU9fqQYRi2FDyiJUpQmhd+c 20 | r/A1/qRs37YGSMI8oh8rzJ1Y74I1DOoZzl7u7AGOXdqMPFSReuczbWJ3ovDrTL94 21 | /1AJSbYXRjGQ0t9q0oWUkIHoxV+U1JE5DSYmG7p5fX/4YVfXImK4QFJFAoGBAI3G 22 | zLELbPrOrWnO3+theHwUublWkBw2UBKRqzd0QMW6/W+rLUEQmg0MaD5oYWhFctcN 23 | Z5+yiDNl5hj35Q+iT2a69w5GH2jOJdqNo4ml0SmBHmmfZo7marx0yShm8p5kXw39 24 | osvhCpo1sqNKPiFMAH4JAeKJ13Z24PvTbOUaEzjvAoGAMMlBaOHyhOUtcNwjg12C 25 | IpSZiStcQFOPgOb+4lO6MKyQaU4yp8Y6yXZGuBfZDdYKxXc7g9pGPEu7wYE8JX4a 26 | xcixJrkvbq8kMbbS2GcStrDohPAgmptdhHGR00+jGN1EnK40a0nKrFii0RwvucxW 27 | FYizcklNMPmFOHuJQopwG+g= 28 | -----END PRIVATE KEY----- 29 | -----BEGIN CERTIFICATE----- 30 | MIIDTjCCAjagAwIBAgIUIiI/iRVTq/tVgh0aRA5NdV00hWQwDQYJKoZIhvcNAQEL 31 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM 32 | BENQQU4xEjAQBgNVBAMMCVR3aWdneSBDQTAeFw0xODEwMjkyMDE3MDhaFw0yOTEw 33 | MTEyMDE3MDhaMFsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMQ0w 34 | CwYDVQQHDARQZXJsMRQwEgYDVQQKDAtUd2lnZ3k6OlRMUzESMBAGA1UEAwwJbG9j 35 | YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6jaY2tjY9J8o 36 | SyyMSC40aC7e2fK6fevxu6cYKEOVEia+ctoMexXyp2pRflBCXWYIzXCJFlpvGsmw 37 | SLSl3JhR9GsVjBmV7EHTXcNrc1s4/HZIlhqXcjCzARS5YoaH8lwKR5e6HoPpBQla 38 | QN6L9dWeqddL1Sk3ew6PgAHH7Fb/0VCVj0uGvhE0DEc5PcWDuhirfNH9EUZL/1QL 39 | tcr5hH2mQLSoVBa6mykJiEnSI5A246FoF3NC4OzQnH9ae5jQYelI8QqsaNDPvhLT 40 | w+2nTvuQ7COy7qCxeRrI9QwUSE5CbxjQUTs9EvfNl0Tnm8g6OTTnCCI4qZ7NQNsF 41 | G0Kzw/N4nwIDAQABoyAwHjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDAN 42 | BgkqhkiG9w0BAQsFAAOCAQEAVOEsMwoWZozYM102nwwRDveaqsLmQUIq6CfAQ1tC 43 | 6WtxtV3ZpydfRJauwNEa1tPtWn7RrqHttZwwG5YD8TvxqJvX18nJXQz8GUef2L65 44 | ZxPoiZOd+pL7eB9P7Hscyp2q76JWJDd0j86QMoL8P9MqZ6J1z4UgoeaDVX1YDRe9 45 | WBtgI+sy4O+v1uPHNv1yT3mG4cTrc8PKFh5G9KmqMAAqha9HslaEWHwz7mXJxOS0 46 | PegBGFjjIw/S6XV5kf7ZLbBnZDRKah9BPxL/K8lmZnBReAJaNMVkE/1KA/5aNp4J 47 | 3m9c4DXL+svrQim8nFG3TizjbPkmuRnts5+jwSe5IB3bvQ== 48 | -----END CERTIFICATE----- 49 | -------------------------------------------------------------------------------- /t/ssl_largebody.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | use Test::Requires { 'LWP::Protocol::https' => 6 }; 4 | use Test::TCP; 5 | use LWP::UserAgent; 6 | use FindBin '$Bin'; 7 | use Starman::Server; 8 | 9 | # https://github.com/miyagawa/Starman/issues/78 10 | 11 | my $host = 'localhost'; 12 | my $ca_cert = "$Bin/ssl_ca.pem"; 13 | my $server_pem = "$Bin/ssl_key.pem"; 14 | my $body = 'x'x32*1024; # > 16KB 15 | 16 | my ($success, $status, $content); 17 | 18 | test_tcp( 19 | client => sub { 20 | my $port = shift; 21 | 22 | my $ua = LWP::UserAgent->new( 23 | timeout => 2, 24 | ssl_opts => { 25 | verify_hostname => 1, 26 | SSL_ca_file => $ca_cert, 27 | }, 28 | ); 29 | 30 | my $res = $ua->get("https://$host:$port"); 31 | $success = $res->is_success; 32 | $status = $res->status_line; 33 | $content = $res->decoded_content; 34 | }, 35 | server => sub { 36 | my $port = shift; 37 | Starman::Server->new->run( 38 | sub { [ 200, [], [$body] ] }, 39 | { 40 | host => $host, 41 | port => $port, 42 | ssl => 1, 43 | ssl_key => $server_pem, 44 | ssl_cert => $server_pem, 45 | }, 46 | ); 47 | } 48 | ); 49 | 50 | ok $success, 'HTTPS connection succeeded'; 51 | diag $status if not $success; 52 | is $content, $body; 53 | 54 | done_testing; 55 | -------------------------------------------------------------------------------- /t/suite.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | use Plack::Test::Suite; 5 | 6 | Plack::Test::Suite->run_server_tests('Starman'); 7 | done_testing(); 8 | 9 | -------------------------------------------------------------------------------- /xt/release/findbin.t: -------------------------------------------------------------------------------- 1 | use Test::TCP; 2 | use LWP::UserAgent; 3 | use FindBin; 4 | use Test::More; 5 | 6 | my $s = Test::TCP->new( 7 | code => sub { 8 | my $port = shift; 9 | exec $^X, "script/starman", "--port", $port, "--max-requests=1", "--workers=1", "t/findbin.psgi"; 10 | }, 11 | ); 12 | 13 | my $ua = LWP::UserAgent->new(timeout => 3); 14 | 15 | for (1..2) { 16 | my $res = $ua->get("http://localhost:" . $s->port); 17 | is $res->content, $FindBin::Bin; 18 | } 19 | 20 | done_testing; 21 | -------------------------------------------------------------------------------- /xt/release/rand.t: -------------------------------------------------------------------------------- 1 | use Test::TCP; 2 | use LWP::UserAgent; 3 | use FindBin; 4 | use Test::More; 5 | 6 | for (1..2) { # preload, non-preload 7 | my @preload = $_ == 1 ? ("--preload-app") : (); 8 | 9 | my $s = Test::TCP->new( 10 | code => sub { 11 | my $port = shift; 12 | exec $^X, "script/starman", @preload, "--port", $port, "--max-requests=1", "--workers=1", "t/rand.psgi"; 13 | }, 14 | ); 15 | 16 | my $ua = LWP::UserAgent->new; 17 | 18 | my @res; 19 | for (1..2) { 20 | push @res, $ua->get("http://localhost:" . $s->port); 21 | } 22 | 23 | isnt $res[0]->content, $res[1]->content; 24 | 25 | undef $s; 26 | } 27 | 28 | done_testing; 29 | --------------------------------------------------------------------------------