├── .gitignore ├── LICENSE ├── README.md ├── README.zh.md ├── _config.yml ├── package-list ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── gitlab │ │ └── leibnizhu │ │ └── sbnetty │ │ ├── bootstrap │ │ ├── EmbeddedNettyAutoConfiguration.java │ │ └── EmbeddedNettyFactory.java │ │ ├── core │ │ ├── ClientAbortException.java │ │ ├── NettyAsyncContext.java │ │ ├── NettyContainer.java │ │ ├── NettyContext.java │ │ ├── NettyFilterChain.java │ │ ├── NettyRequestDispatcher.java │ │ ├── RequestDispatcherHandler.java │ │ ├── RequestSession.java │ │ └── RequestSessionAggregator.java │ │ ├── registration │ │ ├── AbstractNettyRegistration.java │ │ ├── NettyFilterRegistration.java │ │ └── NettyServletRegistration.java │ │ ├── request │ │ ├── HttpRequestInputStream.java │ │ ├── HttpRequestInputStreamReadListenerOp.java │ │ ├── NettyHttpServletRequest.java │ │ └── NettyPart.java │ │ ├── response │ │ ├── HttpResponseOutputStream.java │ │ └── NettyHttpServletResponse.java │ │ ├── session │ │ ├── NettyHttpSession.java │ │ ├── NettyHttpSessionFacade.java │ │ └── NettySessionManager.java │ │ └── utils │ │ ├── MappingData.java │ │ ├── MimeTypeUtil.java │ │ └── RequestUrlPatternMapper.java └── resources │ └── META-INF │ └── spring.factories └── test ├── java └── io │ └── gitlab │ └── leibnizhu │ └── sbnetty │ ├── benchmark │ └── NettyServletBenchmark.java │ └── functional │ ├── CustomMVCConfiguration.java │ ├── RequestUtlPatternMapperTest.java │ ├── TestWebApp.java │ └── TestWebAppTester.java └── resources ├── application.yml └── banner.txt /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ 25 | .idea/ 26 | .vim/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-starter-netty 2 | [[English]](https://github.com/Leibnizhu/spring-boot-starter-netty/blob/master/README.md) [[中文]](https://github.com/Leibnizhu/spring-boot-starter-netty/blob/master/README.zh.md) 3 | ## Introduction 4 | * This is a Spring Boot embedded servlet container project base on netty API. 5 | * Only supports Netty versions greater than 4.1.41.Final, you can specify 6 | the version number to be used in the pom.xml file of the project. 7 | 8 | ## Maven Dependencies 9 | 1. Clone project and install: 10 | ```shell 11 | $ mvn clean install 12 | ``` 13 | 14 | 1. add the dependencies below to your maven project: 15 | ```xml 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-web 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-tomcat 25 | 26 | 27 | 28 | 29 | 30 | io.gitlab.leibnizhu 31 | spring-boot-starter-netty 32 | 1.1-RELEASE 33 | 34 | 35 | 36 | io.netty 37 | netty-all 38 | 4.1.113.Final 39 | 40 | 41 | ``` 42 | 43 | ## Blogs about how to design/code this project 44 | Only Chineses, updating one after another…… 45 | [基于Netty的Spring Boot内置Servlet容器的实现(一)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%B8%80/) 46 | [基于Netty的Spring Boot内置Servlet容器的实现(二)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%BA%8C/) 47 | [基于Netty的Spring Boot内置Servlet容器的实现(三)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%B8%89/) 48 | [基于Netty的Spring Boot内置Servlet容器的实现(四)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%9B%9B/) 49 | [基于Netty的Spring Boot内置Servlet容器的实现(五)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%BA%94/) 50 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # spring-boot-starter-netty 2 | [[English]](https://github.com/Leibnizhu/spring-boot-starter-netty/blob/master/README.md) [[中文]](https://github.com/Leibnizhu/spring-boot-starter-netty/blob/master/README.zh.md) 3 | 4 | ## 简介 5 | * 一个基于Netty实现的Spring Boot内置Servlet容器。 6 | * 仅支持 Netty 4.1.41.Final 及以上版本,可以在项目pom.xml文件中指定使用的版本号。 7 | 8 | ## 构建 9 | 1. 获取工程并安装到本地: 10 | ```shell 11 | $ mvn clean install 12 | ``` 13 | 14 | 1. 在你的Spring-Boot项目中加入以下依赖: 15 | ```xml 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-web 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-tomcat 25 | 26 | 27 | 28 | 29 | 30 | io.gitlab.leibnizhu 31 | spring-boot-starter-netty 32 | 1.2 33 | 34 | 35 | 36 | io.netty 37 | netty-all 38 | 4.1.113.Final 39 | 40 | 41 | ``` 42 | 43 | ## 代码设计分析的博文 44 | 陆续更新中…… 45 | [基于Netty的Spring Boot内置Servlet容器的实现(一)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%B8%80/) 46 | [基于Netty的Spring Boot内置Servlet容器的实现(二)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%BA%8C/) 47 | [基于Netty的Spring Boot内置Servlet容器的实现(三)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%B8%89/) 48 | [基于Netty的Spring Boot内置Servlet容器的实现(四)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%9B%9B/) 49 | [基于Netty的Spring Boot内置Servlet容器的实现(五)](https://leibnizhu.github.io/p/%E5%9F%BA%E4%BA%8ENetty%E7%9A%84Spring-Boot%E5%86%85%E7%BD%AEServlet%E5%AE%B9%E5%99%A8%E7%9A%84%E5%AE%9E%E7%8E%B0%E4%BA%94/) 50 | 51 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /package-list: -------------------------------------------------------------------------------- 1 | io.gitlab.leibnizhu.sbnetty.benchmark 2 | io.gitlab.leibnizhu.sbnetty.bootstrap 3 | io.gitlab.leibnizhu.sbnetty.core 4 | io.gitlab.leibnizhu.sbnetty.functional 5 | io.gitlab.leibnizhu.sbnetty.registration 6 | io.gitlab.leibnizhu.sbnetty.request 7 | io.gitlab.leibnizhu.sbnetty.response 8 | io.gitlab.leibnizhu.sbnetty.session 9 | io.gitlab.leibnizhu.sbnetty.utils 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | io.gitlab.leibnizhu 8 | spring-boot-starter-netty 9 | jar 10 | 1.2 11 | 12 | spring-boot-starter-netty 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | repo 19 | 20 | 21 | 22 | 23 | master 24 | https://github.com/Leibnizhu/spring-boot-starter-netty 25 | scm:git:git@github.com:Leibnizhu/spring-boot-starter-netty.git 26 | scm:git:git@github.com:Leibnizhu/spring-boot-starter-netty.git 27 | 28 | 29 | 30 | 31 | Leibniz007 32 | Leibniz 33 | Leibnizhu@gmail.com 34 | 35 | 36 | 37 | 38 | UTF-8 39 | UTF-8 40 | 1.8 41 | 8 42 | 3.6.2 43 | 3.1.0 44 | 3.0.0 45 | 3.3.1 46 | 3.5.0 47 | 3.0.2 48 | 3.0.1 49 | 2.10.4 50 | 1.6 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-parent 56 | 2.7.18 57 | 58 | 59 | 60 | 61 | 62 | 63 | io.netty 64 | netty-all 65 | 66 | 67 | org.javassist 68 | javassist 69 | 3.20.0-GA 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-starter-web 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-tomcat 79 | 80 | 81 | 82 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-test 89 | test 90 | 91 | 92 | 93 | javax.servlet 94 | javax.servlet-api 95 | 3.1.0 96 | 97 | 98 | com.google.guava 99 | guava 100 | 32.0.0-jre 101 | 102 | 103 | junit 104 | junit 105 | test 106 | 107 | 108 | com.alibaba 109 | fastjson 110 | 1.2.83 111 | test 112 | 113 | 114 | org.openjdk.jmh 115 | jmh-core 116 | 1.19 117 | test 118 | 119 | 120 | org.openjdk.jmh 121 | jmh-generator-annprocess 122 | 1.19 123 | test 124 | 125 | 126 | 127 | 128 | 129 | 130 | oss 131 | 132 | https://oss.sonatype.org/service/local/staging/deploy/maven2 133 | 134 | 135 | 136 | oss 137 | false 138 | 139 | https://oss.sonatype.org/content/repositories/snapshots 140 | 141 | 142 | 143 | 144 | 145 | 146 | release 147 | 148 | 149 | 150 | org.apache.maven.plugins 151 | maven-source-plugin 152 | ${source-plugin.version} 153 | 154 | 155 | attach-sources 156 | 157 | jar-no-fork 158 | 159 | 160 | 161 | 162 | true 163 | 164 | 165 | 166 | org.apache.maven.plugins 167 | maven-javadoc-plugin 168 | ${javadoc-plugin.version} 169 | 170 | 171 | package 172 | 173 | jar 174 | 175 | 176 | 177 | 178 | 179 | 180 | http://docs.oracle.com/javase/${javadoc.version}/docs/api 181 | 182 | 183 | 184 | 185 | 186 | org.apache.maven.plugins 187 | maven-gpg-plugin 188 | ${gpg-plugin.version} 189 | 190 | 191 | install 192 | 193 | sign 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | oss 203 | 204 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | org.apache.maven.plugins 215 | maven-compiler-plugin 216 | 217 | ${java.version} 218 | ${java.version} 219 | 220 | 221 | 222 | 223 | org.apache.maven.plugins 224 | maven-jar-plugin 225 | ${jar-plugin.version} 226 | 227 | 228 | 229 | org.apache.maven.plugins 230 | maven-war-plugin 231 | 232 | spring-boot-starter-netty 233 | false 234 | 235 | 236 | 237 | 238 | org.apache.maven.plugins 239 | maven-clean-plugin 240 | ${clean-plugin.version} 241 | 242 | 243 | 244 | org.apache.maven.plugins 245 | maven-resources-plugin 246 | ${resources-plugin.version} 247 | 248 | 249 | 250 | org.apache.maven.plugins 251 | maven-surefire-plugin 252 | ${surefire-plugin.version} 253 | 254 | true 255 | 256 | 257 | 258 | 259 | org.apache.maven.plugins 260 | maven-source-plugin 261 | 262 | 263 | attach-sources 264 | 265 | jar 266 | 267 | 268 | 269 | 270 | 271 | 272 | org.apache.maven.plugins 273 | maven-javadoc-plugin 274 | 2.10.3 275 | 276 | 277 | attach-javadocs 278 | install 279 | 280 | jar 281 | 282 | 283 | 284 | 285 | UTF-8 286 | UTF-8 287 | -Xdoclint:none 288 | 289 | 290 | 291 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/bootstrap/EmbeddedNettyAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.bootstrap; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigureOrder; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 7 | import org.springframework.boot.autoconfigure.condition.SearchStrategy; 8 | import org.springframework.boot.web.servlet.server.ServletWebServerFactory; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.core.Ordered; 12 | 13 | import io.netty.bootstrap.Bootstrap; 14 | 15 | /** 16 | * 配置加载内置Netty容器的工厂类Bean。 17 | * 最早是直接将EmbeddedNettyFactory加@Component注解,这样集成在任何环境中都会加载,可能引起端口冲突。 18 | * 所以通过这个配置类,配置在当前上下文缺少EmbeddedServletContainerFactory接口实现类时(即缺少内置Servlet容器),加载EmbeddedNettyFactory 19 | * 这样SpringBoot项目在引入这个maven依赖,并且排除了内置tomcat依赖、且没引入其他servlet容器(如jetty)时,就可以通过工厂类加载并启动netty容器了。 20 | * 21 | * @author Leibniz 2017-08-24 22 | */ 23 | @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 24 | @Configuration 25 | @ConditionalOnWebApplication // 在Web环境下才会起作用 26 | public class EmbeddedNettyAutoConfiguration { 27 | @Configuration 28 | @ConditionalOnClass({Bootstrap.class}) // Netty的Bootstrap类必须在classloader中存在,才能启动Netty容器 29 | @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) //当前Spring容器中不存在EmbeddedServletContainerFactory接口的实例 30 | public static class EmbeddedNetty { 31 | //上述条件注解成立的话就会构造EmbeddedNettyFactory这个EmbeddedServletContainerFactory 32 | @Bean 33 | public EmbeddedNettyFactory embeddedNettyFactory() { 34 | return new EmbeddedNettyFactory(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/bootstrap/EmbeddedNettyFactory.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.bootstrap; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.net.URL; 5 | import java.net.URLClassLoader; 6 | import java.util.Random; 7 | 8 | import javax.servlet.ServletException; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.boot.web.server.WebServer; 13 | import org.springframework.boot.web.servlet.ServletContextInitializer; 14 | import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory; 15 | import org.springframework.context.ResourceLoaderAware; 16 | import org.springframework.core.io.ResourceLoader; 17 | import org.springframework.util.ClassUtils; 18 | 19 | import io.gitlab.leibnizhu.sbnetty.core.NettyContainer; 20 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 21 | import io.netty.bootstrap.Bootstrap; 22 | 23 | /** 24 | * Spring Boot会查找EmbeddedServletContainerFactory接口的实现类(工厂类),调用其getEmbeddedServletContainer()方法,来获取web应用的容器 25 | * 所以我们要实现这个接口,这里不直接实现,而是通过继承AbstractEmbeddedServletContainerFactory类来实现 26 | * 27 | * @author Leibniz on 2017-08-24. 28 | */ 29 | public class EmbeddedNettyFactory extends AbstractServletWebServerFactory implements ResourceLoaderAware { 30 | private final Logger log = LoggerFactory.getLogger(getClass()); 31 | private static final String SERVER_INFO = "Netty@SpringBoot"; 32 | private ResourceLoader resourceLoader; 33 | 34 | @Override 35 | public WebServer getWebServer(ServletContextInitializer... initializers) { 36 | ClassLoader parentClassLoader = resourceLoader != null ? resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader(); 37 | //Netty启动环境相关信息 38 | Package nettyPackage = Bootstrap.class.getPackage(); 39 | String title = nettyPackage.getImplementationTitle(); 40 | String version = nettyPackage.getImplementationVersion(); 41 | log.info("Running with " + title + " " + version); 42 | //是否支持默认Servlet 43 | if (isRegisterDefaultServlet()) { 44 | log.warn("This container does not support a default servlet"); 45 | } 46 | //上下文 47 | NettyContext context = new NettyContext(getContextPath(), new URLClassLoader(new URL[]{}, parentClassLoader), SERVER_INFO); 48 | for (ServletContextInitializer initializer : initializers) { 49 | try { 50 | initializer.onStartup(context); 51 | } catch (ServletException e) { 52 | throw new RuntimeException(e); 53 | } 54 | } 55 | //从SpringBoot配置中获取端口,如果没有则随机生成 56 | int port = getPort() > 0 ? getPort() : new Random().nextInt(65535 - 1024) + 1024; 57 | InetSocketAddress address = new InetSocketAddress(port); 58 | log.info("Server initialized with port: " + port); 59 | return new NettyContainer(address, context); //初始化容器并返回 60 | } 61 | 62 | @Override 63 | public void setResourceLoader(ResourceLoader resourceLoader) { 64 | this.resourceLoader = resourceLoader; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/ClientAbortException.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import java.io.IOException; 4 | 5 | public class ClientAbortException extends IOException { 6 | public ClientAbortException(String message) { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/NettyAsyncContext.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | import javax.servlet.*; 7 | import javax.servlet.http.HttpServletRequest; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * 14 | */ 15 | public class NettyAsyncContext implements AsyncContext { 16 | private ServletRequest servletRequest; 17 | private final ChannelHandlerContext ctx; 18 | private ServletResponse servletResponse; 19 | private boolean asyncStarted; 20 | private List listeners; 21 | 22 | public NettyAsyncContext(ServletRequest servletRequest, ChannelHandlerContext ctx) { 23 | this.servletRequest = servletRequest; 24 | this.ctx = ctx; 25 | this.listeners = new ArrayList<>(); 26 | } 27 | 28 | public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { 29 | this.servletRequest = servletRequest; 30 | this.servletResponse = servletResponse; 31 | asyncStarted = true; 32 | return this; 33 | } 34 | 35 | @Override 36 | public ServletRequest getRequest() { 37 | return servletRequest; 38 | } 39 | 40 | @Override 41 | public ServletResponse getResponse() { 42 | return servletResponse; 43 | } 44 | 45 | @Override 46 | public boolean hasOriginalRequestAndResponse() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void dispatch() { 52 | if (servletRequest instanceof HttpServletRequest) { 53 | HttpServletRequest request = (HttpServletRequest) servletRequest; 54 | String path = request.getServletPath(); 55 | String pathInfo = request.getPathInfo(); 56 | if (null != pathInfo) { 57 | path += pathInfo; 58 | } 59 | dispatch(path); 60 | } 61 | } 62 | 63 | @Override 64 | public void dispatch(String path) { 65 | dispatch(servletRequest.getServletContext(), path); 66 | } 67 | 68 | @Override 69 | public void dispatch(ServletContext context, String path) { 70 | final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; 71 | httpRequest.setAttribute(ASYNC_CONTEXT_PATH, httpRequest.getContextPath()); 72 | httpRequest.setAttribute(ASYNC_PATH_INFO, httpRequest.getPathInfo()); 73 | httpRequest.setAttribute(ASYNC_QUERY_STRING, httpRequest.getQueryString()); 74 | httpRequest.setAttribute(ASYNC_REQUEST_URI, httpRequest.getRequestURI()); 75 | httpRequest.setAttribute(ASYNC_SERVLET_PATH, httpRequest.getServletPath()); 76 | final NettyRequestDispatcher dispatcher = (NettyRequestDispatcher) context.getRequestDispatcher(path); 77 | ctx.executor().submit(new Runnable() { 78 | @Override 79 | public void run() { 80 | try { 81 | dispatcher.dispatch(httpRequest, servletResponse); 82 | // TODO is this right? 83 | for (AsyncListener listener : ImmutableList.copyOf(listeners)) { 84 | listener.onComplete(new AsyncEvent(NettyAsyncContext.this)); 85 | } 86 | } catch (ServletException | IOException e) { 87 | // TODO notify listeners 88 | e.printStackTrace(); 89 | } 90 | } 91 | }); 92 | } 93 | 94 | @Override 95 | public void complete() { 96 | try { 97 | servletResponse.getOutputStream().close(); 98 | } catch (IOException e) { 99 | // TODO notify listeners 100 | e.printStackTrace(); 101 | } 102 | } 103 | 104 | @Override 105 | public void start(Runnable run) { 106 | ctx.executor().submit(run, Object.class); 107 | } 108 | 109 | @Override 110 | public void addListener(AsyncListener listener) { 111 | listeners.add(listener); 112 | } 113 | 114 | @Override 115 | public void addListener(AsyncListener listener, ServletRequest servletRequest, ServletResponse servletResponse) { 116 | 117 | } 118 | 119 | @Override 120 | public T createListener(Class clazz) throws ServletException { 121 | return null; 122 | } 123 | 124 | @Override 125 | public void setTimeout(long timeout) { 126 | 127 | } 128 | 129 | @Override 130 | public long getTimeout() { 131 | return 0; 132 | } 133 | 134 | public boolean isAsyncStarted() { 135 | return asyncStarted; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/NettyContainer.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import com.google.common.base.StandardSystemProperty; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.*; 6 | import io.netty.channel.epoll.EpollChannelOption; 7 | import io.netty.channel.epoll.EpollEventLoopGroup; 8 | import io.netty.channel.epoll.EpollServerSocketChannel; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.channel.socket.nio.NioServerSocketChannel; 12 | import io.netty.handler.codec.http.HttpServerCodec; 13 | import io.netty.handler.stream.ChunkedWriteHandler; 14 | import io.netty.util.concurrent.DefaultEventExecutorGroup; 15 | import org.apache.commons.logging.Log; 16 | import org.apache.commons.logging.LogFactory; 17 | import org.springframework.boot.web.server.WebServer; 18 | import org.springframework.boot.web.server.WebServerException; 19 | 20 | import java.net.InetSocketAddress; 21 | 22 | import static com.google.common.base.Preconditions.checkNotNull; 23 | 24 | /** 25 | * Netty servle容器 26 | * 处理请求,返回响应 27 | * 目前不支持JSP,考虑到SpringBoot多用于REST+前后端分离,也不会去实现JSP 28 | * 29 | * @author Leibniz 30 | */ 31 | public class NettyContainer implements WebServer { 32 | private final Log log = LogFactory.getLog(getClass()); 33 | 34 | private final InetSocketAddress address; //监听端口地址 35 | private final NettyContext servletContext; //Context 36 | 37 | //Netty所需的线程池,分别用于接收/监听请求以及处理请求读写 38 | private EventLoopGroup bossGroup; 39 | private EventLoopGroup workerGroup; 40 | private DefaultEventExecutorGroup servletExecutor; 41 | 42 | public NettyContainer(InetSocketAddress address, NettyContext servletContext) { 43 | this.address = address; 44 | this.servletContext = servletContext; 45 | } 46 | 47 | @Override 48 | public void start() throws WebServerException { 49 | servletContext.setInitialised(false); 50 | 51 | ServerBootstrap sb = new ServerBootstrap(); 52 | //根据不同系统初始化对应的EventLoopGroup 53 | if ("Linux".equals(StandardSystemProperty.OS_NAME.value())) { 54 | bossGroup = new EpollEventLoopGroup(1); 55 | workerGroup = new EpollEventLoopGroup();//不带参数,线程数传入0,实际解析为 Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); 56 | sb.channel(EpollServerSocketChannel.class) 57 | .group(bossGroup, workerGroup); 58 | } else { 59 | bossGroup = new NioEventLoopGroup(1); 60 | workerGroup = new NioEventLoopGroup(); 61 | sb.channel(NioServerSocketChannel.class) 62 | .group(bossGroup, workerGroup); 63 | } 64 | sb.option(ChannelOption.SO_REUSEADDR, true) 65 | .option(ChannelOption.SO_BACKLOG, 100); 66 | log.info("Bootstrap configuration: " + sb.toString()); 67 | 68 | servletExecutor = new DefaultEventExecutorGroup(50); 69 | sb.childHandler(new ChannelInitializer() { 70 | @Override 71 | protected void initChannel(SocketChannel ch) throws Exception { 72 | ChannelPipeline p = ch.pipeline(); 73 | p.addLast("codec", new HttpServerCodec(4096, 8192, 8192, false)); //HTTP编码解码Handler 74 | p.addLast("chunked", new ChunkedWriteHandler()); 75 | p.addLast("aggregator", new RequestSessionAggregator(servletContext)); //聚合http请求,等待http请求完全解析完成后,在交给工作线程 76 | p.addLast(checkNotNull(servletExecutor), "filterChain", new RequestDispatcherHandler(servletContext)); //获取请求分发器,让对应的Servlet处理请求,同时处理404情况 77 | } 78 | }); 79 | 80 | servletContext.setInitialised(true); 81 | 82 | ChannelFuture future = sb.bind(address).awaitUninterruptibly(); 83 | Throwable cause = future.cause(); 84 | if (null != cause) { 85 | throw new WebServerException("Could not start Netty server", cause); 86 | } 87 | log.info(servletContext.getServerInfo() + " started on port: " + getPort()); 88 | } 89 | 90 | /** 91 | * 优雅地关闭各种资源 92 | * 93 | * @throws EmbeddedServletContainerException 94 | */ 95 | @Override 96 | public void stop() throws WebServerException { 97 | log.info("Embedded Netty Servlet Container(by Leibniz.Hu) is now shuting down."); 98 | try { 99 | if (null != bossGroup) { 100 | bossGroup.shutdownGracefully().await(); 101 | } 102 | if (null != workerGroup) { 103 | workerGroup.shutdownGracefully().await(); 104 | } 105 | if (null != servletExecutor) { 106 | servletExecutor.shutdownGracefully().await(); 107 | } 108 | } catch (InterruptedException e) { 109 | throw new WebServerException("Container stop interrupted", e); 110 | } 111 | } 112 | 113 | @Override 114 | public int getPort() { 115 | return address.getPort(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/NettyContext.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.gitlab.leibnizhu.sbnetty.registration.NettyFilterRegistration; 5 | import io.gitlab.leibnizhu.sbnetty.registration.NettyServletRegistration; 6 | import io.gitlab.leibnizhu.sbnetty.session.NettySessionManager; 7 | import io.gitlab.leibnizhu.sbnetty.utils.MimeTypeUtil; 8 | import io.gitlab.leibnizhu.sbnetty.utils.RequestUrlPatternMapper; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import javax.servlet.*; 13 | import javax.servlet.descriptor.JspConfigDescriptor; 14 | import java.io.File; 15 | import java.io.FileNotFoundException; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | import java.util.*; 21 | 22 | import static com.google.common.base.Preconditions.checkNotNull; 23 | import static com.google.common.base.Preconditions.checkState; 24 | 25 | /** 26 | * ServletContext实现 27 | */ 28 | public class NettyContext implements ServletContext { 29 | private final Logger log = LoggerFactory.getLogger(getClass()); 30 | 31 | private final String contextPath; //保证不以“/”结尾 32 | private final ClassLoader classLoader; 33 | private final String serverInfo; 34 | private volatile boolean initialized; //记录是否初始化完毕 35 | private RequestUrlPatternMapper servletUrlPatternMapper; 36 | private NettySessionManager sessionManager; 37 | 38 | private final Map servlets = new HashMap<>(); //getServletRegistration()等方法要用,key是ServletName 39 | private final Map filters = new HashMap<>(); //getFilterRegistration()等方法要用,Key是FilterName 40 | private final Map servletMappings = new HashMap<>(); //保存请求路径urlPattern与Servlet名的映射,urlPattern是不带contextPath的 41 | private final Hashtable attributes = new Hashtable<>(); 42 | 43 | /** 44 | * 默认构造方法 45 | * 46 | * @param contextPath contextPath 47 | * @param classLoader classLoader 48 | * @param serverInfo 服务器信息,写在响应的server响应头字段 49 | */ 50 | public NettyContext(String contextPath, ClassLoader classLoader, String serverInfo) { 51 | if(contextPath.endsWith("/")){ 52 | contextPath = contextPath.substring(0, contextPath.length() -1); 53 | } 54 | this.contextPath = contextPath; 55 | this.classLoader = classLoader; 56 | this.serverInfo = serverInfo; 57 | this.servletUrlPatternMapper = new RequestUrlPatternMapper(contextPath); 58 | this.sessionManager = new NettySessionManager(this); 59 | } 60 | 61 | public NettySessionManager getSessionManager() { 62 | return sessionManager; 63 | } 64 | 65 | void setInitialised(boolean initialized) { 66 | this.initialized = initialized; 67 | } 68 | 69 | private boolean isInitialised() { 70 | return initialized; 71 | } 72 | 73 | private void checkNotInitialised() { 74 | checkState(!isInitialised(), "This method can not be called before the context has been initialised"); 75 | } 76 | 77 | public void addServletMapping(String urlPattern, String name, Servlet servlet) throws ServletException { 78 | checkNotInitialised(); 79 | servletMappings.put(urlPattern, checkNotNull(name)); 80 | servletUrlPatternMapper.addServlet(urlPattern, servlet, name); 81 | } 82 | 83 | public void addFilterMapping(EnumSet dispatcherTypes, boolean isMatchAfter, String urlPattern) { 84 | checkNotInitialised(); 85 | //TODO 过滤器的urlPatter解析 86 | } 87 | 88 | /** 89 | * SpringBoot只有一个Context,我觉得直接返回this就可以了 90 | */ 91 | @Override 92 | public ServletContext getContext(String uripath) { 93 | return this; 94 | } 95 | 96 | @Override 97 | public String getContextPath() { 98 | return contextPath; 99 | } 100 | 101 | @Override 102 | public int getMajorVersion() { 103 | return 3; 104 | } 105 | 106 | @Override 107 | public int getMinorVersion() { 108 | return 0; 109 | } 110 | 111 | @Override 112 | public int getEffectiveMajorVersion() { 113 | return 3; 114 | } 115 | 116 | @Override 117 | public int getEffectiveMinorVersion() { 118 | return 0; 119 | } 120 | 121 | @Override 122 | public String getMimeType(String file) { 123 | return MimeTypeUtil.getMimeTypeByFileName(file); 124 | } 125 | 126 | @Override 127 | public Set getResourcePaths(String path) { 128 | Set thePaths = new HashSet<>(); 129 | if (!path.endsWith("/")) { 130 | path += "/"; 131 | } 132 | String basePath = getRealPath(path); 133 | if (basePath == null) { 134 | return thePaths; 135 | } 136 | File theBaseDir = new File(basePath); 137 | if (!theBaseDir.exists() || !theBaseDir.isDirectory()) { 138 | return thePaths; 139 | } 140 | String theFiles[] = theBaseDir.list(); 141 | if (theFiles == null) { 142 | return thePaths; 143 | } 144 | for (String filename : theFiles) { 145 | File testFile = new File(basePath + File.separator + filename); 146 | if (testFile.isFile()) 147 | thePaths.add(path + filename); 148 | else if (testFile.isDirectory()) 149 | thePaths.add(path + filename + "/"); 150 | } 151 | return thePaths; 152 | } 153 | 154 | @Override 155 | public URL getResource(String path) throws MalformedURLException { 156 | if (!path.startsWith("/")) 157 | throw new MalformedURLException("Path '" + path + "' does not start with '/'"); 158 | URL url = new URL(getClassLoader().getResource(""), path.substring(1)); 159 | try { 160 | url.openStream(); 161 | } catch(FileNotFoundException e){ 162 | url = new URL(getClassLoader().getResource(""), "static/" + path.substring(1)); 163 | try { 164 | url.openStream(); 165 | } catch (IOException e1) { 166 | log.warn("Resource not exist: " + path); 167 | url = null; 168 | } 169 | } catch (Throwable t) { 170 | log.error("Throwing exception when getting InputStream of " + path + " in /", t); 171 | url = null; 172 | } 173 | return url; 174 | } 175 | 176 | @Override 177 | public InputStream getResourceAsStream(String path) { 178 | try { 179 | URL url = getResource(path); 180 | if (url == null) return null; 181 | return url.openStream(); 182 | } catch (IOException e) { 183 | log.error(e.getMessage(), e); 184 | return null; 185 | } 186 | } 187 | 188 | @Override 189 | public RequestDispatcher getRequestDispatcher(String path) { 190 | String servletName = servletUrlPatternMapper.getServletNameByRequestURI(path); 191 | /*String servletName = servletMappings.get(path); 192 | if (servletName == null) { 193 | servletName = servletMappings.get("/"); 194 | }*/ 195 | Servlet servlet; 196 | try { 197 | servlet = null == servletName ? null : servlets.get(servletName).getServlet(true); 198 | if (servlet == null) { 199 | return null; 200 | } 201 | //TODO 过滤器的urlPatter解析 202 | List allNeedFilters = new ArrayList<>(); 203 | for (NettyFilterRegistration registration : this.filters.values()) { 204 | allNeedFilters.add(registration.getFilter()); 205 | } 206 | FilterChain filterChain = new NettyFilterChain(servlet, allNeedFilters); 207 | return new NettyRequestDispatcher( filterChain,path); 208 | } catch (ServletException e) { 209 | log.error("Throwing exception when getting Filter from NettyFilterRegistration of path " + path, e); 210 | return null; 211 | } 212 | } 213 | 214 | @Override 215 | public RequestDispatcher getNamedDispatcher(String name) { 216 | return null; 217 | } 218 | 219 | @Override 220 | public Servlet getServlet(String name) throws ServletException { 221 | return servlets.get(name).getServlet(true); 222 | } 223 | 224 | @Override 225 | public Enumeration getServlets() { 226 | return Collections.emptyEnumeration(); 227 | } 228 | 229 | @Override 230 | public Enumeration getServletNames() { 231 | return Collections.emptyEnumeration(); 232 | } 233 | 234 | @Override 235 | public void log(String msg) { 236 | log.info(msg); 237 | } 238 | 239 | @Override 240 | public void log(Exception exception, String msg) { 241 | log.error(msg, exception); 242 | } 243 | 244 | @Override 245 | public void log(String message, Throwable throwable) { 246 | log.error(message, throwable); 247 | } 248 | 249 | @Override 250 | public String getRealPath(String path) { 251 | if (!path.startsWith("/")) 252 | return null; 253 | try { 254 | URL url = getResource(path); 255 | if (url == null) return null; 256 | File f = new File(url.toURI()); 257 | return f.getAbsolutePath(); 258 | } catch (Throwable t) { 259 | log.error("Throwing exception when getting real path of " + path, t); 260 | return null; 261 | } 262 | } 263 | 264 | @Override 265 | public String getServerInfo() { 266 | return serverInfo; 267 | } 268 | 269 | @Override 270 | public String getInitParameter(String name) { 271 | return null; 272 | } 273 | 274 | @Override 275 | public Enumeration getInitParameterNames() { 276 | return Collections.emptyEnumeration(); 277 | } 278 | 279 | @Override 280 | public boolean setInitParameter(String name, String value) { 281 | return false; 282 | } 283 | 284 | @Override 285 | public Object getAttribute(String name) { 286 | return attributes.get(name); 287 | } 288 | 289 | @Override 290 | public Enumeration getAttributeNames() { 291 | return attributes.keys(); 292 | } 293 | 294 | @Override 295 | public void setAttribute(String name, Object object) { 296 | attributes.put(name, object); 297 | } 298 | 299 | @Override 300 | public void removeAttribute(String name) { 301 | attributes.remove(name); 302 | } 303 | 304 | @Override 305 | public String getServletContextName() { 306 | return getContextPath().toUpperCase(Locale.ENGLISH); 307 | } 308 | 309 | @Override 310 | public ServletRegistration.Dynamic addServlet(String servletName, String className) { 311 | return addServlet(servletName, className, null); 312 | } 313 | 314 | @Override 315 | public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) { 316 | return addServlet(servletName, servlet.getClass().getName(), servlet); 317 | } 318 | 319 | @Override 320 | public ServletRegistration.Dynamic addServlet(String servletName, Class servletClass) { 321 | return addServlet(servletName, servletClass.getName()); 322 | } 323 | 324 | private ServletRegistration.Dynamic addServlet(String servletName, String className, Servlet servlet) { 325 | NettyServletRegistration servletRegistration = new NettyServletRegistration(this, servletName, className, servlet); 326 | servlets.put(servletName, servletRegistration); 327 | return servletRegistration; 328 | } 329 | 330 | @Override 331 | public T createServlet(Class c) throws ServletException { 332 | try { 333 | return c.newInstance(); 334 | } catch (InstantiationException | IllegalAccessException e) { 335 | log.error("Throwing exception when creating instance of " + c.getName(), e); 336 | } 337 | return null; 338 | } 339 | 340 | @Override 341 | public ServletRegistration getServletRegistration(String servletName) { 342 | return null; 343 | } 344 | 345 | @Override 346 | public Map getServletRegistrations() { 347 | return null; 348 | } 349 | 350 | @Override 351 | public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, String className) { 352 | return addFilter(filterName, className, null); 353 | } 354 | 355 | @Override 356 | public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Filter filter) { 357 | return addFilter(filterName, filter.getClass().getName(), filter); 358 | } 359 | 360 | private javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, String className, Filter filter) { 361 | NettyFilterRegistration filterRegistration = new NettyFilterRegistration(this, filterName, className, filter); 362 | filters.put(filterName, filterRegistration); 363 | return filterRegistration; 364 | } 365 | 366 | @Override 367 | public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class filterClass) { 368 | return addFilter(filterName, filterClass.getName()); 369 | } 370 | 371 | @Override 372 | public T createFilter(Class c) throws ServletException { 373 | try { 374 | return c.newInstance(); 375 | } catch (InstantiationException | IllegalAccessException e) { 376 | e.printStackTrace(); 377 | return null; 378 | } 379 | } 380 | 381 | @Override 382 | public javax.servlet.FilterRegistration getFilterRegistration(String filterName) { 383 | return filters.get(filterName); 384 | } 385 | 386 | @Override 387 | public Map getFilterRegistrations() { 388 | return ImmutableMap.copyOf(filters); 389 | } 390 | 391 | @Override 392 | public SessionCookieConfig getSessionCookieConfig() { 393 | return null; 394 | } 395 | 396 | @Override 397 | public void setSessionTrackingModes(Set sessionTrackingModes) throws IllegalStateException, IllegalArgumentException { 398 | 399 | } 400 | 401 | @Override 402 | public Set getDefaultSessionTrackingModes() { 403 | return null; 404 | } 405 | 406 | @Override 407 | public Set getEffectiveSessionTrackingModes() { 408 | return null; 409 | } 410 | 411 | 412 | //TODO 暂不支持Listener,现在很少用了吧 413 | @Override 414 | public void addListener(String className) { 415 | 416 | } 417 | 418 | @Override 419 | public void addListener(T t) { 420 | 421 | } 422 | 423 | @Override 424 | public void addListener(Class listenerClass) { 425 | 426 | } 427 | 428 | @Override 429 | public T createListener(Class c) throws ServletException { 430 | return null; 431 | } 432 | 433 | @Override 434 | public void declareRoles(String... roleNames) { 435 | 436 | } 437 | 438 | @Override 439 | public String getVirtualServerName() { 440 | return null; 441 | } 442 | 443 | @Override 444 | public ClassLoader getClassLoader() { 445 | return classLoader; 446 | } 447 | 448 | @Override 449 | public JspConfigDescriptor getJspConfigDescriptor() { 450 | return null; 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/NettyFilterChain.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import javax.servlet.*; 4 | import java.io.IOException; 5 | import java.util.Iterator; 6 | 7 | import static com.google.common.base.Preconditions.checkNotNull; 8 | 9 | /** 10 | * 实现过滤器链 11 | */ 12 | public class NettyFilterChain implements FilterChain { 13 | /** 14 | * 考虑到每个请求只有一个线程处理,而且ServletContext在每次请求时都会new 一个SimpleFilterChain对象 15 | * 所以这里把过滤器链的Iterator作为FilterChain的私有变量,没有线程安全问题 16 | */ 17 | private final Iterator filterIterator; 18 | private final Servlet servlet; 19 | 20 | public NettyFilterChain(Servlet servlet, Iterable filters) throws ServletException { 21 | this.filterIterator = checkNotNull(filters).iterator(); 22 | this.servlet = checkNotNull(servlet); 23 | } 24 | 25 | /** 26 | * 每个Filter在处理完请求之后调用FilterChain的这个方法。 27 | * 这时候应该找到下一个Filter,调用其doFilter()方法。 28 | * 如果没有下一个了,应该调用servlet的service()方法了 29 | */ 30 | @Override 31 | public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { 32 | if (filterIterator.hasNext()) { 33 | Filter filter = filterIterator.next(); 34 | filter.doFilter(request, response, this); 35 | } else { 36 | servlet.service(request, response); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/NettyRequestDispatcher.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.request.NettyHttpServletRequest; 4 | 5 | import javax.servlet.*; 6 | import javax.servlet.http.HttpServletRequestWrapper; 7 | import java.io.IOException; 8 | 9 | /** 10 | * 分发器,除了传统的forward和include,把正常的Servlet调用也放在这里dispatch()方法 11 | */ 12 | public class NettyRequestDispatcher implements RequestDispatcher { 13 | private final FilterChain filterChain; 14 | private final String path; 15 | 16 | NettyRequestDispatcher(FilterChain filterChain, String path) { 17 | this.filterChain = filterChain; 18 | this.path = path; 19 | } 20 | 21 | @Override 22 | public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException { 23 | request.setAttribute(NettyHttpServletRequest.DISPATCHER_TYPE, DispatcherType.FORWARD); 24 | NettyHttpServletRequest servletRequest = (NettyHttpServletRequest) request; 25 | filterChain.doFilter(new HttpServletRequestWrapper(servletRequest) { 26 | @Override 27 | public String getRequestURI() { 28 | return path; 29 | } 30 | }, response); 31 | } 32 | 33 | @Override 34 | public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException { 35 | request.setAttribute(NettyHttpServletRequest.DISPATCHER_TYPE, DispatcherType.INCLUDE); 36 | // TODO implement 37 | } 38 | 39 | void dispatch(ServletRequest request, ServletResponse response) throws ServletException, IOException { 40 | request.setAttribute(NettyHttpServletRequest.DISPATCHER_TYPE, DispatcherType.REQUEST); 41 | filterChain.doFilter(request, response); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/RequestDispatcherHandler.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import org.apache.commons.logging.Log; 7 | import org.apache.commons.logging.LogFactory; 8 | 9 | import static com.google.common.base.Preconditions.checkNotNull; 10 | 11 | /** 12 | * 读入请求数据时,对请求URI获取分发器,找不到返回404错误. 13 | * 找到则调用FilterChain进行业务逻辑,最后关闭输出流 14 | */ 15 | @ChannelHandler.Sharable 16 | class RequestDispatcherHandler extends SimpleChannelInboundHandler { 17 | private final Log logger = LogFactory.getLog(getClass()); 18 | private final NettyContext context; 19 | 20 | RequestDispatcherHandler(NettyContext context) { 21 | this.context = checkNotNull(context); 22 | } 23 | 24 | @Override 25 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 26 | ctx.flush(); 27 | } 28 | 29 | @Override 30 | protected void channelRead0(ChannelHandlerContext ctx, RequestSession requestSession) throws Exception { 31 | String requestURI = requestSession.getServletRequest().getRequestURI(); 32 | try { 33 | NettyRequestDispatcher dispatcher = (NettyRequestDispatcher) context.getRequestDispatcher(requestURI); 34 | if (dispatcher == null) { 35 | requestSession.getServletResponse().sendError(404); 36 | return; 37 | } 38 | dispatcher.dispatch(requestSession.getServletRequest(), requestSession.getServletResponse()); 39 | } finally { 40 | if (!requestSession.getServletRequest().isAsyncStarted()) { 41 | requestSession.destroy(); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 48 | logger.error("Unexpected exception caught during request", cause); 49 | ctx.close(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/RequestSession.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.request.HttpRequestInputStream; 4 | import io.gitlab.leibnizhu.sbnetty.request.NettyHttpServletRequest; 5 | import io.gitlab.leibnizhu.sbnetty.response.NettyHttpServletResponse; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.http.HttpContent; 8 | import io.netty.handler.codec.http.HttpHeaderNames; 9 | import io.netty.handler.codec.http.HttpMethod; 10 | import io.netty.handler.codec.http.HttpRequest; 11 | import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; 12 | import io.netty.util.ReferenceCountUtil; 13 | 14 | import java.io.Closeable; 15 | import java.io.IOException; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | 19 | public class RequestSession { 20 | private final AtomicBoolean destroyed = new AtomicBoolean(false); 21 | 22 | private final HttpRequest nettyRequest; 23 | private final NettyHttpServletRequest servletRequest; 24 | private final NettyHttpServletResponse servletResponse; 25 | 26 | private final HttpRequestInputStream inputStream; 27 | private HttpPostRequestDecoder httpPostRequestDecoder; 28 | 29 | 30 | public NettyHttpServletRequest getServletRequest() { 31 | return servletRequest; 32 | } 33 | 34 | public NettyHttpServletResponse getServletResponse() { 35 | return servletResponse; 36 | } 37 | 38 | public RequestSession(ChannelHandlerContext ctx, HttpRequest request, NettyContext servletContext) { 39 | this.nettyRequest = request; 40 | this.inputStream = new HttpRequestInputStream(); 41 | 42 | if (request.method().equals(HttpMethod.POST)) { 43 | String contentType = String.valueOf(request.headers().get(HttpHeaderNames.CONTENT_TYPE)); 44 | if (HttpPostRequestDecoder.isMultipart(request) 45 | || contentType.toLowerCase().startsWith("application/x-www-form-urlencoded") 46 | ) { 47 | // 对于x-www-form-urlencoded和multipart,使用netty的解析器来处理 48 | httpPostRequestDecoder = new HttpPostRequestDecoder(request); 49 | } 50 | } 51 | this.servletRequest = new NettyHttpServletRequest(ctx, servletContext, request, httpPostRequestDecoder, inputStream); 52 | this.servletResponse = new NettyHttpServletResponse(ctx, servletContext, servletRequest); 53 | } 54 | 55 | 56 | public void destroy() { 57 | if (!destroyed.compareAndSet(false, true)) { 58 | return; 59 | } 60 | 61 | closeQuietly(inputStream); 62 | closeQuietly(servletResponse.getOutputStream()); 63 | 64 | if (httpPostRequestDecoder != null) { 65 | httpPostRequestDecoder.destroy(); 66 | httpPostRequestDecoder = null; 67 | } 68 | ReferenceCountUtil.release(nettyRequest); 69 | } 70 | 71 | public static void closeQuietly(final Closeable closeable) { 72 | if (closeable != null) { 73 | try { 74 | closeable.close(); 75 | } catch (final IOException ignore) { 76 | 77 | } 78 | } 79 | } 80 | 81 | public void offer(HttpContent msg) { 82 | if (destroyed.get()) { 83 | return; 84 | } 85 | 86 | if (httpPostRequestDecoder != null) { 87 | httpPostRequestDecoder.offer(msg); 88 | } else { 89 | inputStream.offer(msg); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/core/RequestSessionAggregator.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.core; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | import io.netty.handler.codec.http.*; 6 | import io.netty.util.ReferenceCountUtil; 7 | 8 | /** 9 | * 不使用HttpObjectAggregator,因为在处理文件上传时,HttpObjectAggregator将文件内容存储在内存中, 10 | * 在处理大文件上传时,会有内存溢出风险 11 | */ 12 | public class RequestSessionAggregator extends SimpleChannelInboundHandler { 13 | 14 | private RequestSession requestSession; 15 | private final NettyContext servletContext; 16 | 17 | public RequestSessionAggregator(NettyContext servletContext) { 18 | this.servletContext = servletContext; 19 | } 20 | 21 | @Override 22 | protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { 23 | if (msg instanceof HttpRequest) { 24 | HttpRequest request = ReferenceCountUtil.retain((HttpRequest) msg); 25 | requestSession = new RequestSession(ctx, request, servletContext); 26 | if (HttpUtil.is100ContinueExpected(request)) { //请求头包含Expect: 100-continue 27 | ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)); 28 | } 29 | } else if (msg instanceof HttpContent) { 30 | requestSession.offer((HttpContent) msg); 31 | if (msg instanceof LastHttpContent) { 32 | ctx.fireChannelRead(requestSession); 33 | requestSession = null; 34 | } 35 | } else { 36 | ctx.close(); 37 | } 38 | } 39 | 40 | @Override 41 | public void channelInactive(ChannelHandlerContext ctx) { 42 | if (requestSession != null) { 43 | requestSession.destroy(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/registration/AbstractNettyRegistration.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.registration; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 4 | 5 | import javax.servlet.FilterConfig; 6 | import javax.servlet.Registration; 7 | import javax.servlet.ServletConfig; 8 | import javax.servlet.ServletContext; 9 | import java.util.Collections; 10 | import java.util.Enumeration; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | import static com.google.common.base.Preconditions.checkNotNull; 15 | 16 | /** 17 | * 实现Filter和Servlet的动态注册的抽象类 18 | */ 19 | class AbstractNettyRegistration implements Registration, Registration.Dynamic, ServletConfig, FilterConfig { 20 | private final String name; 21 | private final String className; 22 | private final NettyContext context; 23 | protected boolean asyncSupported; 24 | 25 | protected AbstractNettyRegistration(String name, String className, NettyContext context) { 26 | this.name = checkNotNull(name); 27 | this.className = checkNotNull(className); 28 | this.context = checkNotNull(context); 29 | } 30 | 31 | @Override 32 | public void setAsyncSupported(boolean isAsyncSupported) { 33 | asyncSupported = isAsyncSupported; 34 | } 35 | 36 | @Override 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | @Override 42 | public String getClassName() { 43 | return className; 44 | } 45 | 46 | @Override 47 | public String getFilterName() { 48 | return name; 49 | } 50 | 51 | @Override 52 | public String getServletName() { 53 | return name; 54 | } 55 | 56 | @Override 57 | public ServletContext getServletContext() { 58 | return context; 59 | } 60 | 61 | NettyContext getNettyContext() { 62 | return context; 63 | } 64 | 65 | //Init Parameter相关的方法,因为没用到,暂时不实现 66 | 67 | @Override 68 | public boolean setInitParameter(String name, String value) { 69 | return false; 70 | } 71 | 72 | @Override 73 | public String getInitParameter(String name) { 74 | return null; 75 | } 76 | 77 | @Override 78 | public Enumeration getInitParameterNames() { 79 | return Collections.emptyEnumeration(); 80 | } 81 | 82 | @Override 83 | public Set setInitParameters(Map initParameters) { 84 | return Collections.emptySet(); 85 | } 86 | 87 | @Override 88 | public Map getInitParameters() { 89 | return Collections.emptyMap(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/registration/NettyFilterRegistration.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.registration; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 4 | 5 | import javax.servlet.DispatcherType; 6 | import javax.servlet.Filter; 7 | import javax.servlet.FilterRegistration; 8 | import javax.servlet.ServletException; 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | import java.util.EnumSet; 12 | import java.util.LinkedList; 13 | 14 | /** 15 | * Filter的注册器,一个Filter对应一个注册器 16 | */ 17 | public class NettyFilterRegistration extends AbstractNettyRegistration implements FilterRegistration.Dynamic { 18 | private volatile boolean initialised; 19 | private Filter filter; 20 | private Collection urlPatternMappings = new LinkedList<>(); 21 | 22 | public NettyFilterRegistration(NettyContext context, String filterName, String className, Filter filter) { 23 | super(filterName, className, context); 24 | this.filter = filter; 25 | } 26 | 27 | public Filter getFilter() throws ServletException { 28 | if (!initialised) { 29 | synchronized (this) { 30 | if (!initialised) { 31 | if (null == filter) { 32 | try { 33 | filter = (Filter) Class.forName(getClassName()).newInstance(); //反射获取实例 34 | } catch (Exception e) { 35 | throw new ServletException(e); 36 | } 37 | } 38 | filter.init(this); //初始化Filter 39 | initialised = true; 40 | } 41 | } 42 | } 43 | return filter; 44 | } 45 | 46 | @Override 47 | public void addMappingForUrlPatterns(EnumSet dispatcherTypes, boolean isMatchAfter, String... urlPatterns) { 48 | NettyContext context = getNettyContext(); 49 | for (String urlPattern : urlPatterns) { 50 | context.addFilterMapping(dispatcherTypes, isMatchAfter, urlPattern); 51 | } 52 | urlPatternMappings.addAll(Arrays.asList(urlPatterns)); 53 | } 54 | 55 | @Override 56 | public Collection getUrlPatternMappings() { 57 | return urlPatternMappings; 58 | } 59 | 60 | //Servlet相关的不管 61 | @Override 62 | public void addMappingForServletNames(EnumSet dispatcherTypes, boolean isMatchAfter, String... servletNames) { 63 | 64 | } 65 | 66 | @Override 67 | public Collection getServletNameMappings() { 68 | return null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/registration/NettyServletRegistration.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.registration; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.*; 8 | import java.util.*; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | /** 12 | * Servlet的注册器,一个Servlet对应一个注册器 13 | */ 14 | public class NettyServletRegistration extends AbstractNettyRegistration implements ServletRegistration.Dynamic { 15 | private final Logger log = LoggerFactory.getLogger(getClass()); 16 | private final AtomicBoolean initialised = new AtomicBoolean(false); 17 | private volatile Servlet servlet; 18 | private Collection urlPatternMappings = new LinkedList<>(); 19 | 20 | public NettyServletRegistration(NettyContext context, String servletName, String className, Servlet servlet) { 21 | super(servletName, className, context); 22 | this.servlet = servlet; 23 | } 24 | 25 | public Servlet getServlet(boolean ensureInitialized) throws ServletException { 26 | if (servlet == null) { 27 | synchronized (this) { 28 | if (servlet == null) { 29 | try { 30 | servlet = (Servlet) Class.forName(getClassName()).newInstance(); //反射获取实例 31 | } catch (Exception e) { 32 | throw new ServletException(e); 33 | } 34 | } 35 | } 36 | } 37 | if (ensureInitialized) { 38 | if (initialised.compareAndSet(false, true)) { 39 | servlet.init(this); //初始化Servlet 40 | } 41 | } 42 | return servlet; 43 | } 44 | 45 | @Override 46 | public void setLoadOnStartup(int loadOnStartup) { 47 | 48 | } 49 | 50 | @Override 51 | public Set setServletSecurity(ServletSecurityElement constraint) { 52 | return null; 53 | } 54 | 55 | @Override 56 | public void setMultipartConfig(MultipartConfigElement multipartConfig) { 57 | 58 | } 59 | 60 | @Override 61 | public void setRunAsRole(String roleName) { 62 | 63 | } 64 | 65 | @Override 66 | public String getRunAsRole() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public Set addMapping(String... urlPatterns) { 72 | //在RequestUrlPatternMapper中会检查url Pattern是否冲突 73 | NettyContext context = getNettyContext(); 74 | for (String urlPattern : urlPatterns) { 75 | try { 76 | // 这里获取servlet的时候,不能初始化,否则springboot刷新依赖遇到循环bean依赖时会失败 77 | context.addServletMapping(urlPattern, getName(), getServlet(false)); 78 | } catch (ServletException e) { 79 | log.error("Throwing exception when getting Servlet in NettyServletRegistration.", e); 80 | } 81 | } 82 | urlPatternMappings.addAll(Arrays.asList(urlPatterns)); 83 | return new HashSet<>(urlPatternMappings); 84 | } 85 | 86 | @Override 87 | public Collection getMappings() { 88 | return urlPatternMappings; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/request/HttpRequestInputStream.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.request; 2 | 3 | import com.google.common.primitives.Ints; 4 | import io.gitlab.leibnizhu.sbnetty.core.ClientAbortException; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.CompositeByteBuf; 7 | import io.netty.buffer.Unpooled; 8 | import io.netty.handler.codec.ByteToMessageDecoder; 9 | import io.netty.handler.codec.http.HttpContent; 10 | import io.netty.handler.codec.http.LastHttpContent; 11 | import io.netty.util.ReferenceCountUtil; 12 | 13 | import javax.servlet.ReadListener; 14 | import javax.servlet.ServletInputStream; 15 | import java.io.IOException; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | public class HttpRequestInputStream extends ServletInputStream { 19 | private final AtomicBoolean closed = new AtomicBoolean(false); 20 | private final Object lock = new Object(); 21 | 22 | private final ByteToMessageDecoder.Cumulator cumulator = ByteToMessageDecoder.COMPOSITE_CUMULATOR; 23 | private ByteBuf buf = Unpooled.EMPTY_BUFFER; 24 | 25 | 26 | private volatile boolean lastReached = false; 27 | 28 | private final HttpRequestInputStreamReadListenerOp httpRequestInputStreamReadListenerOp = new HttpRequestInputStreamReadListenerOp(); 29 | 30 | public void offer(HttpContent httpContent) { 31 | if (closed.get()) { 32 | return; 33 | } 34 | 35 | ByteBuf content = httpContent.content().retain(); 36 | 37 | buf = cumulator.cumulate(content.alloc(), buf, content); 38 | if (httpContent instanceof LastHttpContent) { 39 | lastReached = true; 40 | } 41 | httpRequestInputStreamReadListenerOp.notifyDataAvailable(); 42 | } 43 | 44 | 45 | @Override 46 | public boolean isFinished() { 47 | if (closed.get()) { 48 | return true; 49 | } 50 | return lastReached && buf.readableBytes() == 0; 51 | } 52 | 53 | 54 | /** 55 | * 已读入至少一次HttpContent且未读取完所有内容,或者HttpContent队列非空 56 | */ 57 | @Override 58 | public boolean isReady() { 59 | if (closed.get()) { 60 | return false; 61 | } 62 | return buf.readableBytes() > 0; 63 | } 64 | 65 | @Override 66 | public void setReadListener(ReadListener readListener) { 67 | httpRequestInputStreamReadListenerOp.setReadListener(readListener); 68 | } 69 | 70 | /** 71 | * 跳过n个字节 72 | */ 73 | @Override 74 | public long skip(long n) throws IOException { 75 | checkNotClosed(); 76 | synchronized (lock) { 77 | long realSkip = 0; 78 | while (realSkip < n && buf.readableBytes() > 0) { 79 | long skipLen = Math.min(buf.readableBytes(), n - realSkip); 80 | buf.skipBytes(Ints.checkedCast(skipLen)); 81 | realSkip += skipLen; 82 | } 83 | return realSkip; 84 | } 85 | } 86 | 87 | 88 | @Override 89 | public int available() throws IOException { 90 | return buf.readableBytes(); 91 | } 92 | 93 | @Override 94 | public void close() throws IOException { 95 | if (closed.compareAndSet(false, true)) { 96 | synchronized (lock) { 97 | // close和read不能同时发生 98 | ReferenceCountUtil.release(buf); 99 | } 100 | } 101 | } 102 | 103 | @Override 104 | public int read(byte[] b, int off, int len) throws IOException { 105 | if (isFinished()) { 106 | return -1; 107 | } 108 | synchronized (lock) { 109 | try { 110 | if (isFinished()) { 111 | return -1; 112 | } 113 | int realLen = Math.min(buf.readableBytes(), len); 114 | buf.readBytes(b, off, realLen); 115 | if (buf instanceof CompositeByteBuf) { 116 | // discard read bytes with batch read model 117 | ((CompositeByteBuf) buf).discardSomeReadBytes(); 118 | } 119 | return realLen; 120 | } finally { 121 | if (isFinished()) { 122 | httpRequestInputStreamReadListenerOp.notifyAllRead(); 123 | } 124 | } 125 | } 126 | } 127 | 128 | @Override 129 | public int read() throws IOException { 130 | if (isFinished()) { 131 | return -1; 132 | } 133 | synchronized (lock) { 134 | try { 135 | if (isFinished()) { 136 | return -1; 137 | } 138 | return buf.readByte() & 0xFF; 139 | } finally { 140 | 141 | if (isFinished()) { 142 | httpRequestInputStreamReadListenerOp.notifyAllRead(); 143 | } 144 | } 145 | } 146 | } 147 | 148 | 149 | private void checkNotClosed() throws IOException { 150 | if (closed.get()) { 151 | throw new ClientAbortException("Stream is closed"); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/request/HttpRequestInputStreamReadListenerOp.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.request; 2 | 3 | import javax.servlet.ReadListener; 4 | 5 | import static com.google.common.base.Preconditions.checkNotNull; 6 | 7 | public class HttpRequestInputStreamReadListenerOp { 8 | private ReadListener readListener; 9 | private volatile boolean dataAvailableCalled; 10 | 11 | public void setReadListener(ReadListener readListener) { 12 | checkNotNull(readListener); 13 | if (this.readListener != null) { 14 | throw new IllegalStateException("ReadListener is already set"); 15 | } 16 | this.readListener = readListener; 17 | } 18 | 19 | void notifyDataAvailable() { 20 | if (dataAvailableCalled) { 21 | return; 22 | } 23 | dataAvailableCalled = true; 24 | if (readListener == null) { 25 | return; 26 | } 27 | try { 28 | readListener.onDataAvailable(); 29 | } catch (Throwable t) { 30 | readListener.onError(t); 31 | } 32 | } 33 | 34 | void notifyAllRead() { 35 | if (readListener == null) { 36 | return; 37 | } 38 | try { 39 | readListener.onAllDataRead(); 40 | } catch (Throwable t) { 41 | readListener.onError(t); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/request/NettyHttpServletRequest.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.request; 2 | 3 | import com.google.common.base.Splitter; 4 | import com.google.common.base.Supplier; 5 | import com.google.common.base.Suppliers; 6 | import io.gitlab.leibnizhu.sbnetty.core.NettyAsyncContext; 7 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 8 | import io.gitlab.leibnizhu.sbnetty.core.NettyRequestDispatcher; 9 | import io.gitlab.leibnizhu.sbnetty.session.NettyHttpSession; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.handler.codec.http.*; 12 | import io.netty.handler.codec.http.cookie.ServerCookieDecoder; 13 | import io.netty.handler.codec.http.multipart.Attribute; 14 | import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; 15 | import io.netty.handler.codec.http.multipart.InterfaceHttpData; 16 | import io.netty.util.NetUtil; 17 | 18 | import javax.servlet.*; 19 | import javax.servlet.http.Cookie; 20 | import javax.servlet.http.*; 21 | import java.io.BufferedReader; 22 | import java.io.IOException; 23 | import java.io.InputStreamReader; 24 | import java.net.InetSocketAddress; 25 | import java.nio.charset.Charset; 26 | import java.nio.charset.StandardCharsets; 27 | import java.security.Principal; 28 | import java.util.*; 29 | import java.util.concurrent.ConcurrentHashMap; 30 | import java.util.stream.Collectors; 31 | 32 | /** 33 | * @author Leibniz 34 | */ 35 | public class NettyHttpServletRequest implements HttpServletRequest { 36 | public static final String DISPATCHER_TYPE = NettyRequestDispatcher.class.getName() + ".DISPATCHER_TYPE"; 37 | 38 | private final ChannelHandlerContext ctx; 39 | private final NettyContext servletContext; 40 | private final HttpRequest request; 41 | private final HttpRequestInputStream inputStream; 42 | private final HttpPostRequestDecoder httpPostRequestDecoder; 43 | private boolean asyncSupported = true; 44 | private NettyAsyncContext asyncContext; 45 | 46 | public NettyHttpServletRequest(ChannelHandlerContext ctx, NettyContext servletContext, 47 | HttpRequest request, HttpPostRequestDecoder httpPostRequestDecoder, 48 | HttpRequestInputStream requestInputStream) { 49 | this.ctx = ctx; 50 | this.servletContext = servletContext; 51 | this.request = request; 52 | this.inputStream = requestInputStream; 53 | this.httpPostRequestDecoder = httpPostRequestDecoder; 54 | this.attributes = new ConcurrentHashMap<>(); 55 | this.headers = request.headers(); 56 | parseSession(); 57 | } 58 | 59 | public boolean isKeepAlive() { 60 | return HttpUtil.isKeepAlive(request); 61 | } 62 | 63 | /*====== Cookie 相关方法 开始 ======*/ 64 | private Cookie[] cookies; 65 | private transient boolean isCookieParsed = false; 66 | 67 | @Override 68 | public Cookie[] getCookies() { 69 | if (!isCookieParsed) { 70 | parseCookie(); 71 | } 72 | return cookies; 73 | } 74 | 75 | /** 76 | * 解析request中的Cookie到本类的cookies数组中 77 | * 78 | * @author Leibniz 79 | */ 80 | private void parseCookie() { 81 | if (isCookieParsed) { 82 | return; 83 | } 84 | 85 | String cookieOriginStr = this.headers.get("Cookie"); 86 | if (cookieOriginStr == null) { 87 | return; 88 | } 89 | Set nettyCookies = ServerCookieDecoder.LAX.decode(cookieOriginStr); 90 | if (nettyCookies.isEmpty()) { 91 | return; 92 | } 93 | this.cookies = new Cookie[nettyCookies.size()]; 94 | Iterator itr = nettyCookies.iterator(); 95 | int i = 0; 96 | while (itr.hasNext()) { 97 | io.netty.handler.codec.http.cookie.Cookie nettyCookie = itr.next(); 98 | Cookie servletCookie = new Cookie(nettyCookie.name(), nettyCookie.value()); 99 | // servletCookie.setMaxAge(Ints.checkedCast(nettyCookie.maxAge())); 100 | if (nettyCookie.domain() != null) servletCookie.setDomain(nettyCookie.domain()); 101 | if (nettyCookie.path() != null) servletCookie.setPath(nettyCookie.path()); 102 | servletCookie.setHttpOnly(nettyCookie.isHttpOnly()); 103 | this.cookies[i++] = servletCookie; 104 | } 105 | 106 | this.isCookieParsed = true; 107 | } 108 | 109 | /*====== Cookie 相关方法 结束 ======*/ 110 | 111 | 112 | /*====== Header 相关方法 开始 ======*/ 113 | private final HttpHeaders headers; 114 | 115 | @Override 116 | public long getDateHeader(String name) { 117 | String value = headers.get(name); 118 | try { 119 | return Long.parseLong(value); 120 | } catch (NumberFormatException e) { 121 | try { 122 | return headers.getTimeMillis(name, -1L); 123 | } catch (Exception e2) { 124 | return -1L; 125 | } 126 | } 127 | } 128 | 129 | @Override 130 | public String getHeader(String name) { 131 | return this.headers.get(name); 132 | } 133 | 134 | @Override 135 | public Enumeration getHeaders(String name) { 136 | return Collections.enumeration(this.headers.getAll(name)); 137 | } 138 | 139 | @Override 140 | public Enumeration getHeaderNames() { 141 | return Collections.enumeration(this.headers.names()); 142 | } 143 | 144 | @Override 145 | public int getIntHeader(String name) { 146 | String headerStringValue = this.headers.get(name); 147 | if (headerStringValue == null) { 148 | return -1; 149 | } 150 | return Integer.parseInt(headerStringValue); 151 | } 152 | /*====== Header 相关方法 结束 ======*/ 153 | 154 | 155 | /*====== 各种路径 相关方法 开始 ======*/ 156 | private String servletPath; 157 | private String queryString; 158 | private String pathInfo; 159 | private String requestUri; 160 | private transient boolean isPathsParsed = false; 161 | 162 | private void checkAndParsePaths() { 163 | if (isPathsParsed) { 164 | return; 165 | } 166 | if (request.uri().startsWith(servletContext.getContextPath())) { 167 | String servletPath = request.uri().substring(servletContext.getContextPath().length()); 168 | if (!servletPath.startsWith("/")) { 169 | servletPath = "/" + servletPath; 170 | } 171 | int queryInx = servletPath.indexOf('?'); 172 | if (queryInx > -1) { 173 | this.queryString = servletPath.substring(queryInx + 1); 174 | servletPath = servletPath.substring(0, queryInx); 175 | } 176 | this.servletPath = servletPath; 177 | this.requestUri = this.servletContext.getContextPath() + servletPath; //TODO 加上pathInfo 178 | } else { 179 | this.servletPath = ""; 180 | this.requestUri = ""; 181 | } 182 | this.pathInfo = null; 183 | 184 | isPathsParsed = true; 185 | } 186 | 187 | @Override 188 | public String getMethod() { 189 | return request.method().name(); 190 | } 191 | 192 | //TODO ServletPath和PathInfo应该是互补的,根据URL-Pattern匹配的路径不同而不同 193 | // 现在把PathInfo恒为null,ServletPath恒为uri-contextPath 194 | // 可以满足SpringBoot的需求,但不满足ServletPath和PathInfo的语义 195 | // 需要在RequestUrlPatternMapper匹配的时候设置,new NettyRequestDispatcher的时候传入MapperData 196 | @Override 197 | public String getPathInfo() { 198 | checkAndParsePaths(); 199 | return this.pathInfo; 200 | } 201 | 202 | @Override 203 | public String getQueryString() { 204 | checkAndParsePaths(); 205 | return this.queryString; 206 | } 207 | 208 | @Override 209 | public String getRequestURI() { 210 | checkAndParsePaths(); 211 | return this.requestUri; 212 | } 213 | 214 | @Override 215 | public String getServletPath() { 216 | checkAndParsePaths(); 217 | return this.servletPath; 218 | } 219 | 220 | @Override 221 | public String getContextPath() { 222 | return servletContext.getContextPath(); 223 | } 224 | 225 | @Override 226 | public StringBuffer getRequestURL() { 227 | checkAndParsePaths(); 228 | StringBuffer url = new StringBuffer(); 229 | url.append("http:").append("//") 230 | .append(request.headers().get(HttpHeaderNames.HOST)) 231 | .append(getRequestURI()); 232 | return url; 233 | } 234 | 235 | @Override 236 | @Deprecated 237 | public String getRealPath(String path) { 238 | return null; 239 | } 240 | /*====== 各种路径 相关方法 结束 ======*/ 241 | 242 | 243 | /*====== Session 相关方法 开始 ======*/ 244 | private NettyHttpSession session; 245 | private boolean isCookieSession; 246 | private boolean isURLSession; 247 | 248 | /** 249 | * 先后看请求路径和Cookie中是否有sessionid 250 | * 有,则从SessionManager获取session对象放入session属性 251 | * 如果session对象过期,则创建一个新的并放入 252 | * 无,则创建一个新Session并放入 253 | */ 254 | private void parseSession() { 255 | String sessionId; 256 | NettyHttpSession curSession; 257 | 258 | //从Cookie解析SessionID 259 | sessionId = getSessionIdFromCookie(); 260 | if (sessionId != null) { 261 | curSession = servletContext.getSessionManager().getSession(sessionId); 262 | if (null != curSession) { 263 | this.isCookieSession = true; 264 | recoverySession(curSession); 265 | return; 266 | } 267 | } 268 | 269 | if (!this.isCookieSession) { 270 | // 从请求路径解析SessionID 271 | sessionId = getSessionIdFromUrl(); 272 | curSession = servletContext.getSessionManager().getSession(sessionId); 273 | if (null != curSession) { 274 | this.isURLSession = true; 275 | recoverySession(curSession); 276 | } 277 | } 278 | } 279 | 280 | /** 281 | * @return 从URL解析到的SessionID 282 | */ 283 | private String getSessionIdFromUrl() { 284 | StringBuilder u = new StringBuilder(request.uri()); 285 | int sessionStart = u.toString().indexOf(";" + NettyHttpSession.SESSION_REQUEST_PARAMETER_NAME + "="); 286 | if (sessionStart == -1) { 287 | return null; 288 | } 289 | int sessionEnd = u.toString().indexOf(';', sessionStart + 1); 290 | if (sessionEnd == -1) 291 | sessionEnd = u.toString().indexOf('?', sessionStart + 1); 292 | if (sessionEnd == -1) // still 293 | sessionEnd = u.length(); 294 | return u.substring(sessionStart + NettyHttpSession.SESSION_REQUEST_PARAMETER_NAME.length() + 2, sessionEnd); 295 | } 296 | 297 | /** 298 | * @return 从Cookie解析到的SessionID 299 | */ 300 | private String getSessionIdFromCookie() { 301 | Cookie[] cookies = getCookies(); 302 | if (cookies == null) { 303 | return null; 304 | } 305 | for (Cookie cookie : cookies) { 306 | if (cookie.getName().equals(NettyHttpSession.SESSION_COOKIE_NAME)) { 307 | return cookie.getValue(); 308 | } 309 | } 310 | return null; 311 | } 312 | 313 | /** 314 | * 恢复旧Session 315 | * 316 | * @param curSession 要恢复的Session对象 317 | */ 318 | private void recoverySession(NettyHttpSession curSession) { 319 | this.session = curSession; 320 | this.session.setNew(false); 321 | this.servletContext.getSessionManager().updateAccessTime(this.session); 322 | } 323 | 324 | @Override 325 | public HttpSession getSession(boolean create) { 326 | boolean valid = isRequestedSessionIdValid(); //在管理器存在,且没到期 327 | //可用则直接返回 328 | if (valid) { 329 | return session.getSession(); 330 | } 331 | //不可用则判断是否新建 332 | if (!create) { 333 | session = null; //如果过期了设为null 334 | return null; 335 | } 336 | //不可用且允许新建则新建之 337 | this.session = createtSession(); 338 | return this.session.getSession(); 339 | } 340 | 341 | @Override 342 | public HttpSession getSession() { 343 | return getSession(true); 344 | } 345 | 346 | @Override 347 | public String changeSessionId() { 348 | this.session = createtSession(); 349 | return this.session.getId(); 350 | } 351 | 352 | private NettyHttpSession createtSession() { 353 | return servletContext.getSessionManager().createSession(); 354 | } 355 | 356 | @Override 357 | public boolean isRequestedSessionIdValid() { 358 | return servletContext.getSessionManager().checkValid(session); 359 | } 360 | 361 | @Override 362 | public boolean isRequestedSessionIdFromCookie() { 363 | return isCookieSession; 364 | } 365 | 366 | @Override 367 | public boolean isRequestedSessionIdFromURL() { 368 | return isURLSession; 369 | } 370 | 371 | @Override 372 | @Deprecated 373 | public boolean isRequestedSessionIdFromUrl() { 374 | return isRequestedSessionIdFromURL(); 375 | } 376 | 377 | @Override 378 | public String getRequestedSessionId() { 379 | return session.getId(); 380 | } 381 | /*====== Session 相关方法 结束 ======*/ 382 | 383 | 384 | 385 | /*====== Request Parameters 相关方法 开始 ======*/ 386 | 387 | private transient boolean isParameterParsed = false; //请求参数是否已经解析 388 | private final Map paramMap = new HashMap<>(); //存储请求参数 389 | 390 | 391 | private void fillRequestParams() { 392 | if (isParameterParsed) { 393 | return; 394 | } 395 | Map> multiMap = new HashMap<>(); 396 | QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri(), characterEncodingModel); 397 | Map> params = queryStringDecoder.parameters(); 398 | for (Map.Entry> entry : params.entrySet()) { 399 | List valueList = entry.getValue(); 400 | multiMap.put(entry.getKey(), valueList); 401 | } 402 | if (httpPostRequestDecoder != null) { 403 | for (InterfaceHttpData bodyHttpData : httpPostRequestDecoder.getBodyHttpDatas()) { 404 | if (bodyHttpData instanceof Attribute) { 405 | Attribute attribute = (Attribute) bodyHttpData; 406 | try { 407 | String value = attribute.getValue(); 408 | String name = attribute.getName(); 409 | multiMap.computeIfAbsent(name, k -> new ArrayList<>()).add(value); 410 | } catch (IOException e) { 411 | throw new RuntimeException(e); 412 | } 413 | } 414 | } 415 | } 416 | multiMap.forEach((s, strings) -> paramMap.put(s, strings.toArray(new String[0]))); 417 | this.isParameterParsed = true; 418 | } 419 | 420 | @Override 421 | public String getParameter(String name) { 422 | fillRequestParams(); 423 | String[] values = paramMap.get(name); 424 | if (values != null && values.length > 0) { 425 | return values[0]; 426 | } 427 | return null; 428 | } 429 | 430 | @Override 431 | public Enumeration getParameterNames() { 432 | fillRequestParams(); 433 | return Collections.enumeration(paramMap.keySet()); 434 | } 435 | 436 | @Override 437 | public String[] getParameterValues(String name) { 438 | fillRequestParams(); 439 | return paramMap.get(name); 440 | } 441 | 442 | @Override 443 | public Map getParameterMap() { 444 | fillRequestParams(); 445 | return paramMap; 446 | } 447 | 448 | /*====== Request Parameters 相关方法 结束 ======*/ 449 | 450 | 451 | /*====== 请求协议、地址、端口 相关方法 开始 ======*/ 452 | @Override 453 | public String getProtocol() { 454 | return request.protocolVersion().toString(); 455 | } 456 | 457 | @Override 458 | public String getScheme() { 459 | return request.protocolVersion().protocolName(); 460 | } 461 | 462 | private static final Splitter splitter = Splitter.on(':').omitEmptyStrings().trimResults(); 463 | 464 | private static class HostAndPort { 465 | private String host; 466 | private int port; 467 | 468 | static HostAndPort of(String host, int port) { 469 | HostAndPort hostAndPort = new HostAndPort(); 470 | hostAndPort.host = host; 471 | hostAndPort.port = port; 472 | return hostAndPort; 473 | } 474 | 475 | static HostAndPort of(String host) { 476 | return of(host, 80); 477 | } 478 | } 479 | 480 | private final java.util.function.Supplier hostAndPort = 481 | Suppliers.memoize(new Supplier() { 482 | @Override 483 | public HostAndPort get() { 484 | String host = request.headers().get(HttpHeaderNames.HOST); 485 | if (host == null) { 486 | InetSocketAddress addr = (InetSocketAddress) ctx.channel().localAddress(); 487 | return HostAndPort.of(NetUtil.getHostname(addr), addr.getPort()); 488 | } 489 | List strings = splitter.splitToList(host); 490 | return strings.size() == 1 ? 491 | HostAndPort.of(host) : 492 | HostAndPort.of(strings.get(0), Integer.parseInt(strings.get(1))); 493 | } 494 | }); 495 | 496 | 497 | @Override 498 | public String getServerName() { 499 | return hostAndPort.get().host; 500 | } 501 | 502 | @Override 503 | public int getServerPort() { 504 | return hostAndPort.get().port; 505 | } 506 | 507 | @Override 508 | public String getLocalName() { 509 | return "localhost"; 510 | } 511 | 512 | @Override 513 | public String getLocalAddr() { 514 | return "127.0.0.1"; 515 | } 516 | 517 | @Override 518 | public int getLocalPort() { 519 | return 0; 520 | } 521 | 522 | @Override 523 | public String getRemoteAddr() { 524 | return ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress(); 525 | } 526 | 527 | @Override 528 | public String getRemoteHost() { 529 | return ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName(); 530 | } 531 | 532 | @Override 533 | public int getRemotePort() { 534 | return ((InetSocketAddress) ctx.channel().remoteAddress()).getPort(); 535 | } 536 | 537 | /*====== 请求协议、地址、端口 相关方法 结束 ======*/ 538 | 539 | 540 | /*====== Request Attributes 相关方法 开始 ======*/ 541 | private final Map attributes; 542 | 543 | @Override 544 | public Object getAttribute(String name) { 545 | return attributes.get(name); 546 | } 547 | 548 | @Override 549 | public Enumeration getAttributeNames() { 550 | return Collections.enumeration(attributes.keySet()); 551 | } 552 | 553 | @Override 554 | public void setAttribute(String name, Object o) { 555 | attributes.put(name, o); 556 | } 557 | 558 | @Override 559 | public void removeAttribute(String name) { 560 | attributes.remove(name); 561 | } 562 | 563 | @Override 564 | public DispatcherType getDispatcherType() { 565 | return attributes.containsKey(DISPATCHER_TYPE) ? (DispatcherType) attributes.get(DISPATCHER_TYPE) : DispatcherType.REQUEST; 566 | } 567 | 568 | /*====== Request Attributes 相关方法 结束 ======*/ 569 | 570 | 571 | 572 | /*====== 异步 相关方法 开始 ======*/ 573 | 574 | @Override 575 | public AsyncContext startAsync() { 576 | return startAsync(this, null); 577 | } 578 | 579 | @Override 580 | public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { 581 | return ((NettyAsyncContext) getAsyncContext()).startAsync(servletRequest, servletResponse); 582 | } 583 | 584 | @Override 585 | public boolean isAsyncStarted() { 586 | return null != asyncContext && asyncContext.isAsyncStarted(); 587 | } 588 | 589 | @SuppressWarnings("unused") 590 | void setAsyncSupported(boolean asyncSupported) { 591 | this.asyncSupported = asyncSupported; 592 | } 593 | 594 | @Override 595 | public boolean isAsyncSupported() { 596 | return asyncSupported; 597 | } 598 | 599 | @Override 600 | public AsyncContext getAsyncContext() { 601 | if (null == asyncContext) { 602 | asyncContext = new NettyAsyncContext(this, ctx); 603 | } 604 | return asyncContext; 605 | } 606 | 607 | /*====== 异步 相关方法 结束 ======*/ 608 | 609 | /*====== multipart/form-data 相关方法 开始 ======*/ 610 | @Override 611 | public Collection getParts() throws IOException, IllegalStateException, ServletException { 612 | if (httpPostRequestDecoder == null || !httpPostRequestDecoder.isMultipart()) { 613 | return null; 614 | } 615 | return httpPostRequestDecoder.getBodyHttpDatas().stream() 616 | .map(NettyPart::of).filter(Objects::nonNull) 617 | .collect(Collectors.toList()); 618 | } 619 | 620 | @Override 621 | public Part getPart(String name) throws IOException, IllegalStateException, ServletException { 622 | if (httpPostRequestDecoder == null || !httpPostRequestDecoder.isMultipart()) { 623 | return null; 624 | } 625 | return Optional.ofNullable(httpPostRequestDecoder.getBodyHttpData(name)) 626 | .map(NettyPart::of) 627 | .orElse(null); 628 | } 629 | 630 | /*====== multipart/form-data 相关方法 结束 ======*/ 631 | 632 | @Override 633 | public boolean isSecure() { 634 | return getScheme().equalsIgnoreCase("HTTPS"); 635 | } 636 | 637 | @Override 638 | public ServletContext getServletContext() { 639 | return servletContext; 640 | } 641 | 642 | 643 | @Override 644 | public ServletInputStream getInputStream() { 645 | return inputStream; 646 | } 647 | 648 | @Override 649 | public BufferedReader getReader() throws IOException { 650 | return new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding())); 651 | } 652 | 653 | @Override 654 | public int getContentLength() { 655 | return request.headers().getInt(HttpHeaderNames.CONTENT_LENGTH, -1); 656 | } 657 | 658 | @Override 659 | public long getContentLengthLong() { 660 | return getContentLength(); 661 | } 662 | 663 | private String characterEncoding; 664 | 665 | private Charset characterEncodingModel = StandardCharsets.UTF_8; 666 | 667 | @Override 668 | public String getCharacterEncoding() { 669 | if (characterEncoding == null) { 670 | characterEncoding = parseCharacterEncoding(); 671 | } 672 | 673 | characterEncodingModel = Charset.forName(characterEncoding); 674 | 675 | return characterEncoding; 676 | } 677 | 678 | @Override 679 | public void setCharacterEncoding(String env) { 680 | characterEncoding = env; 681 | } 682 | 683 | @Override 684 | public String getContentType() { 685 | return headers.get("content-type"); 686 | } 687 | 688 | private static final String DEFAULT_CHARSET = "UTF-8"; 689 | 690 | private String parseCharacterEncoding() { 691 | String contentType = getContentType(); 692 | if (contentType == null) { 693 | return DEFAULT_CHARSET; 694 | } 695 | int start = contentType.indexOf("charset="); 696 | if (start < 0) { 697 | return DEFAULT_CHARSET; 698 | } 699 | String encoding = contentType.substring(start + 8); 700 | int end = encoding.indexOf(';'); 701 | if (end >= 0) { 702 | encoding = encoding.substring(0, end); 703 | } 704 | encoding = encoding.trim(); 705 | if ((encoding.length() > 2) && (encoding.startsWith("\"")) 706 | && (encoding.endsWith("\""))) { 707 | encoding = encoding.substring(1, encoding.length() - 1); 708 | } 709 | return encoding.trim(); 710 | } 711 | 712 | 713 | /*====== 以下是暂不处理的接口方法 ======*/ 714 | 715 | @Override 716 | public Locale getLocale() { 717 | return null; 718 | } 719 | 720 | @Override 721 | public Enumeration getLocales() { 722 | return null; 723 | } 724 | 725 | @Override 726 | public RequestDispatcher getRequestDispatcher(String path) { 727 | if (path == null) { 728 | return null; 729 | } 730 | 731 | int fragmentPos = path.indexOf('#'); 732 | if (fragmentPos > -1) { 733 | path = path.substring(0, fragmentPos); 734 | } 735 | 736 | // If the path is already context-relative, just pass it through 737 | if (path.startsWith("/")) { 738 | return servletContext.getRequestDispatcher(path); 739 | } 740 | String pathInfo = getPathInfo(); 741 | String requestPath = null; 742 | 743 | if (pathInfo == null) { 744 | requestPath = servletPath; 745 | } else { 746 | requestPath = servletPath + pathInfo; 747 | } 748 | 749 | int pos = requestPath.lastIndexOf('/'); 750 | String relative = null; 751 | 752 | if (pos >= 0) { 753 | relative = requestPath.substring(0, pos + 1) + path; 754 | } else { 755 | relative = requestPath + path; 756 | } 757 | return servletContext.getRequestDispatcher(relative); 758 | } 759 | 760 | @Override 761 | public String getAuthType() { 762 | return null; 763 | } 764 | 765 | @Override 766 | public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { 767 | return false; 768 | } 769 | 770 | @Override 771 | public void login(String username, String password) throws ServletException { 772 | } 773 | 774 | @Override 775 | public void logout() throws ServletException { 776 | } 777 | 778 | @Override 779 | public T upgrade(Class handlerClass) throws IOException, ServletException { 780 | return null; 781 | } 782 | 783 | @Override 784 | public String getPathTranslated() { 785 | return null; 786 | } 787 | 788 | @Override 789 | public boolean isUserInRole(String role) { 790 | return false; 791 | } 792 | 793 | @Override 794 | public Principal getUserPrincipal() { 795 | return null; 796 | } 797 | 798 | @Override 799 | public String getRemoteUser() { 800 | return null; 801 | } 802 | } 803 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/request/NettyPart.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.request; 2 | 3 | import io.netty.handler.codec.http.DefaultHttpHeaders; 4 | import io.netty.handler.codec.http.HttpHeaderNames; 5 | import io.netty.handler.codec.http.HttpHeaderValues; 6 | import io.netty.handler.codec.http.HttpHeaders; 7 | import io.netty.handler.codec.http.multipart.FileUpload; 8 | import io.netty.handler.codec.http.multipart.HttpData; 9 | import io.netty.handler.codec.http.multipart.InterfaceHttpData; 10 | 11 | import javax.servlet.http.Part; 12 | import java.io.ByteArrayInputStream; 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.nio.file.Files; 17 | import java.util.Collection; 18 | 19 | public class NettyPart implements Part { 20 | private final HttpData httpData; 21 | private final HttpHeaders httpHeaders = new DefaultHttpHeaders(); 22 | 23 | private NettyPart(HttpData httpData) { 24 | this.httpData = httpData; 25 | 26 | if (httpData instanceof FileUpload) { 27 | FileUpload fileUpload = (FileUpload) httpData; 28 | httpHeaders.set(HttpHeaderNames.CONTENT_DISPOSITION, HttpHeaderValues.FORM_DATA + "; " + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + 29 | "\"; " + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\""); 30 | httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, fileUpload.getContentType() + 31 | (fileUpload.getCharset() != null ? "; " + HttpHeaderValues.CHARSET + '=' + fileUpload.getCharset().name() : "")); 32 | httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, fileUpload.length()); 33 | httpHeaders.set("Completed", fileUpload.isCompleted()); 34 | httpHeaders.set("IsInMemory", fileUpload.isInMemory()); 35 | } else { 36 | httpHeaders.set(HttpHeaderNames.CONTENT_DISPOSITION, HttpHeaderValues.FORM_DATA + "; " + HttpHeaderValues.NAME + "=\"" + httpData.getName() + "\""); 37 | } 38 | } 39 | 40 | public static NettyPart of(InterfaceHttpData httpData) { 41 | if (!(httpData instanceof HttpData)) { 42 | return null; 43 | } 44 | return new NettyPart((HttpData) httpData); 45 | } 46 | 47 | @Override 48 | public InputStream getInputStream() throws IOException { 49 | if (httpData.isInMemory()) { 50 | return new ByteArrayInputStream(httpData.get()); 51 | } 52 | File file = httpData.getFile(); 53 | return Files.newInputStream(file.toPath()); 54 | } 55 | 56 | @Override 57 | public String getContentType() { 58 | if (httpData instanceof FileUpload) { 59 | return ((FileUpload) httpData).getContentType(); 60 | } 61 | return null; 62 | } 63 | 64 | @Override 65 | public String getName() { 66 | return httpData.getName(); 67 | } 68 | 69 | @Override 70 | public String getSubmittedFileName() { 71 | if (httpData instanceof FileUpload) { 72 | return ((FileUpload) httpData).getFilename(); 73 | } 74 | return null; 75 | } 76 | 77 | @Override 78 | public long getSize() { 79 | return httpData.length(); 80 | } 81 | 82 | @Override 83 | public void write(String fileName) throws IOException { 84 | if (httpData instanceof FileUpload) { 85 | httpData.renameTo(new File(fileName)); 86 | } 87 | } 88 | 89 | @Override 90 | public void delete() throws IOException { 91 | if (httpData instanceof FileUpload) { 92 | httpData.delete(); 93 | } 94 | } 95 | 96 | @Override 97 | public String getHeader(String name) { 98 | return httpHeaders.get(name); 99 | } 100 | 101 | @Override 102 | public Collection getHeaders(String name) { 103 | return httpHeaders.getAllAsString(name); 104 | } 105 | 106 | @Override 107 | public Collection getHeaderNames() { 108 | return httpHeaders.names(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/response/HttpResponseOutputStream.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.response; 2 | 3 | import com.google.common.base.Preconditions; 4 | import io.gitlab.leibnizhu.sbnetty.core.ClientAbortException; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelFutureListener; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.handler.codec.ByteToMessageDecoder; 11 | import io.netty.handler.codec.http.DefaultLastHttpContent; 12 | 13 | import javax.servlet.ServletOutputStream; 14 | import javax.servlet.WriteListener; 15 | import java.io.IOException; 16 | import java.util.concurrent.locks.ReentrantLock; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | public class HttpResponseOutputStream extends ServletOutputStream { 21 | 22 | private final ChannelHandlerContext ctx; 23 | private final NettyHttpServletResponse servletResponse; 24 | private WriteListener writeListener; 25 | 26 | private final ByteToMessageDecoder.Cumulator cumulator = ByteToMessageDecoder.COMPOSITE_CUMULATOR; 27 | private ByteBuf buf = Unpooled.EMPTY_BUFFER; 28 | private static final int DEFAULT_BUFFER_SIZE = 1024 * 8; 29 | private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 4; 30 | private volatile boolean closed; 31 | 32 | private volatile boolean hasCommit = false; 33 | 34 | public boolean isHasCommit() { 35 | return hasCommit; 36 | } 37 | 38 | private Integer outerBufferSize; 39 | private final ReentrantLock lock = new ReentrantLock(); 40 | 41 | HttpResponseOutputStream(ChannelHandlerContext ctx, NettyHttpServletResponse servletResponse) { 42 | this.ctx = ctx; 43 | this.servletResponse = servletResponse; 44 | } 45 | 46 | @Override 47 | public boolean isReady() { 48 | return ctx.channel().isWritable(); 49 | } 50 | 51 | @Override 52 | public void setWriteListener(WriteListener writeListener) { 53 | checkNotNull(writeListener); 54 | if (this.writeListener != null) { 55 | throw new IllegalStateException("writeListener already set"); 56 | } 57 | this.writeListener = writeListener; 58 | 59 | // write is Possible in any time 60 | ctx.executor().execute(() -> { 61 | try { 62 | writeListener.onWritePossible(); 63 | } catch (Exception e) { 64 | writeListener.onError(e); 65 | } 66 | }); 67 | } 68 | 69 | 70 | @Override 71 | public void write(byte[] b, int off, int len) throws IOException { 72 | lock.lock(); 73 | try { 74 | checkClose(); 75 | // 这个byte[]数组会被重复使用,所以这里需要复制新的空间 76 | ByteBuf appendedBuffer = ctx.alloc().buffer(len); 77 | appendedBuffer.writeBytes(b, off, len); 78 | 79 | buf = cumulator.cumulate(ctx.alloc(), buf, appendedBuffer); 80 | if (buf.readableBytes() >= getBufferSize()) { 81 | flush(); 82 | } 83 | } finally { 84 | lock.unlock(); 85 | } 86 | } 87 | 88 | 89 | @Override 90 | public void write(int b) throws IOException { 91 | lock.lock(); 92 | try { 93 | checkClose(); 94 | buf.writeByte(b); 95 | } finally { 96 | lock.unlock(); 97 | } 98 | 99 | } 100 | 101 | 102 | @Override 103 | public void flush() { 104 | performFlush(true); 105 | } 106 | 107 | private void performFlush(boolean flushNetty) { 108 | lock.lock(); 109 | try { 110 | hasCommit = true; 111 | servletResponse.ensureResponseHeader(buf.readableBytes() > 0); 112 | if (buf.readableBytes() == 0) { 113 | return; 114 | } 115 | if (flushNetty) { 116 | ctx.writeAndFlush(buf); 117 | } else { 118 | ctx.write(buf); 119 | } 120 | buf = Unpooled.EMPTY_BUFFER; 121 | } finally { 122 | lock.unlock(); 123 | } 124 | } 125 | 126 | private void checkClose() throws IOException { 127 | if (closed) { 128 | throw new ClientAbortException("user reset"); 129 | } 130 | } 131 | 132 | 133 | @Override 134 | public void close() throws IOException { 135 | lock.lock(); 136 | try { 137 | if (closed) { 138 | return; 139 | } 140 | closed = true; 141 | performFlush(false); 142 | ChannelFuture future = ctx.writeAndFlush(DefaultLastHttpContent.EMPTY_LAST_CONTENT); 143 | if (!servletResponse.isKeepAlive()) { 144 | future.addListener(ChannelFutureListener.CLOSE);//如果不是keep-alive,写完后关闭channel 145 | } 146 | } finally { 147 | buf = Unpooled.EMPTY_BUFFER; 148 | lock.unlock(); 149 | } 150 | } 151 | 152 | void resetBuffer() { 153 | Preconditions.checkArgument(!hasCommit, "can not perform after commit"); 154 | buf.readerIndex(0); 155 | buf.writerIndex(0); 156 | } 157 | 158 | int getBufferSize() { 159 | if (outerBufferSize != null) { 160 | return outerBufferSize; 161 | } 162 | int choosedBufferSize = buf.nioBufferCount(); 163 | if (choosedBufferSize < DEFAULT_BUFFER_SIZE) { 164 | choosedBufferSize = DEFAULT_BUFFER_SIZE; 165 | } else if (choosedBufferSize > MAX_BUFFER_SIZE) { 166 | choosedBufferSize = MAX_BUFFER_SIZE; 167 | } 168 | return choosedBufferSize; 169 | } 170 | 171 | void setBufferSize(int size) { 172 | outerBufferSize = size; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/response/NettyHttpServletResponse.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.response; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.base.Charsets; 5 | import com.google.common.base.Optional; 6 | import com.google.common.net.MediaType; 7 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 8 | import io.gitlab.leibnizhu.sbnetty.request.NettyHttpServletRequest; 9 | import io.gitlab.leibnizhu.sbnetty.session.NettyHttpSession; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.handler.codec.http.*; 12 | import io.netty.util.AsciiString; 13 | import io.netty.util.concurrent.FastThreadLocal; 14 | 15 | import javax.servlet.ServletOutputStream; 16 | import javax.servlet.http.Cookie; 17 | import javax.servlet.http.HttpServletResponse; 18 | import javax.servlet.http.HttpSession; 19 | import java.io.IOException; 20 | import java.io.PrintWriter; 21 | import java.nio.charset.Charset; 22 | import java.text.DateFormat; 23 | import java.text.SimpleDateFormat; 24 | import java.util.*; 25 | import java.util.concurrent.atomic.AtomicBoolean; 26 | 27 | import static com.google.common.base.Preconditions.checkState; 28 | 29 | /** 30 | * Http响应对象 31 | */ 32 | public class NettyHttpServletResponse implements HttpServletResponse { 33 | /** 34 | * SimpleDateFormat非线程安全,为了节省内存提高效率,把他放在ThreadLocal里 35 | * 用于设置HTTP响应头的时间信息 36 | */ 37 | private static final FastThreadLocal FORMAT = new FastThreadLocal() { 38 | @Override 39 | protected DateFormat initialValue() { 40 | DateFormat df = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.ENGLISH); 41 | df.setTimeZone(TimeZone.getTimeZone("GMT")); 42 | return df; 43 | } 44 | }; 45 | 46 | private static final Locale DEFAULT_LOCALE = Locale.getDefault(); 47 | private static final String DEFAULT_CHARACTER_ENCODING = Charsets.UTF_8.name(); 48 | 49 | private final NettyContext servletContext; 50 | 51 | private final NettyHttpServletRequest httpServletRequest; 52 | 53 | private final HttpResponse response; 54 | 55 | private final HttpResponseOutputStream outputStream; 56 | private boolean usingOutputStream; 57 | private PrintWriter writer; 58 | private final List cookies; 59 | private String contentType; 60 | private String characterEncoding = DEFAULT_CHARACTER_ENCODING; 61 | private Locale locale; 62 | private final AtomicBoolean hasWriteHeader = new AtomicBoolean(false); 63 | private final ChannelHandlerContext ctx; 64 | 65 | 66 | public NettyHttpServletResponse(ChannelHandlerContext ctx, NettyContext servletContext, NettyHttpServletRequest httpServletRequest) { 67 | this.ctx = ctx; 68 | this.servletContext = servletContext; 69 | this.httpServletRequest = httpServletRequest; 70 | this.outputStream = new HttpResponseOutputStream(ctx, this); 71 | this.cookies = new ArrayList<>(); 72 | 73 | this.response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, false); 74 | HttpUtil.setKeepAlive(response, httpServletRequest.isKeepAlive()); 75 | } 76 | 77 | 78 | 79 | private boolean useChunked = false; 80 | 81 | public void ensureResponseHeader(boolean hasBody) { 82 | if (!hasWriteHeader.compareAndSet(false, true)) { 83 | return; 84 | } 85 | if (!HttpUtil.isContentLengthSet(response)) { 86 | if (hasBody) { 87 | // 在开始写body的时候,都还没有contentLength出现,那么这个请求应该是trunk的 88 | response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); 89 | useChunked = true; 90 | } else { 91 | HttpUtil.setContentLength(response, 0L); 92 | } 93 | } 94 | HttpHeaders headers = response.headers(); 95 | if (null != contentType) { 96 | String value = null == characterEncoding ? contentType : contentType + "; charset=" + characterEncoding; //Content Type 响应头的内容 97 | headers.set(HttpHeaderNames.CONTENT_TYPE, value); 98 | } 99 | CharSequence date = getFormattedDate(); 100 | headers.set(HttpHeaderNames.DATE, date); // 时间日期响应头 101 | headers.set(HttpHeaderNames.SERVER, servletContext.getServerInfo()); //服务器信息响应头 102 | 103 | 104 | HttpSession session = httpServletRequest.getSession(false); 105 | if (session != null && session.isNew()) { 106 | String sessionCookieStr = NettyHttpSession.SESSION_COOKIE_NAME + 107 | "=" + httpServletRequest.getRequestedSessionId() + 108 | "; path=/; domain=" + 109 | httpServletRequest.getServerName(); 110 | headers.add(HttpHeaderNames.SET_COOKIE, sessionCookieStr); 111 | } 112 | 113 | for (Cookie cookie : cookies) { 114 | StringBuilder sb = new StringBuilder(); 115 | sb.append(cookie.getName()).append("=").append(cookie.getValue()) 116 | .append("; max-Age=").append(cookie.getMaxAge()); 117 | if (cookie.getPath() != null) sb.append("; path=").append(cookie.getPath()); 118 | if (cookie.getDomain() != null) sb.append("; domain=").append(cookie.getDomain()); 119 | headers.add(HttpHeaderNames.SET_COOKIE, sb.toString()); 120 | } 121 | ctx.write(response); 122 | } 123 | 124 | public boolean isKeepAlive() { 125 | return httpServletRequest.isKeepAlive(); 126 | } 127 | 128 | /** 129 | * @return 线程安全的获取当前时间格式化后的字符串 130 | */ 131 | @VisibleForTesting 132 | private CharSequence getFormattedDate() { 133 | return new AsciiString(FORMAT.get().format(new Date())); 134 | } 135 | 136 | @Override 137 | public void addCookie(Cookie cookie) { 138 | cookies.add(cookie); 139 | } 140 | 141 | @Override 142 | public boolean containsHeader(String name) { 143 | return response.headers().contains(name); 144 | } 145 | 146 | @Override 147 | public String encodeURL(String url) { 148 | if (!httpServletRequest.isRequestedSessionIdFromCookie()) { 149 | //来自Cookie的Session ID,则客户端肯定支持Cookie,无需重写URL 150 | return url; 151 | } 152 | return url + ";" + NettyHttpSession.SESSION_REQUEST_PARAMETER_NAME + "=" + httpServletRequest.getRequestedSessionId(); 153 | } 154 | 155 | @Override 156 | public String encodeRedirectURL(String url) { 157 | return encodeURL(url); 158 | } 159 | 160 | @Override 161 | @Deprecated 162 | public String encodeUrl(String url) { 163 | return encodeURL(url); 164 | } 165 | 166 | @Override 167 | @Deprecated 168 | public String encodeRedirectUrl(String url) { 169 | return encodeRedirectURL(url); 170 | } 171 | 172 | @Override 173 | public void sendError(int sc, String msg) throws IOException { 174 | checkNotCommitted(); 175 | response.setStatus(new HttpResponseStatus(sc, msg)); 176 | } 177 | 178 | @Override 179 | public void sendError(int sc) throws IOException { 180 | checkNotCommitted(); 181 | response.setStatus(HttpResponseStatus.valueOf(sc)); 182 | } 183 | 184 | @Override 185 | public void sendRedirect(String location) throws IOException { 186 | checkNotCommitted(); 187 | response.setStatus(HttpResponseStatus.FOUND); 188 | response.headers().set("Location", location); 189 | } 190 | 191 | @Override 192 | public void setDateHeader(String name, long date) { 193 | response.headers().set(name, date); 194 | } 195 | 196 | @Override 197 | public void addDateHeader(String name, long date) { 198 | response.headers().add(name, date); 199 | } 200 | 201 | @Override 202 | public void setHeader(String name, String value) { 203 | if (name == null || name.isEmpty() || value == null) { 204 | return; 205 | } 206 | if (isCommitted()) { 207 | return; 208 | } 209 | if (setHeaderField(name, value)) { 210 | return; 211 | } 212 | response.headers().set(name, value); 213 | } 214 | 215 | private boolean setHeaderField(String name, String value) { 216 | char c = name.charAt(0);//减少判断的时间,提高效率 217 | if ('C' == c || 'c' == c) { 218 | if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(name)) { 219 | setContentType(value); 220 | return true; 221 | } 222 | } 223 | return false; 224 | } 225 | 226 | @Override 227 | public void addHeader(String name, String value) { 228 | if (name == null || name.isEmpty() || value == null) { 229 | return; 230 | } 231 | if (isCommitted()) { 232 | return; 233 | } 234 | if (setHeaderField(name, value)) { 235 | return; 236 | } 237 | response.headers().add(name, value); 238 | } 239 | 240 | @Override 241 | public void setIntHeader(String name, int value) { 242 | if (name == null || name.isEmpty()) { 243 | return; 244 | } 245 | if (isCommitted()) { 246 | return; 247 | } 248 | response.headers().set(name, value); 249 | } 250 | 251 | @Override 252 | public void addIntHeader(String name, int value) { 253 | if (name == null || name.isEmpty()) { 254 | return; 255 | } 256 | if (isCommitted()) { 257 | return; 258 | } 259 | response.headers().add(name, value); 260 | } 261 | 262 | @Override 263 | public void setContentType(String type) { 264 | if (isCommitted()) { 265 | return; 266 | } 267 | if (hasWriter()) { 268 | return; 269 | } 270 | if (null == type) { 271 | contentType = null; 272 | return; 273 | } 274 | MediaType mediaType = MediaType.parse(type); 275 | Optional charset = mediaType.charset(); 276 | if (charset.isPresent()) { 277 | setCharacterEncoding(charset.get().name()); 278 | } 279 | contentType = mediaType.type() + '/' + mediaType.subtype(); 280 | } 281 | 282 | @Override 283 | public String getContentType() { 284 | return contentType; 285 | } 286 | 287 | @Override 288 | public void setStatus(int sc) { 289 | response.setStatus(HttpResponseStatus.valueOf(sc)); 290 | } 291 | 292 | @Override 293 | @Deprecated 294 | public void setStatus(int sc, String sm) { 295 | response.setStatus(new HttpResponseStatus(sc, sm)); 296 | } 297 | 298 | @Override 299 | public int getStatus() { 300 | return response.status().code(); 301 | } 302 | 303 | @Override 304 | public String getHeader(String name) { 305 | return response.headers().get(name); 306 | } 307 | 308 | @Override 309 | public Collection getHeaders(String name) { 310 | return response.headers().getAll(name); 311 | } 312 | 313 | @Override 314 | public Collection getHeaderNames() { 315 | return response.headers().names(); 316 | } 317 | 318 | @Override 319 | public String getCharacterEncoding() { 320 | return characterEncoding; 321 | } 322 | 323 | //Writer和OutputStream不能同时使用 324 | 325 | @Override 326 | public ServletOutputStream getOutputStream() { 327 | checkState(!hasWriter(), "getWriter has already been called for this response"); 328 | usingOutputStream = true; 329 | return outputStream; 330 | } 331 | 332 | @Override 333 | public PrintWriter getWriter() { 334 | checkState(!usingOutputStream, "getOutputStream has already been called for this response"); 335 | if (!hasWriter()) { 336 | writer = new PrintWriter(outputStream); 337 | } 338 | return writer; 339 | } 340 | 341 | @Override 342 | public void setCharacterEncoding(String charset) { 343 | if (hasWriter()) { 344 | return; 345 | } 346 | characterEncoding = charset; 347 | } 348 | 349 | private boolean hasWriter() { 350 | return null != writer; 351 | } 352 | 353 | @Override 354 | public void setContentLength(int len) { 355 | HttpUtil.setContentLength(response, len); 356 | } 357 | 358 | @Override 359 | public void setContentLengthLong(long len) { 360 | HttpUtil.setContentLength(response, len); 361 | } 362 | 363 | @Override 364 | public void setBufferSize(int size) { 365 | outputStream.setBufferSize(size); 366 | } 367 | 368 | @Override 369 | public int getBufferSize() { 370 | return outputStream.getBufferSize(); 371 | } 372 | 373 | @Override 374 | public void flushBuffer() { 375 | outputStream.flush(); 376 | } 377 | 378 | @Override 379 | public void resetBuffer() { 380 | outputStream.resetBuffer(); 381 | } 382 | 383 | @Override 384 | public boolean isCommitted() { 385 | return outputStream.isHasCommit(); 386 | } 387 | 388 | private void checkNotCommitted() throws IOException { 389 | if (outputStream.isHasCommit()) { 390 | throw new IOException("Cannot perform this operation after response has been committed"); 391 | } 392 | } 393 | 394 | @Override 395 | public void reset() { 396 | resetBuffer(); 397 | usingOutputStream = false; 398 | writer = null; 399 | } 400 | 401 | @Override 402 | public void setLocale(Locale loc) { 403 | locale = loc; 404 | } 405 | 406 | @Override 407 | public Locale getLocale() { 408 | return null == locale ? DEFAULT_LOCALE : locale; 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/session/NettyHttpSession.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.session; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.http.HttpSession; 5 | import javax.servlet.http.HttpSessionContext; 6 | import java.io.Serializable; 7 | import java.util.Collections; 8 | import java.util.Enumeration; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * @author Leibniz.Hu 15 | * Created on 2017-08-28 20:57. 16 | */ 17 | public class NettyHttpSession implements HttpSession, Serializable { 18 | public static final String SESSION_COOKIE_NAME = "JSESSIONID"; 19 | public static final String SESSION_REQUEST_PARAMETER_NAME = "jsessionid"; 20 | private NettySessionManager manager; 21 | private long creationTime; 22 | private long lastAccessedTime; 23 | private int interval = NettySessionManager.SESSION_LIFE_SECONDS; 24 | private String id; 25 | 26 | NettyHttpSession(String id, NettySessionManager manager){ 27 | long curTime = System.currentTimeMillis(); 28 | this.creationTime = curTime; 29 | this.lastAccessedTime = curTime; 30 | this.id = id; 31 | this.manager = manager; 32 | this.sessionFacade = new NettyHttpSessionFacade(this); 33 | } 34 | 35 | private HttpSession sessionFacade; 36 | 37 | public HttpSession getSession(){ 38 | return sessionFacade; 39 | } 40 | 41 | @Override 42 | public long getCreationTime() { 43 | return creationTime; 44 | } 45 | 46 | @Override 47 | public String getId() { 48 | return id; 49 | } 50 | 51 | void updateAccessTime() { 52 | lastAccessedTime = System.currentTimeMillis(); 53 | } 54 | 55 | @Override 56 | public long getLastAccessedTime() { 57 | return lastAccessedTime; 58 | } 59 | 60 | @Override 61 | public ServletContext getServletContext() { 62 | return manager.getServletContext(); 63 | } 64 | 65 | @Override 66 | public void setMaxInactiveInterval(int interval) { 67 | this.interval = interval; 68 | } 69 | 70 | @Override 71 | public int getMaxInactiveInterval() { 72 | return interval; 73 | } 74 | 75 | @Override 76 | @Deprecated 77 | public HttpSessionContext getSessionContext() { 78 | return null; 79 | } 80 | 81 | private Map attributes = new ConcurrentHashMap<>(); 82 | 83 | @Override 84 | public Object getAttribute(String name) { 85 | return attributes.get(name); 86 | } 87 | 88 | @Override 89 | @Deprecated 90 | public Object getValue(String name) { 91 | return attributes.get(name); 92 | } 93 | 94 | @Override 95 | public Enumeration getAttributeNames() { 96 | return Collections.enumeration(attributes.keySet()); 97 | } 98 | 99 | @Override 100 | @Deprecated 101 | public String[] getValueNames() { 102 | Set nameSet = attributes.keySet(); 103 | String[] nameArray = new String[nameSet.size()]; 104 | return nameSet.toArray(nameArray); 105 | } 106 | 107 | @Override 108 | public void setAttribute(String name, Object value) { 109 | attributes.put(name, value); 110 | } 111 | 112 | @Override 113 | @Deprecated 114 | public void putValue(String name, Object value) { 115 | attributes.put(name, value); 116 | } 117 | 118 | @Override 119 | public void removeAttribute(String name) { 120 | attributes.remove(name); 121 | } 122 | 123 | @Override 124 | @Deprecated 125 | public void removeValue(String name) { 126 | attributes.remove(name); 127 | } 128 | 129 | @Override 130 | public void invalidate() { 131 | attributes.clear(); 132 | attributes = null; 133 | manager.invalidate(this); 134 | manager = null; 135 | } 136 | 137 | private boolean isNew = true; 138 | @Override 139 | public boolean isNew() { 140 | return isNew; 141 | } 142 | 143 | public void setNew(boolean isNew){ 144 | this.isNew = isNew; 145 | } 146 | 147 | /** 148 | * 是否过期 149 | */ 150 | public boolean expire(){ 151 | return System.currentTimeMillis() - creationTime >= interval * 1000; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/session/NettyHttpSessionFacade.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.session; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.http.HttpSession; 5 | import javax.servlet.http.HttpSessionContext; 6 | import java.util.Enumeration; 7 | 8 | /** 9 | * NettyHttpSession的门面包装类 10 | * 11 | * @author Leibniz.Hu 12 | * Created on 2017-08-29 18:07. 13 | */ 14 | public class NettyHttpSessionFacade implements HttpSession { 15 | private NettyHttpSession session; 16 | 17 | public NettyHttpSessionFacade(NettyHttpSession session) { 18 | this.session = session; 19 | } 20 | 21 | @Override 22 | public long getCreationTime() { 23 | return session.getCreationTime(); 24 | } 25 | 26 | @Override 27 | public String getId() { 28 | return session.getId(); 29 | } 30 | 31 | @Override 32 | public long getLastAccessedTime() { 33 | return session.getLastAccessedTime(); 34 | } 35 | 36 | @Override 37 | public ServletContext getServletContext() { 38 | return session.getServletContext(); 39 | } 40 | 41 | @Override 42 | public void setMaxInactiveInterval(int interval) { 43 | session.setMaxInactiveInterval(interval); 44 | } 45 | 46 | @Override 47 | public int getMaxInactiveInterval() { 48 | return session.getMaxInactiveInterval(); 49 | } 50 | 51 | @Override 52 | @Deprecated 53 | public HttpSessionContext getSessionContext() { 54 | return session.getSessionContext(); 55 | } 56 | 57 | @Override 58 | public Object getAttribute(String name) { 59 | return session.getAttribute(name); 60 | } 61 | 62 | @Override 63 | @Deprecated 64 | public Object getValue(String name) { 65 | return session.getValue(name); 66 | } 67 | 68 | @Override 69 | @Deprecated 70 | public Enumeration getAttributeNames() { 71 | return session.getAttributeNames(); 72 | } 73 | 74 | @Override 75 | @Deprecated 76 | public String[] getValueNames() { 77 | return session.getValueNames(); 78 | } 79 | 80 | @Override 81 | public void setAttribute(String name, Object value) { 82 | session.setAttribute(name, value); 83 | } 84 | 85 | @Override 86 | @Deprecated 87 | public void putValue(String name, Object value) { 88 | session.putValue(name, value); 89 | } 90 | 91 | @Override 92 | public void removeAttribute(String name) { 93 | session.removeAttribute(name); 94 | } 95 | 96 | @Override 97 | @Deprecated 98 | public void removeValue(String name) { 99 | session.removeValue(name); 100 | } 101 | 102 | @Override 103 | public void invalidate() { 104 | session.invalidate(); 105 | } 106 | 107 | @Override 108 | public boolean isNew() { 109 | return session.isNew(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/session/NettySessionManager.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.session; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.core.NettyContext; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.ServletContext; 8 | import javax.servlet.http.HttpSession; 9 | import java.util.Map; 10 | import java.util.Random; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * @author Leibniz.Hu 15 | * Created on 2017-08-28 20:59. 16 | */ 17 | public class NettySessionManager { 18 | private Logger log = LoggerFactory.getLogger(getClass()); 19 | 20 | private NettyContext servletContext; 21 | private Map sessions = new ConcurrentHashMap<>(); 22 | static final int SESSION_LIFE_SECONDS = 60 * 30; 23 | private static final int SESSION_LIFE_CHECK_INTER = 1000 * 60; 24 | 25 | public NettySessionManager(NettyContext servletContext){ 26 | this.servletContext = servletContext; 27 | Thread thread = new Thread(new checkInvalidSessions(), "Session-Check"); 28 | thread.setDaemon(true); 29 | thread.start(); 30 | } 31 | 32 | ServletContext getServletContext() { 33 | return servletContext; 34 | } 35 | 36 | void invalidate(HttpSession session) { 37 | sessions.remove(session.getId()); 38 | } 39 | 40 | public void updateAccessTime(NettyHttpSession session){ 41 | if(session != null){ 42 | session.updateAccessTime(); 43 | } 44 | } 45 | 46 | public boolean checkValid(NettyHttpSession session) { 47 | return session != null && sessions.get(session.getId()) != null && !session.expire(); 48 | } 49 | 50 | public NettyHttpSession getSession(String id){ 51 | return id == null ? null : sessions.get(id); 52 | } 53 | 54 | public NettyHttpSession createSession(){ 55 | String id = createUniqueSessionId(); 56 | NettyHttpSession newSession = new NettyHttpSession(id, this); 57 | sessions.put(id ,newSession); 58 | return newSession; 59 | } 60 | 61 | private String createUniqueSessionId() { 62 | String prefix = String.valueOf(100000 + new Random().nextInt(899999)); 63 | return new StringBuilder().append(System.currentTimeMillis()).reverse().append(prefix).toString(); 64 | } 65 | 66 | /** 67 | * 超时的Session无效化,定期执行 68 | */ 69 | private class checkInvalidSessions implements Runnable { 70 | @Override 71 | public void run() { 72 | log.info("Session Manager expire-checking thread has been started..."); 73 | while(true){ 74 | try { 75 | Thread.sleep(SESSION_LIFE_CHECK_INTER); 76 | } catch (InterruptedException e) { 77 | e.printStackTrace(); 78 | } 79 | long curTime = System.currentTimeMillis(); 80 | for(NettyHttpSession session : sessions.values()){ 81 | if(session.expire()){ 82 | log.info("Session(ID={}) is invalidated by Session Manager", session.getId()); 83 | session.invalidate(); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/utils/MappingData.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.utils; 2 | 3 | import javax.servlet.Servlet; 4 | 5 | /** 6 | * @author Leibniz.Hu 7 | * Created on 2017-08-25 12:28. 8 | */ 9 | public class MappingData { 10 | 11 | Servlet servlet = null; 12 | String servletName; 13 | String redirectPath ; 14 | 15 | public void recycle() { 16 | servlet = null; 17 | servletName = null; 18 | redirectPath = null; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/utils/MimeTypeUtil.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Locale; 5 | import java.util.Map; 6 | 7 | /** 8 | * @author Leibniz.Hu 9 | * Created on 2017-08-24 21:03. 10 | */ 11 | public class MimeTypeUtil { 12 | private static final Map MIME_TYPE_MAPPING = new HashMap<>(); 13 | 14 | static { 15 | MIME_TYPE_MAPPING.put("abs", "audio/x-mpeg"); 16 | MIME_TYPE_MAPPING.put("ai", "application/postscript"); 17 | MIME_TYPE_MAPPING.put("aif", "audio/x-aiff"); 18 | MIME_TYPE_MAPPING.put("aifc", "audio/x-aiff"); 19 | MIME_TYPE_MAPPING.put("aiff", "audio/x-aiff"); 20 | MIME_TYPE_MAPPING.put("aim", "application/x-aim"); 21 | MIME_TYPE_MAPPING.put("art", "image/x-jg"); 22 | MIME_TYPE_MAPPING.put("asf", "video/x-ms-asf"); 23 | MIME_TYPE_MAPPING.put("asx", "video/x-ms-asf"); 24 | MIME_TYPE_MAPPING.put("au", "audio/basic"); 25 | MIME_TYPE_MAPPING.put("avi", "video/x-msvideo"); 26 | MIME_TYPE_MAPPING.put("avx", "video/x-rad-screenplay"); 27 | MIME_TYPE_MAPPING.put("bcpio", "application/x-bcpio"); 28 | MIME_TYPE_MAPPING.put("bin", "application/octet-stream"); 29 | MIME_TYPE_MAPPING.put("bmp", "image/bmp"); 30 | MIME_TYPE_MAPPING.put("body", "text/html"); 31 | MIME_TYPE_MAPPING.put("cdf", "application/x-cdf"); 32 | MIME_TYPE_MAPPING.put("cer", "application/pkix-cert"); 33 | MIME_TYPE_MAPPING.put("class", "application/java"); 34 | MIME_TYPE_MAPPING.put("cpio", "application/x-cpio"); 35 | MIME_TYPE_MAPPING.put("csh", "application/x-csh"); 36 | MIME_TYPE_MAPPING.put("css", "text/css"); 37 | MIME_TYPE_MAPPING.put("dib", "image/bmp"); 38 | MIME_TYPE_MAPPING.put("doc", "application/msword"); 39 | MIME_TYPE_MAPPING.put("dtd", "application/xml-dtd"); 40 | MIME_TYPE_MAPPING.put("dv", "video/x-dv"); 41 | MIME_TYPE_MAPPING.put("dvi", "application/x-dvi"); 42 | MIME_TYPE_MAPPING.put("eps", "application/postscript"); 43 | MIME_TYPE_MAPPING.put("etx", "text/x-setext"); 44 | MIME_TYPE_MAPPING.put("exe", "application/octet-stream"); 45 | MIME_TYPE_MAPPING.put("gif", "image/gif"); 46 | MIME_TYPE_MAPPING.put("gtar", "application/x-gtar"); 47 | MIME_TYPE_MAPPING.put("gz", "application/x-gzip"); 48 | MIME_TYPE_MAPPING.put("hdf", "application/x-hdf"); 49 | MIME_TYPE_MAPPING.put("hqx", "application/mac-binhex40"); 50 | MIME_TYPE_MAPPING.put("htc", "text/x-component"); 51 | MIME_TYPE_MAPPING.put("htm", "text/html"); 52 | MIME_TYPE_MAPPING.put("html", "text/html"); 53 | MIME_TYPE_MAPPING.put("ief", "image/ief"); 54 | MIME_TYPE_MAPPING.put("jad", "text/vnd.sun.j2me.app-descriptor"); 55 | MIME_TYPE_MAPPING.put("jar", "application/java-archive"); 56 | MIME_TYPE_MAPPING.put("java", "text/x-java-source"); 57 | MIME_TYPE_MAPPING.put("jnlp", "application/x-java-jnlp-file"); 58 | MIME_TYPE_MAPPING.put("jpe", "image/jpeg"); 59 | MIME_TYPE_MAPPING.put("jpeg", "image/jpeg"); 60 | MIME_TYPE_MAPPING.put("jpg", "image/jpeg"); 61 | MIME_TYPE_MAPPING.put("js", "application/javascript"); 62 | MIME_TYPE_MAPPING.put("jsf", "text/plain"); 63 | MIME_TYPE_MAPPING.put("jspf", "text/plain"); 64 | MIME_TYPE_MAPPING.put("kar", "audio/midi"); 65 | MIME_TYPE_MAPPING.put("latex", "application/x-latex"); 66 | MIME_TYPE_MAPPING.put("m3u", "audio/x-mpegurl"); 67 | MIME_TYPE_MAPPING.put("mac", "image/x-macpaint"); 68 | MIME_TYPE_MAPPING.put("man", "text/troff"); 69 | MIME_TYPE_MAPPING.put("mathml", "application/mathml+xml"); 70 | MIME_TYPE_MAPPING.put("me", "text/troff"); 71 | MIME_TYPE_MAPPING.put("mid", "audio/midi"); 72 | MIME_TYPE_MAPPING.put("midi", "audio/midi"); 73 | MIME_TYPE_MAPPING.put("mif", "application/x-mif"); 74 | MIME_TYPE_MAPPING.put("mov", "video/quicktime"); 75 | MIME_TYPE_MAPPING.put("movie", "video/x-sgi-movie"); 76 | MIME_TYPE_MAPPING.put("mp1", "audio/mpeg"); 77 | MIME_TYPE_MAPPING.put("mp2", "audio/mpeg"); 78 | MIME_TYPE_MAPPING.put("mp3", "audio/mpeg"); 79 | MIME_TYPE_MAPPING.put("mp4", "video/mp4"); 80 | MIME_TYPE_MAPPING.put("mpa", "audio/mpeg"); 81 | MIME_TYPE_MAPPING.put("mpe", "video/mpeg"); 82 | MIME_TYPE_MAPPING.put("mpeg", "video/mpeg"); 83 | MIME_TYPE_MAPPING.put("mpega", "audio/x-mpeg"); 84 | MIME_TYPE_MAPPING.put("mpg", "video/mpeg"); 85 | MIME_TYPE_MAPPING.put("mpv2", "video/mpeg2"); 86 | MIME_TYPE_MAPPING.put("nc", "application/x-netcdf"); 87 | MIME_TYPE_MAPPING.put("oda", "application/oda"); 88 | MIME_TYPE_MAPPING.put("odb", "application/vnd.oasis.opendocument.database"); 89 | MIME_TYPE_MAPPING.put("odc", "application/vnd.oasis.opendocument.chart"); 90 | MIME_TYPE_MAPPING.put("odf", "application/vnd.oasis.opendocument.formula"); 91 | MIME_TYPE_MAPPING.put("odg", "application/vnd.oasis.opendocument.graphics"); 92 | MIME_TYPE_MAPPING.put("odi", "application/vnd.oasis.opendocument.image"); 93 | MIME_TYPE_MAPPING.put("odm", "application/vnd.oasis.opendocument.text-master"); 94 | MIME_TYPE_MAPPING.put("odp", "application/vnd.oasis.opendocument.presentation"); 95 | MIME_TYPE_MAPPING.put("ods", "application/vnd.oasis.opendocument.spreadsheet"); 96 | MIME_TYPE_MAPPING.put("odt", "application/vnd.oasis.opendocument.text"); 97 | MIME_TYPE_MAPPING.put("otg", "application/vnd.oasis.opendocument.graphics-template"); 98 | MIME_TYPE_MAPPING.put("oth", "application/vnd.oasis.opendocument.text-web"); 99 | MIME_TYPE_MAPPING.put("otp", "application/vnd.oasis.opendocument.presentation-template"); 100 | MIME_TYPE_MAPPING.put("ots", "application/vnd.oasis.opendocument.spreadsheet-template "); 101 | MIME_TYPE_MAPPING.put("ott", "application/vnd.oasis.opendocument.text-template"); 102 | MIME_TYPE_MAPPING.put("ogx", "application/ogg"); 103 | MIME_TYPE_MAPPING.put("ogv", "video/ogg"); 104 | MIME_TYPE_MAPPING.put("oga", "audio/ogg"); 105 | MIME_TYPE_MAPPING.put("ogg", "audio/ogg"); 106 | MIME_TYPE_MAPPING.put("spx", "audio/ogg"); 107 | MIME_TYPE_MAPPING.put("flac", "audio/flac"); 108 | MIME_TYPE_MAPPING.put("anx", "application/annodex"); 109 | MIME_TYPE_MAPPING.put("axa", "audio/annodex"); 110 | MIME_TYPE_MAPPING.put("axv", "video/annodex"); 111 | MIME_TYPE_MAPPING.put("xspf", "application/xspf+xml"); 112 | MIME_TYPE_MAPPING.put("pbm", "image/x-portable-bitmap"); 113 | MIME_TYPE_MAPPING.put("pct", "image/pict"); 114 | MIME_TYPE_MAPPING.put("pdf", "application/pdf"); 115 | MIME_TYPE_MAPPING.put("pgm", "image/x-portable-graymap"); 116 | MIME_TYPE_MAPPING.put("pic", "image/pict"); 117 | MIME_TYPE_MAPPING.put("pict", "image/pict"); 118 | MIME_TYPE_MAPPING.put("pls", "audio/x-scpls"); 119 | MIME_TYPE_MAPPING.put("png", "image/png"); 120 | MIME_TYPE_MAPPING.put("pnm", "image/x-portable-anymap"); 121 | MIME_TYPE_MAPPING.put("pnt", "image/x-macpaint"); 122 | MIME_TYPE_MAPPING.put("ppm", "image/x-portable-pixmap"); 123 | MIME_TYPE_MAPPING.put("ppt", "application/vnd.ms-powerpoint"); 124 | MIME_TYPE_MAPPING.put("pps", "application/vnd.ms-powerpoint"); 125 | MIME_TYPE_MAPPING.put("ps", "application/postscript"); 126 | MIME_TYPE_MAPPING.put("psd", "image/vnd.adobe.photoshop"); 127 | MIME_TYPE_MAPPING.put("qt", "video/quicktime"); 128 | MIME_TYPE_MAPPING.put("qti", "image/x-quicktime"); 129 | MIME_TYPE_MAPPING.put("qtif", "image/x-quicktime"); 130 | MIME_TYPE_MAPPING.put("ras", "image/x-cmu-raster"); 131 | MIME_TYPE_MAPPING.put("rdf", "application/rdf+xml"); 132 | MIME_TYPE_MAPPING.put("rgb", "image/x-rgb"); 133 | MIME_TYPE_MAPPING.put("rm", "application/vnd.rn-realmedia"); 134 | MIME_TYPE_MAPPING.put("roff", "text/troff"); 135 | MIME_TYPE_MAPPING.put("rtf", "application/rtf"); 136 | MIME_TYPE_MAPPING.put("rtx", "text/richtext"); 137 | MIME_TYPE_MAPPING.put("sh", "application/x-sh"); 138 | MIME_TYPE_MAPPING.put("shar", "application/x-shar"); 139 | /*"shtml", "text/x-server-parsed-html",*/ 140 | MIME_TYPE_MAPPING.put("sit", "application/x-stuffit"); 141 | MIME_TYPE_MAPPING.put("snd", "audio/basic"); 142 | MIME_TYPE_MAPPING.put("src", "application/x-wais-source"); 143 | MIME_TYPE_MAPPING.put("sv4cpio", "application/x-sv4cpio"); 144 | MIME_TYPE_MAPPING.put("sv4crc", "application/x-sv4crc"); 145 | MIME_TYPE_MAPPING.put("svg", "image/svg+xml"); 146 | MIME_TYPE_MAPPING.put("svgz", "image/svg+xml"); 147 | MIME_TYPE_MAPPING.put("swf", "application/x-shockwave-flash"); 148 | MIME_TYPE_MAPPING.put("t", "text/troff"); 149 | MIME_TYPE_MAPPING.put("tar", "application/x-tar"); 150 | MIME_TYPE_MAPPING.put("tcl", "application/x-tcl"); 151 | MIME_TYPE_MAPPING.put("tex", "application/x-tex"); 152 | MIME_TYPE_MAPPING.put("texi", "application/x-texinfo"); 153 | MIME_TYPE_MAPPING.put("texinfo", "application/x-texinfo"); 154 | MIME_TYPE_MAPPING.put("tif", "image/tiff"); 155 | MIME_TYPE_MAPPING.put("tiff", "image/tiff"); 156 | MIME_TYPE_MAPPING.put("tr", "text/troff"); 157 | MIME_TYPE_MAPPING.put("tsv", "text/tab-separated-values"); 158 | MIME_TYPE_MAPPING.put("txt", "text/plain"); 159 | MIME_TYPE_MAPPING.put("ulw", "audio/basic"); 160 | MIME_TYPE_MAPPING.put("ustar", "application/x-ustar"); 161 | MIME_TYPE_MAPPING.put("vxml", "application/voicexml+xml"); 162 | MIME_TYPE_MAPPING.put("xbm", "image/x-xbitmap"); 163 | MIME_TYPE_MAPPING.put("xht", "application/xhtml+xml"); 164 | MIME_TYPE_MAPPING.put("xhtml", "application/xhtml+xml"); 165 | MIME_TYPE_MAPPING.put("xls", "application/vnd.ms-excel"); 166 | MIME_TYPE_MAPPING.put("xml", "application/xml"); 167 | MIME_TYPE_MAPPING.put("xpm", "image/x-xpixmap"); 168 | MIME_TYPE_MAPPING.put("xsl", "application/xml"); 169 | MIME_TYPE_MAPPING.put("xslt", "application/xslt+xml"); 170 | MIME_TYPE_MAPPING.put("xul", "application/vnd.mozilla.xul+xml"); 171 | MIME_TYPE_MAPPING.put("xwd", "image/x-xwindowdump"); 172 | MIME_TYPE_MAPPING.put("vsd", "application/vnd.visio"); 173 | MIME_TYPE_MAPPING.put("wav", "audio/x-wav"); 174 | MIME_TYPE_MAPPING.put("wbmp", "image/vnd.wap.wbmp"); 175 | MIME_TYPE_MAPPING.put("wml", "text/vnd.wap.wml"); 176 | MIME_TYPE_MAPPING.put("wmlc", "application/vnd.wap.wmlc"); 177 | MIME_TYPE_MAPPING.put("wmls", "text/vnd.wap.wmlsc"); 178 | MIME_TYPE_MAPPING.put("wmlscriptc", "application/vnd.wap.wmlscriptc"); 179 | MIME_TYPE_MAPPING.put("wmv", "video/x-ms-wmv"); 180 | MIME_TYPE_MAPPING.put("wrl", "model/vrml"); 181 | MIME_TYPE_MAPPING.put("wspolicy", "application/wspolicy+xml"); 182 | MIME_TYPE_MAPPING.put("Z", "application/x-compress"); 183 | MIME_TYPE_MAPPING.put("z", "application/x-compress"); 184 | MIME_TYPE_MAPPING.put("zip", "application/zip"); 185 | } 186 | 187 | public static String getMimeTypeByFileName(String fileName){ 188 | if (fileName == null) 189 | return (null); 190 | int period = fileName.lastIndexOf('.'); 191 | if (period < 0) 192 | return (null); 193 | String extension = fileName.substring(period + 1); 194 | if (extension.length() < 1) 195 | return (null); 196 | return getMimeTypeByExtension(extension); 197 | } 198 | 199 | private static String getMimeTypeByExtension(String extension){ 200 | return MIME_TYPE_MAPPING.get(extension.toLowerCase(Locale.ENGLISH)); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/io/gitlab/leibnizhu/sbnetty/utils/RequestUrlPatternMapper.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.servlet.Servlet; 7 | import javax.servlet.ServletException; 8 | import java.io.IOException; 9 | import java.util.*; 10 | 11 | /** 12 | * 保存,计算URL-pattern与请求路径的匹配关系 13 | * 14 | * @author Leibniz.Hu 15 | * Created on 2017-08-25 11:32. 16 | */ 17 | public class RequestUrlPatternMapper { 18 | private final Logger log = LoggerFactory.getLogger(getClass()); 19 | 20 | private UrlPatternContext urlPatternContext; 21 | private String contextPath; 22 | 23 | public RequestUrlPatternMapper(String contextPath) { 24 | this.urlPatternContext = new UrlPatternContext(); 25 | this.contextPath = contextPath; 26 | } 27 | 28 | /** 29 | * 增加映射关系 30 | * 31 | * @param urlPattern urlPattern 32 | * @param servlet servlet对象 33 | * @param servletName servletName 34 | */ 35 | public void addServlet(String urlPattern, Servlet servlet, String servletName) throws ServletException { 36 | if (urlPattern.endsWith("/*")) { 37 | // 路径匹配 38 | String pattern = urlPattern.substring(0, urlPattern.length() - 1); 39 | for (MappedServlet ms : urlPatternContext.wildcardServlets) { 40 | if (ms.pattern.equals(pattern)) { 41 | throw new ServletException("URL Pattern('" + urlPattern + "') already exists!"); 42 | } 43 | } 44 | MappedServlet newServlet = new MappedServlet(pattern, servlet, servletName); 45 | urlPatternContext.wildcardServlets.add(newServlet); 46 | urlPatternContext.wildcardServlets.sort((o1, o2) -> o2.pattern.compareTo(o1.pattern)); 47 | log.debug("Curretn Wildcard URL Pattern List = " + Arrays.toString(urlPatternContext.wildcardServlets.toArray())); 48 | } else if (urlPattern.startsWith("*.")) { 49 | // 扩展名匹配 50 | String pattern = urlPattern.substring(2); 51 | if (urlPatternContext.extensionServlets.get(pattern) != null) { 52 | throw new ServletException("URL Pattern('" + urlPattern + "') already exists!"); 53 | } 54 | MappedServlet newServlet = new MappedServlet(pattern, servlet, servletName); 55 | urlPatternContext.extensionServlets.put(pattern, newServlet); 56 | log.debug("Curretn Extension URL Pattern List = " + Arrays.toString(urlPatternContext.extensionServlets.keySet().toArray())); 57 | } else if (urlPattern.equals("/")) { 58 | // Default资源匹配 59 | if (urlPatternContext.defaultServlet != null) { 60 | throw new ServletException("URL Pattern('" + urlPattern + "') already exists!"); 61 | } 62 | urlPatternContext.defaultServlet = new MappedServlet("", servlet, servletName); 63 | } else { 64 | // 精确匹配 65 | String pattern; 66 | if (urlPattern.length() == 0) { 67 | pattern = "/"; 68 | } else { 69 | pattern = urlPattern; 70 | } 71 | if (urlPatternContext.exactServlets.get(pattern) != null) { 72 | throw new ServletException("URL Pattern('" + urlPattern + "') already exists!"); 73 | } 74 | MappedServlet newServlet = new MappedServlet(pattern, servlet, servletName); 75 | urlPatternContext.exactServlets.put(pattern, newServlet); 76 | log.debug("Curretn Exact URL Pattern List = " + Arrays.toString(urlPatternContext.exactServlets.keySet().toArray())); 77 | } 78 | } 79 | 80 | /** 81 | * 删除映射关系 82 | * 83 | * @param urlPattern 84 | */ 85 | public void removeServlet(String urlPattern) { 86 | if (urlPattern.endsWith("/*")) { 87 | //路径匹配 88 | String pattern = urlPattern.substring(0, urlPattern.length() - 2); 89 | urlPatternContext.wildcardServlets.removeIf(mappedServlet -> mappedServlet.pattern.equals(pattern)); 90 | } else if (urlPattern.startsWith("*.")) { 91 | // 扩展名匹配 92 | String pattern = urlPattern.substring(2); 93 | urlPatternContext.extensionServlets.remove(pattern); 94 | } else if (urlPattern.equals("/")) { 95 | // Default资源匹配 96 | urlPatternContext.defaultServlet = null; 97 | } else { 98 | // 精确匹配 99 | String pattern; 100 | if (urlPattern.length() == 0) { 101 | pattern = "/"; 102 | } else { 103 | pattern = urlPattern; 104 | } 105 | urlPatternContext.exactServlets.remove(pattern); 106 | } 107 | } 108 | 109 | public String getServletNameByRequestURI(String absoluteUri) { 110 | MappingData mappingData = new MappingData(); 111 | try { 112 | matchRequestPath(absoluteUri, mappingData); 113 | } catch (IOException e) { 114 | log.error("Throwing exception when getting Servlet Name by request URI, maybe cause by lacking of buffer size.", e); 115 | } 116 | return mappingData.servletName; 117 | } 118 | 119 | /** 120 | * Wrapper mapping. 121 | * 122 | * @throws IOException buffer大小不足 123 | */ 124 | private void matchRequestPath(String absolutePath, MappingData mappingData) throws IOException { 125 | // 处理ContextPath,获取访问的相对URI 126 | boolean noServletPath = absolutePath.equals(contextPath) || absolutePath.equals(contextPath + "/"); 127 | if (!absolutePath.startsWith(contextPath)) { 128 | return; 129 | } 130 | String path = noServletPath ? "/" : absolutePath.substring(contextPath.length()); 131 | //去掉查询字符串 132 | int queryInx = path.indexOf('?'); 133 | if(queryInx > -1){ 134 | path = path.substring(0, queryInx); 135 | } 136 | 137 | // 优先进行精确匹配 138 | internalMapExactWrapper(urlPatternContext.exactServlets, path, mappingData); 139 | 140 | // 然后进行路径匹配 141 | if (mappingData.servlet == null) { 142 | internalMapWildcardWrapper(urlPatternContext.wildcardServlets, path, mappingData); 143 | //TODO 暂不考虑JSP的处理 144 | } 145 | 146 | if (mappingData.servlet == null && noServletPath) { 147 | // 路径为空时,重定向到“/” 148 | mappingData.servlet = urlPatternContext.defaultServlet.object; 149 | mappingData.servletName = urlPatternContext.defaultServlet.servletName; 150 | return; 151 | } 152 | 153 | // 后缀名匹配 154 | if (mappingData.servlet == null) { 155 | internalMapExtensionWrapper(urlPatternContext.extensionServlets, path, mappingData); 156 | } 157 | 158 | //TODO 暂不考虑Welcome资源 159 | 160 | // Default Servlet 161 | if (mappingData.servlet == null) { 162 | if (urlPatternContext.defaultServlet != null) { 163 | mappingData.servlet = urlPatternContext.defaultServlet.object; 164 | mappingData.servletName = urlPatternContext.defaultServlet.servletName; 165 | } 166 | //TODO 暂不考虑请求静态目录资源 167 | if (path.charAt(path.length() - 1) != '/') { 168 | } 169 | } 170 | } 171 | 172 | 173 | /** 174 | * 精确匹配 175 | */ 176 | private void internalMapExactWrapper(Map servlets, String path, MappingData mappingData) { 177 | MappedServlet servlet = servlets.get(path); 178 | if (servlet != null) { 179 | mappingData.servlet = servlet.object; 180 | mappingData.servletName = servlet.servletName; 181 | } 182 | } 183 | 184 | /** 185 | * 路径匹配 186 | */ 187 | private void internalMapWildcardWrapper(List servlets, String path, MappingData mappingData) { 188 | if (!path.endsWith("/")) { 189 | path = path + "/"; 190 | } 191 | MappedServlet result = null; 192 | for (MappedServlet ms : servlets) { 193 | if (path.startsWith(ms.pattern)) { 194 | result = ms; 195 | break; 196 | } 197 | } 198 | if (result != null) { 199 | mappingData.servlet = result.object; 200 | mappingData.servletName = result.servletName; 201 | } 202 | } 203 | 204 | /** 205 | * 后缀名匹配 206 | */ 207 | private void internalMapExtensionWrapper(Map servlets, String path, MappingData mappingData) { 208 | int dotInx = path.lastIndexOf('.'); 209 | path = path.substring(dotInx + 1); 210 | MappedServlet servlet = servlets.get(path); 211 | if (servlet != null) { 212 | mappingData.servlet = servlet.object; 213 | mappingData.servletName = servlet.servletName; 214 | } 215 | } 216 | 217 | /* 218 | * 以下是用到的内部类 219 | */ 220 | 221 | private class UrlPatternContext { 222 | MappedServlet defaultServlet = null; //默认Servlet 223 | Map exactServlets = new HashMap<>(); //精确匹配 224 | List wildcardServlets = new LinkedList<>(); //路径匹配 225 | Map extensionServlets = new HashMap<>(); //扩展名匹配 226 | } 227 | 228 | private class MappedServlet extends MapElement { 229 | @Override 230 | public String toString() { 231 | return pattern; 232 | } 233 | 234 | String servletName; 235 | 236 | MappedServlet(String name, Servlet servlet, String servletName) { 237 | super(name, servlet); 238 | this.servletName = servletName; 239 | } 240 | } 241 | 242 | private class MapElement { 243 | final String pattern; 244 | final T object; 245 | 246 | MapElement(String pattern, T object) { 247 | this.pattern = pattern; 248 | this.object = object; 249 | } 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | io.gitlab.leibnizhu.sbnetty.bootstrap.EmbeddedNettyAutoConfiguration 4 | -------------------------------------------------------------------------------- /src/test/java/io/gitlab/leibnizhu/sbnetty/benchmark/NettyServletBenchmark.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.benchmark; 2 | 3 | import org.openjdk.jmh.annotations.*; 4 | import org.openjdk.jmh.runner.Runner; 5 | import org.openjdk.jmh.runner.RunnerException; 6 | import org.openjdk.jmh.runner.options.Options; 7 | import org.openjdk.jmh.runner.options.OptionsBuilder; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.net.HttpURLConnection; 14 | import java.net.URL; 15 | 16 | /** 17 | * @author Leibniz.Hu 18 | * Created on 2017-09-01 21:12. 19 | */ 20 | @BenchmarkMode(Mode.Throughput) 21 | //@OutputTimeUnit(TimeUnit.NANOSECONDS) 22 | public class NettyServletBenchmark { 23 | @Benchmark 24 | @Warmup(iterations = 10) 25 | @Measurement(iterations = 20) 26 | public void plaintext() { 27 | getUrl("http://localhost:9999/netty/plaintext", false); 28 | } 29 | 30 | @Benchmark 31 | @Warmup(iterations = 10) 32 | @Measurement(iterations = 20) 33 | public void json() { 34 | getUrl("http://localhost:9999/netty/json?msg=1", false); 35 | } 36 | 37 | public static void main(String[] args) throws RunnerException { 38 | Options opt = new OptionsBuilder() 39 | .include(".*" + NettyServletBenchmark.class.getSimpleName() + ".*") 40 | .forks(1) 41 | .build(); 42 | new Runner(opt).run(); 43 | } 44 | 45 | private String getUrl(String url, boolean read) { 46 | BufferedReader br = null; 47 | InputStream is = null; 48 | StringBuilder sbuf = new StringBuilder(); 49 | try { 50 | URL reqURL = new URL(url); 51 | HttpURLConnection connection = (HttpURLConnection) reqURL.openConnection(); // 进行连接,但是实际上getrequest要在下一句的connection.getInputStream() 函数中才会真正发到服务器 52 | connection.setDoOutput(false); 53 | connection.setUseCaches(false); 54 | connection.setRequestMethod("GET"); 55 | connection.setConnectTimeout(200); 56 | connection.setDoInput(true); 57 | connection.connect(); 58 | if (read) { 59 | br = new BufferedReader(new InputStreamReader(connection.getInputStream())); 60 | String line; 61 | while ((line = br.readLine()) != null) { 62 | sbuf.append(line).append("\n"); 63 | } 64 | } else { 65 | is = connection.getInputStream(); 66 | 67 | } 68 | } catch (IOException e) { 69 | System.out.println("连接服务器'" + url + "'时发生错误:" + e.getMessage()); 70 | } finally { 71 | try { 72 | if (null != br) { 73 | br.close(); 74 | } 75 | if (is != null) { 76 | is.close(); 77 | } 78 | } catch (IOException e) { 79 | e.printStackTrace(); 80 | } 81 | } 82 | return sbuf.toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/io/gitlab/leibnizhu/sbnetty/functional/CustomMVCConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.functional; 2 | 3 | import com.alibaba.fastjson.serializer.SerializerFeature; 4 | import com.alibaba.fastjson.support.config.FastJsonConfig; 5 | import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.converter.HttpMessageConverter; 10 | import org.springframework.http.converter.StringHttpMessageConverter; 11 | import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; 12 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 13 | 14 | import java.nio.charset.Charset; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * @author Leibniz.Hu 20 | * Created on 2017-08-31 21:13. 21 | */ 22 | @Configuration 23 | public class CustomMVCConfiguration extends WebMvcConfigurerAdapter { 24 | 25 | @Bean 26 | public HttpMessageConverter responseBodyConverter() { 27 | StringHttpMessageConverter conv = new StringHttpMessageConverter(Charset.forName("UTF-8")); 28 | conv.setWriteAcceptCharset(false); 29 | return conv; 30 | } 31 | 32 | @Bean 33 | FastJsonHttpMessageConverter getFastJsonConv() { 34 | FastJsonHttpMessageConverter conv = new FastJsonHttpMessageConverter(); 35 | 36 | FastJsonConfig oFastJsonConfig = new FastJsonConfig(); 37 | oFastJsonConfig.setSerializerFeatures( 38 | SerializerFeature.WriteMapNullValue, 39 | SerializerFeature.WriteDateUseDateFormat 40 | ); 41 | conv.setFastJsonConfig(oFastJsonConfig); 42 | 43 | List types = new ArrayList<>(); 44 | types.add(MediaType.APPLICATION_JSON_UTF8); 45 | types.add(MediaType.APPLICATION_JSON); 46 | conv.setSupportedMediaTypes(types); 47 | return conv; 48 | } 49 | 50 | @Override 51 | public void configureMessageConverters(List> converters) { 52 | super.configureMessageConverters(converters); 53 | converters.add(responseBodyConverter()); 54 | converters.add(getFastJsonConv()); 55 | } 56 | 57 | @Override 58 | public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 59 | configurer.favorPathExtension(false); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/io/gitlab/leibnizhu/sbnetty/functional/RequestUtlPatternMapperTest.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.functional; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.utils.RequestUrlPatternMapper; 4 | import org.junit.Test; 5 | import org.springframework.web.servlet.DispatcherServlet; 6 | 7 | import javax.servlet.Servlet; 8 | import javax.servlet.ServletException; 9 | 10 | /** 11 | * @author Leibniz.Hu 12 | * Created on 2017-08-27 10:25. 13 | */ 14 | public class RequestUtlPatternMapperTest { 15 | private RequestUrlPatternMapper mapper = new RequestUrlPatternMapper("/netty"); 16 | private String[] cases = new String[]{ 17 | "/netty/", 18 | "/netty/aaa", 19 | "/netty/jsonaa/a/dfd/json", 20 | "/netty/json/", 21 | "/netty/json/bggg", 22 | "/netty/json/a/dfd/json", 23 | "/netty/json/b/dfd/json", 24 | "/netty/json/a/b/dfd/json", 25 | "/netty/json/b/a/dfd/json" 26 | }; 27 | private double[] times = new double[cases.length]; 28 | private int totalCount = 100000; 29 | 30 | @Test 31 | public void testAll() throws ServletException { 32 | addPattern(); 33 | 34 | for (int i = 0; i < totalCount; i++) { 35 | testMatchs(); 36 | } 37 | testMatchs(true); 38 | } 39 | 40 | private void testMatchs() { 41 | testMatchs(false); 42 | } 43 | 44 | private void testMatchs(boolean showLog) { 45 | for(int i = 0; i< cases.length; i++){ 46 | testCase(cases[i], i, showLog); 47 | } 48 | } 49 | 50 | private void testCase(String s, int order) { 51 | testCase(s, order, false); 52 | } 53 | 54 | private void testCase(String s, int order, boolean showLog) { 55 | long t1 = System.nanoTime(); 56 | String result = mapper.getServletNameByRequestURI(s); 57 | times[order] = times[order] + (System.nanoTime() - t1); 58 | if (showLog) { 59 | System.out.println(s + " : " + result + ". Average cost time(ns): " + times[order] / totalCount + " ns"); 60 | } 61 | } 62 | 63 | private void addPattern() throws ServletException { 64 | Servlet obj = new DispatcherServlet(); 65 | mapper.addServlet("/json/a/*", obj, "a"); 66 | mapper.addServlet("/json/a/b/*", obj, "ab"); 67 | mapper.addServlet("/json/b/*", obj, "b"); 68 | mapper.addServlet("/json/*", obj, "jsonsall"); 69 | mapper.addServlet("/json/", obj, "jsonroot"); 70 | mapper.addServlet("/", obj, "root"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/io/gitlab/leibnizhu/sbnetty/functional/TestWebApp.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.functional; 2 | 3 | import java.io.IOException; 4 | import java.util.concurrent.Callable; 5 | 6 | import javax.servlet.ServletException; 7 | import javax.servlet.ServletInputStream; 8 | import javax.servlet.http.HttpServlet; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import javax.servlet.http.HttpSession; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.boot.SpringApplication; 16 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 17 | import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; 18 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.ComponentScan; 21 | import org.springframework.stereotype.Controller; 22 | import org.springframework.web.bind.annotation.RequestMapping; 23 | import org.springframework.web.bind.annotation.RequestMethod; 24 | import org.springframework.web.bind.annotation.RequestParam; 25 | import org.springframework.web.bind.annotation.ResponseBody; 26 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 27 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 28 | 29 | /** 30 | */ 31 | @Controller 32 | @EnableAutoConfiguration(exclude = WebMvcAutoConfiguration.class) 33 | @ComponentScan(basePackages = {"io.gitlab.leibnizhu.sbnetty"}) 34 | @EnableWebMvc 35 | public class TestWebApp extends WebMvcConfigurerAdapter { 36 | private Logger log = LoggerFactory.getLogger(getClass()); 37 | 38 | private static final String MESSAGE = "Hello, World!这是一条测试语句"; 39 | 40 | @RequestMapping(value = "/plaintext", produces = "text/plain; chartset=UTF-8") 41 | @ResponseBody 42 | public String plaintext() { 43 | return MESSAGE; 44 | } 45 | 46 | @RequestMapping(value = "/async", produces = "text/plain") 47 | @ResponseBody 48 | public Callable async() { 49 | return () -> MESSAGE; 50 | } 51 | 52 | @RequestMapping(value = "/json") 53 | @ResponseBody 54 | public Message json(@RequestParam String msg) { 55 | return new Message(MESSAGE + ". msg="+ msg); 56 | } 57 | 58 | @RequestMapping(value = "/session") 59 | @ResponseBody 60 | public Message session(@RequestParam String msg, HttpSession session, HttpServletRequest req) { 61 | if(session.getAttribute("aaa") == null){ 62 | session.setAttribute("aaa", msg); 63 | log.info("sessionId={}, setAttribute aaa={}", session.getId(), msg); 64 | } else { 65 | String oldMsg = (String) session.getAttribute("aaa"); 66 | log.info("sessionId={} is old Session, aaa={}, from Cookie:{}, from URL:{}, valid:{}", session.getId(), oldMsg, req.isRequestedSessionIdFromCookie(), req.isRequestedSessionIdFromURL(), req.isRequestedSessionIdValid()); 67 | } 68 | return new Message(MESSAGE + ". msg="+ msg); 69 | } 70 | 71 | @RequestMapping(value = "/upload", method = RequestMethod.POST) 72 | @ResponseBody 73 | public String upload(HttpServletRequest request) throws IOException { 74 | ServletInputStream inputStream = request.getInputStream(); 75 | int total = 0; 76 | while (true) { 77 | byte[] bytes = new byte[8192]; 78 | int read = inputStream.read(bytes); 79 | if (read == -1) { 80 | break; 81 | } 82 | total += read; 83 | } 84 | return "Total bytes received: " + total; 85 | } 86 | 87 | @RequestMapping("/sleepy") 88 | @ResponseBody 89 | public String sleepy() throws InterruptedException { 90 | int millis = 500; 91 | Thread.sleep(millis); 92 | return "Yawn! I slept for " + millis + "ms"; 93 | } 94 | 95 | @Bean 96 | public ServletRegistrationBean nullServletRegistration() { 97 | return new ServletRegistrationBean(new TestServlet(), "/null/*"); 98 | } 99 | 100 | private class TestServlet extends HttpServlet{ 101 | @Override 102 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 103 | resp.getOutputStream().print("Null Servlet Test"); 104 | resp.getOutputStream().flush(); 105 | resp.getOutputStream().close(); 106 | } 107 | } 108 | 109 | public static void main(String[] args) { 110 | SpringApplication.run(TestWebApp.class, args); 111 | } 112 | 113 | /* @Bean 114 | public HttpMessageConverter getConverter(){ 115 | StringHttpMessageConverter conv = new StringHttpMessageConverter(Charset.forName("UTF-8")); 116 | conv.setWriteAcceptCharset(false); 117 | // List types = new ArrayList<>(); 118 | // types.add(MediaType.TEXT_HTML); 119 | // types.add(MediaType.TEXT_PLAIN); 120 | // conv.setSupportedMediaTypes(types); 121 | return conv; 122 | }*/ 123 | 124 | /* 125 | @Override 126 | public void configureMessageConverters(List> converters) { 127 | super.configureMessageConverters(converters); 128 | StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8")); 129 | converter.setWriteAcceptCharset(false); 130 | converters.add(converter); 131 | } 132 | */ 133 | 134 | private static class Message { 135 | private final String message; 136 | 137 | public Message(String message) { 138 | this.message = message; 139 | } 140 | 141 | public String getMessage() { 142 | return message; 143 | } 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/test/java/io/gitlab/leibnizhu/sbnetty/functional/TestWebAppTester.java: -------------------------------------------------------------------------------- 1 | package io.gitlab.leibnizhu.sbnetty.functional; 2 | 3 | import io.gitlab.leibnizhu.sbnetty.bootstrap.EmbeddedNettyAutoConfiguration; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import org.springframework.test.web.servlet.MvcResult; 12 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 13 | import org.springframework.web.context.WebApplicationContext; 14 | 15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 16 | 17 | /** 18 | * Created by leibniz on 2017-08-24. 19 | */ 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @SpringBootTest(classes = EmbeddedNettyAutoConfiguration.class) 22 | public class TestWebAppTester { 23 | @Autowired 24 | private WebApplicationContext context; 25 | 26 | private MockMvc mockMvc; 27 | 28 | @Before 29 | public void setupMockMvc() { 30 | mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); 31 | } 32 | 33 | @Test 34 | public void jsonTest() throws Exception { 35 | MvcResult result = mockMvc.perform(get("http://localhost:9999/netty/json?msg=1abc")) 36 | // .andExpect(MockMvcResultMatchers.content().json("{\"message\":\"Hello, World!\"}")) 37 | .andReturn(); 38 | String resultStr = result.getResponse().getContentAsString(); 39 | System.out.println(resultStr); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | context-path: /netty 3 | port: 9999 -------------------------------------------------------------------------------- /src/test/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.BRIGHT_YELLOW} 2 | .____ .__ ___. .__ ___ ___ 3 | | | ____ |__|\_ |__ ____ |__|________ / | \ __ __ 4 | | | _/ __ \ | | | __ \ / \ | |\___ / / ~ \| | \ 5 | | |___\ ___/ | | | \_\ \| | \| | / / \ Y /| | / 6 | |_______ \\___ >|__| |___ /|___| /|__|/_____ \ /\\___|_ / |____/ 7 | \/ \/ \/ \/ \/ \/ \/ 8 | ${AnsiColor.BLUE} 9 | ::: Using Embedded Netty Servlet Container (version:${application.version}) ::: \(^O^)/ Spring-Boot ${spring-boot.version} --------------------------------------------------------------------------------