├── .gitignore ├── LICENSE ├── README.md ├── doc ├── rfc2616.txt ├── rfc6455.txt ├── rfc7230.txt ├── rfc7231.txt ├── rfc7232.txt ├── rfc7233.txt ├── rfc7234.txt └── rfc7235.txt ├── pom.xml ├── src ├── main │ └── java │ │ └── com │ │ └── pmeade │ │ └── websocket │ │ ├── example │ │ └── EchoServer.java │ │ ├── http │ │ ├── HttpRequest.java │ │ └── package-info.java │ │ ├── io │ │ ├── LineInputStream.java │ │ ├── WebSocketServerInputStream.java │ │ ├── WebSocketServerOutputStream.java │ │ └── package-info.java │ │ └── net │ │ ├── WebSocket.java │ │ ├── WebSocketServerSocket.java │ │ └── package-info.java └── test │ └── java │ └── com │ └── pmeade │ └── websocket │ ├── http │ └── HttpRequestTest.java │ ├── io │ ├── LineInputStreamTest.java │ ├── WebSocketServerInputStreamTest.java │ └── WebSocketServerOutputStreamTest.java │ └── net │ ├── WebSocketServerSocketTest.java │ └── WebSocketTest.java └── start-echo-server /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | /target/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 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 Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # websocket 2 | High quality low grade server side WebSocket (RFC 6455) implementation. 3 | 4 | ## Motivation 5 | Web requests are typically follow a request-response pattern. The client 6 | communicates the resource that it wants, and the server responds with the 7 | content that resource (or an error status code). After this the connection 8 | between client and server is closed and communication ends. 9 | 10 | WebSocket (RFC 6455) allows an HTTP client, such as a modern web browser, to 11 | request that the HTTP connection not close, but rather be upgraded to a 12 | communication channel for an on-going two-way exchange of binary and text data. 13 | Basically, the client says: Don't hang up yet, let's chat awhile. 14 | 15 | This fills an interesting niche in client-server communications. At first, 16 | it looks and acts (and actually, is) a web (HTTP) request. This allows the 17 | communication to get around most networking/firewall problems involved with 18 | opening a direct communication channel. Yet, after the WebSocket handshake, 19 | the channel acts very much like a traditional socket. The connection stays 20 | open and any data may be put on the wire. 21 | 22 | ## websocket 23 | This component is a server-side WebSocket implementation. By using the 24 | provided WebSocketServerSocket to decorate a ServerSocket, the server can 25 | listen for WebSocket connections and treat them like a traditional socket. 26 | 27 | WebSocketServerSocket itself is a ServerSocket. So, it can be used anywhere 28 | in your application that a ServerSocket might be used. WebSocketServerSocket 29 | can decorate any ServerSocket, including SSLServerSocket, if you want to 30 | support secure WebSocket connections. 31 | 32 | ## Usage 33 | This is how you start listening for WebSocket connections: 34 | 35 | ServerSocket serverSocket = new ServerSocket(PORT); 36 | WebSocketServerSocket webSocketServerSocket = new WebSocketServerSocket(serverSocket); 37 | WebSocket webSocket = webSocketServerSocket.accept(); 38 | 39 | And this is how you read data from the connecting client: 40 | 41 | InputStream is = webSocket.getInputStream(); 42 | int data = is.read(); 43 | 44 | And this is how you communicate back to the connecting client: 45 | 46 | WebSocketServerOutputStream os = webSocket.getOutputStream(); 47 | os.writeString("This is a UTF-8 string."); 48 | os.writeBinary("Some binary data.".getBytes()); 49 | 50 | Note that this usage is as raw as it gets, giving you the bytes sent over 51 | the WebSocket. 52 | 53 | ### Echo server example 54 | Included in this component is the example EchoServer. By running this, you 55 | can test the WebSocket implementation. 56 | 57 | mvn install 58 | ./start-echo-server 59 | 60 | Then point your browser to the following URL: 61 | 62 | * [http://www.websocket.org/echo.html](http://www.websocket.org/echo.html) 63 | 64 | Change the server to: 65 | 66 | localhost:8080 67 | 68 | You can then test that the server is echoing back the bytes that it has 69 | received. 70 | 71 | ## Features 72 | This WebSocket implementation is safe for very large frames. It does NOT 73 | allocate large buffers in anticipation of large frames. 74 | 75 | The code is: 76 | * Checkstyle clean 77 | * FindBugs clean 78 | * PMD/CPD clean 79 | 80 | The code has virtually 100% test coverage in Cobertura. 81 | 82 | ## Shortcomings 83 | This component is fairly specialized, and there are not a whole lot of 84 | options. 85 | 86 | ### Protocol opacity 87 | This component does its best to hide the details of the WebSocket protocol 88 | from the application. 89 | 90 | * No distinction is made between binary frames and text frames. 91 | * No markers indicate the start or end of frames in the stream. 92 | * Control frames (Ping, Pong, Close) are handled internally, and never 93 | visible to the application. 94 | 95 | If you need a nice clear channel to communicate strings or binary blobs 96 | between server and client, this component will meet your requirements. 97 | If you need to get into the guts of the WebSocket protocol, you'll 98 | need to modify the component. 99 | 100 | ### No channels 101 | Unlike a regular Socket or ServerSocket, you cannot call getChannel(). 102 | Attempting to do so will throw a UnsupportedOperationException. 103 | 104 | ## Development ideas 105 | These are some ideas that I might work on in the future. 106 | 107 | * Add a client side socket, to allow a Java process to connect to a 108 | server that speaks WebSocket. 109 | 110 | * Use Guava's EventBus to create an evented I/O object that speaks WebSocket. 111 | 112 | * Create a WebSocketChannel to provide WebSocket protocol support for NIO 113 | channels. 114 | 115 | ## License 116 | This program is free software: you can redistribute it and/or modify 117 | it under the terms of the [GNU Affero General Public License] 118 | (https://www.gnu.org/licenses/agpl-3.0.html) as published by the 119 | [Free Software Foundation](http://www.fsf.org/), either version 3 120 | of the License, or (at your option) any later version. 121 | 122 | This program is distributed in the hope that it will be useful, 123 | but WITHOUT ANY WARRANTY; without even the implied warranty of 124 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 125 | [GNU Affero General Public License] 126 | (https://www.gnu.org/licenses/agpl-3.0.html) for more details. 127 | 128 | You should have received a copy of the [GNU Affero General Public License] 129 | (https://www.gnu.org/licenses/agpl-3.0.html) along with this program. If 130 | not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). 131 | -------------------------------------------------------------------------------- /doc/rfc7235.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Internet Engineering Task Force (IETF) R. Fielding, Ed. 8 | Request for Comments: 7235 Adobe 9 | Obsoletes: 2616 J. Reschke, Ed. 10 | Updates: 2617 greenbytes 11 | Category: Standards Track June 2014 12 | ISSN: 2070-1721 13 | 14 | 15 | Hypertext Transfer Protocol (HTTP/1.1): Authentication 16 | 17 | Abstract 18 | 19 | The Hypertext Transfer Protocol (HTTP) is a stateless application- 20 | level protocol for distributed, collaborative, hypermedia information 21 | systems. This document defines the HTTP Authentication framework. 22 | 23 | Status of This Memo 24 | 25 | This is an Internet Standards Track document. 26 | 27 | This document is a product of the Internet Engineering Task Force 28 | (IETF). It represents the consensus of the IETF community. It has 29 | received public review and has been approved for publication by the 30 | Internet Engineering Steering Group (IESG). Further information on 31 | Internet Standards is available in Section 2 of RFC 5741. 32 | 33 | Information about the current status of this document, any errata, 34 | and how to provide feedback on it may be obtained at 35 | http://www.rfc-editor.org/info/rfc7235. 36 | 37 | Copyright Notice 38 | 39 | Copyright (c) 2014 IETF Trust and the persons identified as the 40 | document authors. All rights reserved. 41 | 42 | This document is subject to BCP 78 and the IETF Trust's Legal 43 | Provisions Relating to IETF Documents 44 | (http://trustee.ietf.org/license-info) in effect on the date of 45 | publication of this document. Please review these documents 46 | carefully, as they describe your rights and restrictions with respect 47 | to this document. Code Components extracted from this document must 48 | include Simplified BSD License text as described in Section 4.e of 49 | the Trust Legal Provisions and are provided without warranty as 50 | described in the Simplified BSD License. 51 | 52 | This document may contain material from IETF Documents or IETF 53 | Contributions published or made publicly available before November 54 | 10, 2008. The person(s) controlling the copyright in some of this 55 | 56 | 57 | 58 | Fielding & Reschke Standards Track [Page 1] 59 | 60 | RFC 7235 HTTP/1.1 Authentication June 2014 61 | 62 | 63 | material may not have granted the IETF Trust the right to allow 64 | modifications of such material outside the IETF Standards Process. 65 | Without obtaining an adequate license from the person(s) controlling 66 | the copyright in such materials, this document may not be modified 67 | outside the IETF Standards Process, and derivative works of it may 68 | not be created outside the IETF Standards Process, except to format 69 | it for publication as an RFC or to translate it into languages other 70 | than English. 71 | 72 | Table of Contents 73 | 74 | 1. Introduction ....................................................3 75 | 1.1. Conformance and Error Handling .............................3 76 | 1.2. Syntax Notation ............................................3 77 | 2. Access Authentication Framework .................................3 78 | 2.1. Challenge and Response .....................................3 79 | 2.2. Protection Space (Realm) ...................................5 80 | 3. Status Code Definitions .........................................6 81 | 3.1. 401 Unauthorized ...........................................6 82 | 3.2. 407 Proxy Authentication Required ..........................6 83 | 4. Header Field Definitions ........................................7 84 | 4.1. WWW-Authenticate ...........................................7 85 | 4.2. Authorization ..............................................8 86 | 4.3. Proxy-Authenticate .........................................8 87 | 4.4. Proxy-Authorization ........................................9 88 | 5. IANA Considerations .............................................9 89 | 5.1. Authentication Scheme Registry .............................9 90 | 5.1.1. Procedure ...........................................9 91 | 5.1.2. Considerations for New Authentication Schemes ......10 92 | 5.2. Status Code Registration ..................................11 93 | 5.3. Header Field Registration .................................11 94 | 6. Security Considerations ........................................12 95 | 6.1. Confidentiality of Credentials ............................12 96 | 6.2. Authentication Credentials and Idle Clients ...............12 97 | 6.3. Protection Spaces .........................................13 98 | 7. Acknowledgments ................................................14 99 | 8. References .....................................................14 100 | 8.1. Normative References ......................................14 101 | 8.2. Informative References ....................................14 102 | Appendix A. Changes from RFCs 2616 and 2617 .......................16 103 | Appendix B. Imported ABNF .........................................16 104 | Appendix C. Collected ABNF ........................................17 105 | Index .............................................................18 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Fielding & Reschke Standards Track [Page 2] 115 | 116 | RFC 7235 HTTP/1.1 Authentication June 2014 117 | 118 | 119 | 1. Introduction 120 | 121 | HTTP provides a general framework for access control and 122 | authentication, via an extensible set of challenge-response 123 | authentication schemes, which can be used by a server to challenge a 124 | client request and by a client to provide authentication information. 125 | This document defines HTTP/1.1 authentication in terms of the 126 | architecture defined in "Hypertext Transfer Protocol (HTTP/1.1): 127 | Message Syntax and Routing" [RFC7230], including the general 128 | framework previously described in "HTTP Authentication: Basic and 129 | Digest Access Authentication" [RFC2617] and the related fields and 130 | status codes previously defined in "Hypertext Transfer Protocol -- 131 | HTTP/1.1" [RFC2616]. 132 | 133 | The IANA Authentication Scheme Registry (Section 5.1) lists 134 | registered authentication schemes and their corresponding 135 | specifications, including the "basic" and "digest" authentication 136 | schemes previously defined by RFC 2617. 137 | 138 | 1.1. Conformance and Error Handling 139 | 140 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 141 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 142 | document are to be interpreted as described in [RFC2119]. 143 | 144 | Conformance criteria and considerations regarding error handling are 145 | defined in Section 2.5 of [RFC7230]. 146 | 147 | 1.2. Syntax Notation 148 | 149 | This specification uses the Augmented Backus-Naur Form (ABNF) 150 | notation of [RFC5234] with a list extension, defined in Section 7 of 151 | [RFC7230], that allows for compact definition of comma-separated 152 | lists using a '#' operator (similar to how the '*' operator indicates 153 | repetition). Appendix B describes rules imported from other 154 | documents. Appendix C shows the collected grammar with all list 155 | operators expanded to standard ABNF notation. 156 | 157 | 2. Access Authentication Framework 158 | 159 | 2.1. Challenge and Response 160 | 161 | HTTP provides a simple challenge-response authentication framework 162 | that can be used by a server to challenge a client request and by a 163 | client to provide authentication information. It uses a case- 164 | insensitive token as a means to identify the authentication scheme, 165 | followed by additional information necessary for achieving 166 | 167 | 168 | 169 | 170 | Fielding & Reschke Standards Track [Page 3] 171 | 172 | RFC 7235 HTTP/1.1 Authentication June 2014 173 | 174 | 175 | authentication via that scheme. The latter can be either a comma- 176 | separated list of parameters or a single sequence of characters 177 | capable of holding base64-encoded information. 178 | 179 | Authentication parameters are name=value pairs, where the name token 180 | is matched case-insensitively, and each parameter name MUST only 181 | occur once per challenge. 182 | 183 | auth-scheme = token 184 | 185 | auth-param = token BWS "=" BWS ( token / quoted-string ) 186 | 187 | token68 = 1*( ALPHA / DIGIT / 188 | "-" / "." / "_" / "~" / "+" / "/" ) *"=" 189 | 190 | The token68 syntax allows the 66 unreserved URI characters 191 | ([RFC3986]), plus a few others, so that it can hold a base64, 192 | base64url (URL and filename safe alphabet), base32, or base16 (hex) 193 | encoding, with or without padding, but excluding whitespace 194 | ([RFC4648]). 195 | 196 | A 401 (Unauthorized) response message is used by an origin server to 197 | challenge the authorization of a user agent, including a 198 | WWW-Authenticate header field containing at least one challenge 199 | applicable to the requested resource. 200 | 201 | A 407 (Proxy Authentication Required) response message is used by a 202 | proxy to challenge the authorization of a client, including a 203 | Proxy-Authenticate header field containing at least one challenge 204 | applicable to the proxy for the requested resource. 205 | 206 | challenge = auth-scheme [ 1*SP ( token68 / #auth-param ) ] 207 | 208 | Note: Many clients fail to parse a challenge that contains an 209 | unknown scheme. A workaround for this problem is to list well- 210 | supported schemes (such as "basic") first. 211 | 212 | A user agent that wishes to authenticate itself with an origin server 213 | -- usually, but not necessarily, after receiving a 401 (Unauthorized) 214 | -- can do so by including an Authorization header field with the 215 | request. 216 | 217 | A client that wishes to authenticate itself with a proxy -- usually, 218 | but not necessarily, after receiving a 407 (Proxy Authentication 219 | Required) -- can do so by including a Proxy-Authorization header 220 | field with the request. 221 | 222 | 223 | 224 | 225 | 226 | Fielding & Reschke Standards Track [Page 4] 227 | 228 | RFC 7235 HTTP/1.1 Authentication June 2014 229 | 230 | 231 | Both the Authorization field value and the Proxy-Authorization field 232 | value contain the client's credentials for the realm of the resource 233 | being requested, based upon a challenge received in a response 234 | (possibly at some point in the past). When creating their values, 235 | the user agent ought to do so by selecting the challenge with what it 236 | considers to be the most secure auth-scheme that it understands, 237 | obtaining credentials from the user as appropriate. Transmission of 238 | credentials within header field values implies significant security 239 | considerations regarding the confidentiality of the underlying 240 | connection, as described in Section 6.1. 241 | 242 | credentials = auth-scheme [ 1*SP ( token68 / #auth-param ) ] 243 | 244 | Upon receipt of a request for a protected resource that omits 245 | credentials, contains invalid credentials (e.g., a bad password) or 246 | partial credentials (e.g., when the authentication scheme requires 247 | more than one round trip), an origin server SHOULD send a 401 248 | (Unauthorized) response that contains a WWW-Authenticate header field 249 | with at least one (possibly new) challenge applicable to the 250 | requested resource. 251 | 252 | Likewise, upon receipt of a request that omits proxy credentials or 253 | contains invalid or partial proxy credentials, a proxy that requires 254 | authentication SHOULD generate a 407 (Proxy Authentication Required) 255 | response that contains a Proxy-Authenticate header field with at 256 | least one (possibly new) challenge applicable to the proxy. 257 | 258 | A server that receives valid credentials that are not adequate to 259 | gain access ought to respond with the 403 (Forbidden) status code 260 | (Section 6.5.3 of [RFC7231]). 261 | 262 | HTTP does not restrict applications to this simple challenge-response 263 | framework for access authentication. Additional mechanisms can be 264 | used, such as authentication at the transport level or via message 265 | encapsulation, and with additional header fields specifying 266 | authentication information. However, such additional mechanisms are 267 | not defined by this specification. 268 | 269 | 2.2. Protection Space (Realm) 270 | 271 | The "realm" authentication parameter is reserved for use by 272 | authentication schemes that wish to indicate a scope of protection. 273 | 274 | A protection space is defined by the canonical root URI (the scheme 275 | and authority components of the effective request URI; see Section 276 | 5.5 of [RFC7230]) of the server being accessed, in combination with 277 | the realm value if present. These realms allow the protected 278 | resources on a server to be partitioned into a set of protection 279 | 280 | 281 | 282 | Fielding & Reschke Standards Track [Page 5] 283 | 284 | RFC 7235 HTTP/1.1 Authentication June 2014 285 | 286 | 287 | spaces, each with its own authentication scheme and/or authorization 288 | database. The realm value is a string, generally assigned by the 289 | origin server, that can have additional semantics specific to the 290 | authentication scheme. Note that a response can have multiple 291 | challenges with the same auth-scheme but with different realms. 292 | 293 | The protection space determines the domain over which credentials can 294 | be automatically applied. If a prior request has been authorized, 295 | the user agent MAY reuse the same credentials for all other requests 296 | within that protection space for a period of time determined by the 297 | authentication scheme, parameters, and/or user preferences (such as a 298 | configurable inactivity timeout). Unless specifically allowed by the 299 | authentication scheme, a single protection space cannot extend 300 | outside the scope of its server. 301 | 302 | For historical reasons, a sender MUST only generate the quoted-string 303 | syntax. Recipients might have to support both token and 304 | quoted-string syntax for maximum interoperability with existing 305 | clients that have been accepting both notations for a long time. 306 | 307 | 3. Status Code Definitions 308 | 309 | 3.1. 401 Unauthorized 310 | 311 | The 401 (Unauthorized) status code indicates that the request has not 312 | been applied because it lacks valid authentication credentials for 313 | the target resource. The server generating a 401 response MUST send 314 | a WWW-Authenticate header field (Section 4.1) containing at least one 315 | challenge applicable to the target resource. 316 | 317 | If the request included authentication credentials, then the 401 318 | response indicates that authorization has been refused for those 319 | credentials. The user agent MAY repeat the request with a new or 320 | replaced Authorization header field (Section 4.2). If the 401 321 | response contains the same challenge as the prior response, and the 322 | user agent has already attempted authentication at least once, then 323 | the user agent SHOULD present the enclosed representation to the 324 | user, since it usually contains relevant diagnostic information. 325 | 326 | 3.2. 407 Proxy Authentication Required 327 | 328 | The 407 (Proxy Authentication Required) status code is similar to 401 329 | (Unauthorized), but it indicates that the client needs to 330 | authenticate itself in order to use a proxy. The proxy MUST send a 331 | Proxy-Authenticate header field (Section 4.3) containing a challenge 332 | applicable to that proxy for the target resource. The client MAY 333 | repeat the request with a new or replaced Proxy-Authorization header 334 | field (Section 4.4). 335 | 336 | 337 | 338 | Fielding & Reschke Standards Track [Page 6] 339 | 340 | RFC 7235 HTTP/1.1 Authentication June 2014 341 | 342 | 343 | 4. Header Field Definitions 344 | 345 | This section defines the syntax and semantics of header fields 346 | related to the HTTP authentication framework. 347 | 348 | 4.1. WWW-Authenticate 349 | 350 | The "WWW-Authenticate" header field indicates the authentication 351 | scheme(s) and parameters applicable to the target resource. 352 | 353 | WWW-Authenticate = 1#challenge 354 | 355 | A server generating a 401 (Unauthorized) response MUST send a 356 | WWW-Authenticate header field containing at least one challenge. A 357 | server MAY generate a WWW-Authenticate header field in other response 358 | messages to indicate that supplying credentials (or different 359 | credentials) might affect the response. 360 | 361 | A proxy forwarding a response MUST NOT modify any WWW-Authenticate 362 | fields in that response. 363 | 364 | User agents are advised to take special care in parsing the field 365 | value, as it might contain more than one challenge, and each 366 | challenge can contain a comma-separated list of authentication 367 | parameters. Furthermore, the header field itself can occur multiple 368 | times. 369 | 370 | For instance: 371 | 372 | WWW-Authenticate: Newauth realm="apps", type=1, 373 | title="Login to \"apps\"", Basic realm="simple" 374 | 375 | This header field contains two challenges; one for the "Newauth" 376 | scheme with a realm value of "apps", and two additional parameters 377 | "type" and "title", and another one for the "Basic" scheme with a 378 | realm value of "simple". 379 | 380 | Note: The challenge grammar production uses the list syntax as 381 | well. Therefore, a sequence of comma, whitespace, and comma can 382 | be considered either as applying to the preceding challenge, or to 383 | be an empty entry in the list of challenges. In practice, this 384 | ambiguity does not affect the semantics of the header field value 385 | and thus is harmless. 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | Fielding & Reschke Standards Track [Page 7] 395 | 396 | RFC 7235 HTTP/1.1 Authentication June 2014 397 | 398 | 399 | 4.2. Authorization 400 | 401 | The "Authorization" header field allows a user agent to authenticate 402 | itself with an origin server -- usually, but not necessarily, after 403 | receiving a 401 (Unauthorized) response. Its value consists of 404 | credentials containing the authentication information of the user 405 | agent for the realm of the resource being requested. 406 | 407 | Authorization = credentials 408 | 409 | If a request is authenticated and a realm specified, the same 410 | credentials are presumed to be valid for all other requests within 411 | this realm (assuming that the authentication scheme itself does not 412 | require otherwise, such as credentials that vary according to a 413 | challenge value or using synchronized clocks). 414 | 415 | A proxy forwarding a request MUST NOT modify any Authorization fields 416 | in that request. See Section 3.2 of [RFC7234] for details of and 417 | requirements pertaining to handling of the Authorization field by 418 | HTTP caches. 419 | 420 | 4.3. Proxy-Authenticate 421 | 422 | The "Proxy-Authenticate" header field consists of at least one 423 | challenge that indicates the authentication scheme(s) and parameters 424 | applicable to the proxy for this effective request URI (Section 5.5 425 | of [RFC7230]). A proxy MUST send at least one Proxy-Authenticate 426 | header field in each 407 (Proxy Authentication Required) response 427 | that it generates. 428 | 429 | Proxy-Authenticate = 1#challenge 430 | 431 | Unlike WWW-Authenticate, the Proxy-Authenticate header field applies 432 | only to the next outbound client on the response chain. This is 433 | because only the client that chose a given proxy is likely to have 434 | the credentials necessary for authentication. However, when multiple 435 | proxies are used within the same administrative domain, such as 436 | office and regional caching proxies within a large corporate network, 437 | it is common for credentials to be generated by the user agent and 438 | passed through the hierarchy until consumed. Hence, in such a 439 | configuration, it will appear as if Proxy-Authenticate is being 440 | forwarded because each proxy will send the same challenge set. 441 | 442 | Note that the parsing considerations for WWW-Authenticate apply to 443 | this header field as well; see Section 4.1 for details. 444 | 445 | 446 | 447 | 448 | 449 | 450 | Fielding & Reschke Standards Track [Page 8] 451 | 452 | RFC 7235 HTTP/1.1 Authentication June 2014 453 | 454 | 455 | 4.4. Proxy-Authorization 456 | 457 | The "Proxy-Authorization" header field allows the client to identify 458 | itself (or its user) to a proxy that requires authentication. Its 459 | value consists of credentials containing the authentication 460 | information of the client for the proxy and/or realm of the resource 461 | being requested. 462 | 463 | Proxy-Authorization = credentials 464 | 465 | Unlike Authorization, the Proxy-Authorization header field applies 466 | only to the next inbound proxy that demanded authentication using the 467 | Proxy-Authenticate field. When multiple proxies are used in a chain, 468 | the Proxy-Authorization header field is consumed by the first inbound 469 | proxy that was expecting to receive credentials. A proxy MAY relay 470 | the credentials from the client request to the next proxy if that is 471 | the mechanism by which the proxies cooperatively authenticate a given 472 | request. 473 | 474 | 5. IANA Considerations 475 | 476 | 5.1. Authentication Scheme Registry 477 | 478 | The "Hypertext Transfer Protocol (HTTP) Authentication Scheme 479 | Registry" defines the namespace for the authentication schemes in 480 | challenges and credentials. It has been created and is now 481 | maintained at . 482 | 483 | 5.1.1. Procedure 484 | 485 | Registrations MUST include the following fields: 486 | 487 | o Authentication Scheme Name 488 | 489 | o Pointer to specification text 490 | 491 | o Notes (optional) 492 | 493 | Values to be added to this namespace require IETF Review (see 494 | [RFC5226], Section 4.1). 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Fielding & Reschke Standards Track [Page 9] 507 | 508 | RFC 7235 HTTP/1.1 Authentication June 2014 509 | 510 | 511 | 5.1.2. Considerations for New Authentication Schemes 512 | 513 | There are certain aspects of the HTTP Authentication Framework that 514 | put constraints on how new authentication schemes can work: 515 | 516 | o HTTP authentication is presumed to be stateless: all of the 517 | information necessary to authenticate a request MUST be provided 518 | in the request, rather than be dependent on the server remembering 519 | prior requests. Authentication based on, or bound to, the 520 | underlying connection is outside the scope of this specification 521 | and inherently flawed unless steps are taken to ensure that the 522 | connection cannot be used by any party other than the 523 | authenticated user (see Section 2.3 of [RFC7230]). 524 | 525 | o The authentication parameter "realm" is reserved for defining 526 | protection spaces as described in Section 2.2. New schemes MUST 527 | NOT use it in a way incompatible with that definition. 528 | 529 | o The "token68" notation was introduced for compatibility with 530 | existing authentication schemes and can only be used once per 531 | challenge or credential. Thus, new schemes ought to use the 532 | auth-param syntax instead, because otherwise future extensions 533 | will be impossible. 534 | 535 | o The parsing of challenges and credentials is defined by this 536 | specification and cannot be modified by new authentication 537 | schemes. When the auth-param syntax is used, all parameters ought 538 | to support both token and quoted-string syntax, and syntactical 539 | constraints ought to be defined on the field value after parsing 540 | (i.e., quoted-string processing). This is necessary so that 541 | recipients can use a generic parser that applies to all 542 | authentication schemes. 543 | 544 | Note: The fact that the value syntax for the "realm" parameter is 545 | restricted to quoted-string was a bad design choice not to be 546 | repeated for new parameters. 547 | 548 | o Definitions of new schemes ought to define the treatment of 549 | unknown extension parameters. In general, a "must-ignore" rule is 550 | preferable to a "must-understand" rule, because otherwise it will 551 | be hard to introduce new parameters in the presence of legacy 552 | recipients. Furthermore, it's good to describe the policy for 553 | defining new parameters (such as "update the specification" or 554 | "use this registry"). 555 | 556 | o Authentication schemes need to document whether they are usable in 557 | origin-server authentication (i.e., using WWW-Authenticate), 558 | and/or proxy authentication (i.e., using Proxy-Authenticate). 559 | 560 | 561 | 562 | Fielding & Reschke Standards Track [Page 10] 563 | 564 | RFC 7235 HTTP/1.1 Authentication June 2014 565 | 566 | 567 | o The credentials carried in an Authorization header field are 568 | specific to the user agent and, therefore, have the same effect on 569 | HTTP caches as the "private" Cache-Control response directive 570 | (Section 5.2.2.6 of [RFC7234]), within the scope of the request in 571 | which they appear. 572 | 573 | Therefore, new authentication schemes that choose not to carry 574 | credentials in the Authorization header field (e.g., using a newly 575 | defined header field) will need to explicitly disallow caching, by 576 | mandating the use of either Cache-Control request directives 577 | (e.g., "no-store", Section 5.2.1.5 of [RFC7234]) or response 578 | directives (e.g., "private"). 579 | 580 | 5.2. Status Code Registration 581 | 582 | The "Hypertext Transfer Protocol (HTTP) Status Code Registry" located 583 | at has been 584 | updated with the registrations below: 585 | 586 | +-------+-------------------------------+-------------+ 587 | | Value | Description | Reference | 588 | +-------+-------------------------------+-------------+ 589 | | 401 | Unauthorized | Section 3.1 | 590 | | 407 | Proxy Authentication Required | Section 3.2 | 591 | +-------+-------------------------------+-------------+ 592 | 593 | 5.3. Header Field Registration 594 | 595 | HTTP header fields are registered within the "Message Headers" 596 | registry maintained at 597 | . 598 | 599 | This document defines the following HTTP header fields, so the 600 | "Permanent Message Header Field Names" registry has been updated 601 | accordingly (see [BCP90]). 602 | 603 | +---------------------+----------+----------+-------------+ 604 | | Header Field Name | Protocol | Status | Reference | 605 | +---------------------+----------+----------+-------------+ 606 | | Authorization | http | standard | Section 4.2 | 607 | | Proxy-Authenticate | http | standard | Section 4.3 | 608 | | Proxy-Authorization | http | standard | Section 4.4 | 609 | | WWW-Authenticate | http | standard | Section 4.1 | 610 | +---------------------+----------+----------+-------------+ 611 | 612 | The change controller is: "IETF (iesg@ietf.org) - Internet 613 | Engineering Task Force". 614 | 615 | 616 | 617 | 618 | Fielding & Reschke Standards Track [Page 11] 619 | 620 | RFC 7235 HTTP/1.1 Authentication June 2014 621 | 622 | 623 | 6. Security Considerations 624 | 625 | This section is meant to inform developers, information providers, 626 | and users of known security concerns specific to HTTP authentication. 627 | More general security considerations are addressed in HTTP messaging 628 | [RFC7230] and semantics [RFC7231]. 629 | 630 | Everything about the topic of HTTP authentication is a security 631 | consideration, so the list of considerations below is not exhaustive. 632 | Furthermore, it is limited to security considerations regarding the 633 | authentication framework, in general, rather than discussing all of 634 | the potential considerations for specific authentication schemes 635 | (which ought to be documented in the specifications that define those 636 | schemes). Various organizations maintain topical information and 637 | links to current research on Web application security (e.g., 638 | [OWASP]), including common pitfalls for implementing and using the 639 | authentication schemes found in practice. 640 | 641 | 6.1. Confidentiality of Credentials 642 | 643 | The HTTP authentication framework does not define a single mechanism 644 | for maintaining the confidentiality of credentials; instead, each 645 | authentication scheme defines how the credentials are encoded prior 646 | to transmission. While this provides flexibility for the development 647 | of future authentication schemes, it is inadequate for the protection 648 | of existing schemes that provide no confidentiality on their own, or 649 | that do not sufficiently protect against replay attacks. 650 | Furthermore, if the server expects credentials that are specific to 651 | each individual user, the exchange of those credentials will have the 652 | effect of identifying that user even if the content within 653 | credentials remains confidential. 654 | 655 | HTTP depends on the security properties of the underlying transport- 656 | or session-level connection to provide confidential transmission of 657 | header fields. In other words, if a server limits access to 658 | authenticated users using this framework, the server needs to ensure 659 | that the connection is properly secured in accordance with the nature 660 | of the authentication scheme used. For example, services that depend 661 | on individual user authentication often require a connection to be 662 | secured with TLS ("Transport Layer Security", [RFC5246]) prior to 663 | exchanging any credentials. 664 | 665 | 6.2. Authentication Credentials and Idle Clients 666 | 667 | Existing HTTP clients and user agents typically retain authentication 668 | information indefinitely. HTTP does not provide a mechanism for the 669 | origin server to direct clients to discard these cached credentials, 670 | since the protocol has no awareness of how credentials are obtained 671 | 672 | 673 | 674 | Fielding & Reschke Standards Track [Page 12] 675 | 676 | RFC 7235 HTTP/1.1 Authentication June 2014 677 | 678 | 679 | or managed by the user agent. The mechanisms for expiring or 680 | revoking credentials can be specified as part of an authentication 681 | scheme definition. 682 | 683 | Circumstances under which credential caching can interfere with the 684 | application's security model include but are not limited to: 685 | 686 | o Clients that have been idle for an extended period, following 687 | which the server might wish to cause the client to re-prompt the 688 | user for credentials. 689 | 690 | o Applications that include a session termination indication (such 691 | as a "logout" or "commit" button on a page) after which the server 692 | side of the application "knows" that there is no further reason 693 | for the client to retain the credentials. 694 | 695 | User agents that cache credentials are encouraged to provide a 696 | readily accessible mechanism for discarding cached credentials under 697 | user control. 698 | 699 | 6.3. Protection Spaces 700 | 701 | Authentication schemes that solely rely on the "realm" mechanism for 702 | establishing a protection space will expose credentials to all 703 | resources on an origin server. Clients that have successfully made 704 | authenticated requests with a resource can use the same 705 | authentication credentials for other resources on the same origin 706 | server. This makes it possible for a different resource to harvest 707 | authentication credentials for other resources. 708 | 709 | This is of particular concern when an origin server hosts resources 710 | for multiple parties under the same canonical root URI (Section 2.2). 711 | Possible mitigation strategies include restricting direct access to 712 | authentication credentials (i.e., not making the content of the 713 | Authorization request header field available), and separating 714 | protection spaces by using a different host name (or port number) for 715 | each party. 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | Fielding & Reschke Standards Track [Page 13] 731 | 732 | RFC 7235 HTTP/1.1 Authentication June 2014 733 | 734 | 735 | 7. Acknowledgments 736 | 737 | This specification takes over the definition of the HTTP 738 | Authentication Framework, previously defined in RFC 2617. We thank 739 | John Franks, Phillip M. Hallam-Baker, Jeffery L. Hostetler, Scott D. 740 | Lawrence, Paul J. Leach, Ari Luotonen, and Lawrence C. Stewart for 741 | their work on that specification. See Section 6 of [RFC2617] for 742 | further acknowledgements. 743 | 744 | See Section 10 of [RFC7230] for the Acknowledgments related to this 745 | document revision. 746 | 747 | 8. References 748 | 749 | 8.1. Normative References 750 | 751 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 752 | Requirement Levels", BCP 14, RFC 2119, March 1997. 753 | 754 | [RFC5234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax 755 | Specifications: ABNF", STD 68, RFC 5234, January 2008. 756 | 757 | [RFC7230] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer 758 | Protocol (HTTP/1.1): Message Syntax and Routing", 759 | RFC 7230, June 2014. 760 | 761 | [RFC7231] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer 762 | Protocol (HTTP/1.1): Semantics and Content", RFC 7231, 763 | June 2014. 764 | 765 | [RFC7234] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, 766 | Ed., "Hypertext Transfer Protocol (HTTP/1.1): Caching", 767 | RFC 7234, June 2014. 768 | 769 | 8.2. Informative References 770 | 771 | [BCP90] Klyne, G., Nottingham, M., and J. Mogul, "Registration 772 | Procedures for Message Header Fields", BCP 90, RFC 3864, 773 | September 2004. 774 | 775 | [OWASP] van der Stock, A., Ed., "A Guide to Building Secure Web 776 | Applications and Web Services", The Open Web Application 777 | Security Project (OWASP) 2.0.1, July 2005, 778 | . 779 | 780 | [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., 781 | Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext 782 | Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999. 783 | 784 | 785 | 786 | Fielding & Reschke Standards Track [Page 14] 787 | 788 | RFC 7235 HTTP/1.1 Authentication June 2014 789 | 790 | 791 | [RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., 792 | Leach, P., Luotonen, A., and L. Stewart, "HTTP 793 | Authentication: Basic and Digest Access Authentication", 794 | RFC 2617, June 1999. 795 | 796 | [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform 797 | Resource Identifier (URI): Generic Syntax", STD 66, 798 | RFC 3986, January 2005. 799 | 800 | [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data 801 | Encodings", RFC 4648, October 2006. 802 | 803 | [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an 804 | IANA Considerations Section in RFCs", BCP 26, RFC 5226, 805 | May 2008. 806 | 807 | [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security 808 | (TLS) Protocol Version 1.2", RFC 5246, August 2008. 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | Fielding & Reschke Standards Track [Page 15] 843 | 844 | RFC 7235 HTTP/1.1 Authentication June 2014 845 | 846 | 847 | Appendix A. Changes from RFCs 2616 and 2617 848 | 849 | The framework for HTTP Authentication is now defined by this 850 | document, rather than RFC 2617. 851 | 852 | The "realm" parameter is no longer always required on challenges; 853 | consequently, the ABNF allows challenges without any auth parameters. 854 | (Section 2) 855 | 856 | The "token68" alternative to auth-param lists has been added for 857 | consistency with legacy authentication schemes such as "Basic". 858 | (Section 2) 859 | 860 | This specification introduces the Authentication Scheme Registry, 861 | along with considerations for new authentication schemes. 862 | (Section 5.1) 863 | 864 | Appendix B. Imported ABNF 865 | 866 | The following core rules are included by reference, as defined in 867 | Appendix B.1 of [RFC5234]: ALPHA (letters), CR (carriage return), 868 | CRLF (CR LF), CTL (controls), DIGIT (decimal 0-9), DQUOTE (double 869 | quote), HEXDIG (hexadecimal 0-9/A-F/a-f), LF (line feed), OCTET (any 870 | 8-bit sequence of data), SP (space), and VCHAR (any visible US-ASCII 871 | character). 872 | 873 | The rules below are defined in [RFC7230]: 874 | 875 | BWS = 876 | OWS = 877 | quoted-string = 878 | token = 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | Fielding & Reschke Standards Track [Page 16] 899 | 900 | RFC 7235 HTTP/1.1 Authentication June 2014 901 | 902 | 903 | Appendix C. Collected ABNF 904 | 905 | In the collected ABNF below, list rules are expanded as per Section 906 | 1.2 of [RFC7230]. 907 | 908 | Authorization = credentials 909 | 910 | BWS = 911 | 912 | OWS = 913 | 914 | Proxy-Authenticate = *( "," OWS ) challenge *( OWS "," [ OWS 915 | challenge ] ) 916 | Proxy-Authorization = credentials 917 | 918 | WWW-Authenticate = *( "," OWS ) challenge *( OWS "," [ OWS challenge 919 | ] ) 920 | 921 | auth-param = token BWS "=" BWS ( token / quoted-string ) 922 | auth-scheme = token 923 | 924 | challenge = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param ) *( 925 | OWS "," [ OWS auth-param ] ) ] ) ] 926 | credentials = auth-scheme [ 1*SP ( token68 / [ ( "," / auth-param ) 927 | *( OWS "," [ OWS auth-param ] ) ] ) ] 928 | 929 | quoted-string = 930 | 931 | token = 932 | token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) 933 | *"=" 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | Fielding & Reschke Standards Track [Page 17] 955 | 956 | RFC 7235 HTTP/1.1 Authentication June 2014 957 | 958 | 959 | Index 960 | 961 | 4 962 | 401 Unauthorized (status code) 6 963 | 407 Proxy Authentication Required (status code) 6 964 | 965 | A 966 | Authorization header field 8 967 | 968 | C 969 | Canonical Root URI 5 970 | 971 | G 972 | Grammar 973 | auth-param 4 974 | auth-scheme 4 975 | Authorization 8 976 | challenge 4 977 | credentials 5 978 | Proxy-Authenticate 8 979 | Proxy-Authorization 9 980 | token68 4 981 | WWW-Authenticate 7 982 | 983 | P 984 | Protection Space 5 985 | Proxy-Authenticate header field 8 986 | Proxy-Authorization header field 9 987 | 988 | R 989 | Realm 5 990 | 991 | W 992 | WWW-Authenticate header field 7 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | Fielding & Reschke Standards Track [Page 18] 1011 | 1012 | RFC 7235 HTTP/1.1 Authentication June 2014 1013 | 1014 | 1015 | Authors' Addresses 1016 | 1017 | Roy T. Fielding (editor) 1018 | Adobe Systems Incorporated 1019 | 345 Park Ave 1020 | San Jose, CA 95110 1021 | USA 1022 | 1023 | EMail: fielding@gbiv.com 1024 | URI: http://roy.gbiv.com/ 1025 | 1026 | 1027 | Julian F. Reschke (editor) 1028 | greenbytes GmbH 1029 | Hafenweg 16 1030 | Muenster, NW 48155 1031 | Germany 1032 | 1033 | EMail: julian.reschke@greenbytes.de 1034 | URI: http://greenbytes.de/tech/webdav/ 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | Fielding & Reschke Standards Track [Page 19] 1067 | 1068 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 25 | 26 | 4.0.0 27 | com.pmeade 28 | websocket 29 | 1.0-SNAPSHOT 30 | jar 31 | 32 | 33 | 1.5 34 | 1.5 35 | UTF-8 36 | 37 | 38 | 39 | 40 | 41 | org.apache.maven.plugins 42 | maven-dependency-plugin 43 | 2.8 44 | 45 | 46 | copy-dependencies 47 | package 48 | 49 | copy-dependencies 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-checkstyle-plugin 62 | 2.12.1 63 | 64 | true 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-javadoc-plugin 70 | 2.9.1 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-jxr-plugin 75 | 2.4 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-pmd-plugin 80 | 3.0.1 81 | 82 | true 83 | ${project.build.sourceEncoding} 84 | ${maven.compiler.target} 85 | 86 | 87 | 88 | org.codehaus.mojo 89 | cobertura-maven-plugin 90 | 2.6 91 | 92 | 93 | org.codehaus.mojo 94 | findbugs-maven-plugin 95 | 3.0.0 96 | 97 | 98 | 99 | 100 | 101 | 102 | com.google.guava 103 | guava 104 | 18.0 105 | 106 | 107 | org.slf4j 108 | slf4j-api 109 | 1.7.7 110 | 111 | 112 | org.slf4j 113 | slf4j-simple 114 | 1.7.7 115 | 116 | 117 | junit 118 | junit 119 | 4.13.1 120 | test 121 | 122 | 123 | org.mockito 124 | mockito-all 125 | 1.9.5 126 | test 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/example/EchoServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * EchoServer.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.example; 20 | 21 | import com.pmeade.websocket.io.WebSocketServerOutputStream; 22 | import com.pmeade.websocket.net.WebSocket; 23 | import com.pmeade.websocket.net.WebSocketServerSocket; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.net.ServerSocket; 27 | 28 | /** 29 | * @author pmeade 30 | */ 31 | public class EchoServer { 32 | public static final int PORT = 8080; 33 | 34 | public static void main(String[] args) { 35 | EchoServer echoServer = new EchoServer(); 36 | try { 37 | echoServer.doIt(); 38 | } catch(Exception e) { 39 | System.err.println(e.getLocalizedMessage()); 40 | e.printStackTrace(System.err); 41 | } 42 | } 43 | 44 | public void doIt() throws Exception 45 | { 46 | ServerSocket serverSocket = new ServerSocket(PORT); 47 | WebSocketServerSocket webSocketServerSocket 48 | = new WebSocketServerSocket(serverSocket); 49 | while(finished == false) { 50 | WebSocket socket = webSocketServerSocket.accept(); 51 | new WebSocketThread(socket).start(); 52 | } 53 | } 54 | 55 | public void finish() { 56 | finished = true; 57 | } 58 | 59 | private boolean finished = false; 60 | } 61 | 62 | class WebSocketThread extends Thread { 63 | public WebSocketThread(WebSocket socket) { 64 | this.webSocket = socket; 65 | } 66 | 67 | @Override 68 | public void run() { 69 | try { 70 | WebSocketServerOutputStream wsos = webSocket.getOutputStream(); 71 | InputStream wsis = webSocket.getInputStream(); 72 | int data = wsis.read(); 73 | while (finished == false && data != -1) { 74 | wsos.writeString("Data received: " + (char)data); 75 | data = wsis.read(); 76 | } 77 | } catch (IOException e) { 78 | finished = true; 79 | System.err.println(e.getLocalizedMessage()); 80 | e.printStackTrace(System.err); 81 | } 82 | try { 83 | webSocket.close(); 84 | } catch (IOException e) { 85 | finished = true; 86 | System.err.println(e.getLocalizedMessage()); 87 | e.printStackTrace(System.err); 88 | } 89 | } 90 | 91 | public void finish() { 92 | finished = true; 93 | } 94 | 95 | private boolean finished = false; 96 | 97 | private final WebSocket webSocket; 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/http/HttpRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * HttpRequest.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.http; 20 | 21 | import com.pmeade.websocket.io.LineInputStream; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.util.Collection; 25 | import java.util.Collections; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.Set; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | import static com.google.common.base.Preconditions.checkNotNull; 33 | 34 | /** 35 | * HttpRequest represents the headers of an HTTP Request as a Map. At 36 | * construction time, it parses the headers of the HTTP Request from 37 | * the provided InputStream, and places them into an accessible but 38 | * immutable Map. 39 | * @author blinkdog 40 | */ 41 | public class HttpRequest implements Map { 42 | /** 43 | * Logging, just in case anything goes wrong. 44 | */ 45 | private static final Logger LOG = 46 | LoggerFactory.getLogger(HttpRequest.class); 47 | 48 | /** 49 | * The Request Line of the HTTP Request is stored in the map under 50 | * this key. 51 | */ 52 | public static final String REQUEST_LINE = "REQUEST_LINE"; 53 | 54 | /** 55 | * Construct an HttpRequest object. 56 | * @param in InputStream providing the content of the HTTP Request to be 57 | * parsed 58 | */ 59 | public HttpRequest(final InputStream in) { 60 | checkNotNull(in); 61 | read(in); 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | * @return {@inheritDoc} 67 | */ 68 | public final int size() { 69 | return headerMap.size(); 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | * @return {@inheritDoc} 75 | */ 76 | public final boolean isEmpty() { 77 | return headerMap.isEmpty(); 78 | } 79 | 80 | /** 81 | * {@inheritDoc} 82 | * @param key {@inheritDoc} 83 | * @return {@inheritDoc} 84 | */ 85 | public final boolean containsKey(final Object key) { 86 | return headerMap.containsKey(key); 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | * @param value {@inheritDoc} 92 | * @return {@inheritDoc} 93 | */ 94 | public final boolean containsValue(final Object value) { 95 | return headerMap.containsValue(value); 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | * @param key {@inheritDoc} 101 | * @return {@inheritDoc} 102 | */ 103 | public final String get(final Object key) { 104 | return headerMap.get(key); 105 | } 106 | 107 | /** 108 | * {@inheritDoc} 109 | * @param key {@inheritDoc} 110 | * @param value {@inheritDoc} 111 | * @return {@inheritDoc} 112 | */ 113 | public final String put(final String key, final String value) { 114 | return headerMap.put(key, value); 115 | } 116 | 117 | /** 118 | * {@inheritDoc} 119 | * @param key {@inheritDoc} 120 | * @return {@inheritDoc} 121 | */ 122 | public final String remove(final Object key) { 123 | return headerMap.remove(key); 124 | } 125 | 126 | /** 127 | * {@inheritDoc} 128 | * @param m {@inheritDoc} 129 | */ 130 | public final void putAll(final Map m) { 131 | headerMap.putAll(m); 132 | } 133 | 134 | /** 135 | * {@inheritDoc} 136 | */ 137 | public final void clear() { 138 | headerMap.clear(); 139 | } 140 | 141 | /** 142 | * {@inheritDoc} 143 | * @return {@inheritDoc} 144 | */ 145 | public final Set keySet() { 146 | return headerMap.keySet(); 147 | } 148 | 149 | /** 150 | * {@inheritDoc} 151 | * @return {@inheritDoc} 152 | */ 153 | public final Collection values() { 154 | return headerMap.values(); 155 | } 156 | 157 | /** 158 | * {@inheritDoc} 159 | * @return {@inheritDoc} 160 | */ 161 | public final Set> entrySet() { 162 | return headerMap.entrySet(); 163 | } 164 | 165 | /** 166 | * Parse the headers of the HTTP Request. The provided InputStream will 167 | * contain HTTP Request data. The headers are parsed and placed into 168 | * the Map. The Map is then made immutable, for safe querying by clients. 169 | * @param in InputStream providing the content of the HTTP Request to be 170 | * parsed 171 | */ 172 | private void read(final InputStream in) { 173 | // wrap the Reader in something more convenient 174 | LineInputStream lis = new LineInputStream(in); 175 | // create a place to store the header values 176 | headerMap = new HashMap(); 177 | // make sure we handle any IOExceptions 178 | try { 179 | // read the first line from the buffered reader 180 | String line = lis.readLine(); 181 | // if the first line is empty 182 | while (line != null && line.isEmpty()) { 183 | // keep reading until we get a non-empty line 184 | line = lis.readLine(); 185 | } 186 | // store the non-empty line as the request line 187 | headerMap.put(REQUEST_LINE, line); 188 | // read the next line from the buffered reader 189 | line = lis.readLine(); 190 | // as long as we don't have a null or empty line 191 | while (line != null && !line.isEmpty()) { 192 | // determine the position of the first colon 193 | int firstColonPos = line.indexOf(":"); 194 | // if there is a colon in the line 195 | if (firstColonPos > 0) { 196 | // separate the header field from the header value 197 | String key = line.substring(0, firstColonPos).trim(); 198 | int length = line.length(); 199 | String value = line.substring(firstColonPos + 1, length); 200 | value = value.trim(); 201 | // if we got both a non-empty field and non-empty value 202 | if (!key.isEmpty() && !value.isEmpty()) { 203 | // add it to the map 204 | headerMap.put(key, value); 205 | // add it to the map in lowercase, as well 206 | headerMap.put(key.toLowerCase(), value); 207 | } 208 | } 209 | // read the next line from the header 210 | line = lis.readLine(); 211 | } 212 | } catch (IOException e) { 213 | // log it, and move on 214 | LOG.error("Unable to read HTTP Request in HttpRequest.read():", e); 215 | } 216 | // after we've processed the header, lock the map 217 | headerMap = Collections.unmodifiableMap(headerMap); 218 | } 219 | 220 | /** 221 | * A Map to store the header fields and values after they are parsed 222 | * from the HTTP Request. The parsing function also makes this map 223 | * unmodifiable, so that it is safe to allow clients to query it. 224 | */ 225 | private Map headerMap; 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/http/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * package-info.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * Provides parsing of headers in HTTP protocol requests. 21 | */ 22 | package com.pmeade.websocket.http; 23 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/io/LineInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LineInputStream.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.io; 20 | 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.nio.charset.StandardCharsets; 25 | 26 | import static com.google.common.base.Preconditions.checkNotNull; 27 | 28 | /** 29 | * LineInputStream decorates an InputStream for line-by-line reading. 30 | * It implements a readLine() method, similar to 31 | * BufferedReader, but does not need an intermediate 32 | * InputStreamReader to translate. 33 | * 34 | *

The reason we avoid an InputStreamReader is that it 35 | * will consume as much of the input as it possibly can in order 36 | * to optimize character encoding from the input bytes. 37 | * 38 | *

In our case, the input will be switching protocols from an HTTP 39 | * Request to WebSocket frames. We want to consume the HTTP Request 40 | * line by line and process it for a WebSocket handshake, but the 41 | * data following that must be handled in a completely different way. 42 | * 43 | *

The ability to handle the data line-by-line like 44 | * BufferedReader is able to do, is very useful for 45 | * parsing the HTTP Reqeust. Therefore we have ListInputStream to 46 | * provide this functionality as an InputStream decorator. 47 | * 48 | *

Unlike BufferedReader, which handles three kinds of 49 | * line endings (CR, LF, CRLF), the HTTP protocol has defined the line 50 | * ending to be CRLF. Therefore, the readLine() method of 51 | * this decorator requires a CRLF (or EOF) sequence to terminate a line. 52 | * 53 | * @author blinkdog 54 | */ 55 | public class LineInputStream extends InputStream { 56 | /** 57 | * Constant defining Carriage Return (CR). Octet 13, Hex 0x0c. 58 | */ 59 | public static final int CR = 13; 60 | 61 | /** 62 | * Constant defining the end of the stream (EOF). This is derived 63 | * from the InputStream API. Calls to read() return -1 64 | * when the end of the stream is reached. 65 | */ 66 | public static final int EOF = -1; 67 | 68 | /** 69 | * Constant defining Line Feed (LF). Octet 10, Hex 0x0a 70 | */ 71 | public static final int LF = 10; 72 | 73 | /** 74 | * Constant defining the canonical name of the UTF-8 character encoding. 75 | */ 76 | public static final String UTF_8 = StandardCharsets.UTF_8.name(); 77 | 78 | /** 79 | * Construct a LineInputStream to decorate the provided InputStream. 80 | * A NullPointerException will be thrown if the provided 81 | * InputStream is null. 82 | * @param in InputStream to be decorated by this LineInputStream 83 | */ 84 | public LineInputStream(final InputStream in) { 85 | checkNotNull(in); 86 | this.inputStream = in; 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | * @return the next byte of data, or -1 if the end of the 92 | * stream is reached. 93 | * @throws IOException if an I/O error occurs 94 | */ 95 | @Override 96 | public final int read() throws IOException { 97 | return inputStream.read(); 98 | } 99 | 100 | /** 101 | * Reads a line of text. A line is considered to be terminated by a 102 | * carriage return ('\r') followed immediately by a linefeed ('\n'). 103 | * This is per the HTTP specification. 104 | * @return String containing the contents of the line, not including 105 | * any line-termination characters, or null if the end of the 106 | * stream has been reached 107 | * @throws IOException if an I/O error occurs 108 | */ 109 | public final String readLine() throws IOException { 110 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 111 | boolean inputTaken = false; 112 | while (true) { 113 | int data = inputStream.read(); 114 | // if this is the end of the stream 115 | if (data == EOF) { 116 | // if we've taken some input already 117 | if (inputTaken) { 118 | // return that input 119 | return baos.toString(UTF_8); 120 | } else { 121 | // otherwise return null 122 | return null; 123 | } 124 | // otherwise, if this is a CR 125 | } else if (data == CR) { 126 | // it may be the end of a line 127 | lastWasCarriageReturn = true; 128 | // otherwise, if this is a LF 129 | } else if (data == LF) { 130 | // if we did follow a CR 131 | if (lastWasCarriageReturn) { 132 | // then this is the end of a line 133 | lastWasCarriageReturn = false; 134 | return baos.toString(UTF_8); 135 | } else { 136 | inputTaken = true; 137 | lastWasCarriageReturn = false; 138 | baos.write(LF); 139 | } 140 | // otherwise... 141 | } else { 142 | // if the last thing was a carriage return 143 | if (lastWasCarriageReturn) { 144 | // write that CR to our line 145 | baos.write(CR); 146 | } 147 | // add the data we just read to the line 148 | inputTaken = true; 149 | lastWasCarriageReturn = false; 150 | baos.write(data); 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * InputStream to be decorated by this LineInputStream. This reference 157 | * is provided at construction time. 158 | */ 159 | private final InputStream inputStream; 160 | 161 | /** 162 | * Flag: Is the last character we processed a Carriage Return (Octet 13)? 163 | * true: Yes, the last character was a CR 164 | * false: No, the last character was not a CR 165 | */ 166 | private boolean lastWasCarriageReturn = false; 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/io/WebSocketServerInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocketServerInputStream.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.io; 20 | 21 | import com.google.common.hash.HashCode; 22 | import com.google.common.hash.HashFunction; 23 | import com.google.common.hash.Hashing; 24 | import com.google.common.io.BaseEncoding; 25 | import com.pmeade.websocket.http.HttpRequest; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.nio.charset.StandardCharsets; 29 | 30 | import static com.google.common.base.Preconditions.checkNotNull; 31 | 32 | /** 33 | * WebSocketServerInputStream decorates an InputStream to handle WebSocket 34 | * frames as specified in RFC 6455. 35 | * @author pmeade 36 | */ 37 | public class WebSocketServerInputStream extends InputStream { 38 | /** 39 | * Constant indicating end of stream. 40 | */ 41 | public static final int EOF = -1; 42 | 43 | /** 44 | * Number of bytes in the WebSocket handshake nonce. 45 | */ 46 | public static final int HANDSHAKE_NONCE_LENGTH = 16; 47 | 48 | /** 49 | * Payload length indicating that the payload's true length is a 50 | * yet-to-be-provided unsigned 16-bit integer. 51 | */ 52 | public static final int LENGTH_16 = 0x7E; 53 | 54 | /** 55 | * A payload specified with 16 bits must have at least this 56 | * length in order to be considered valid. 57 | */ 58 | public static final int LENGTH_16_MIN = 126; 59 | 60 | /** 61 | * Payload length indicating that the payload's true length is a 62 | * yet-to-be-provided unsigned 64-bit integer (MSB = 0). 63 | */ 64 | public static final int LENGTH_64 = 0x7F; 65 | 66 | /** 67 | * A payload specified with 64 bits must have at least this 68 | * length in order to be considered valid. 69 | */ 70 | public static final int LENGTH_64_MIN = 0x10000; 71 | 72 | /** 73 | * Binary mask to limit an int to 8 bits (an unsigned byte). 74 | */ 75 | public static final int MASK_BYTE = 0x000000FF; 76 | 77 | /** 78 | * Binary mask to extract the final fragment flag bit of a WebSocket frame. 79 | */ 80 | public static final int MASK_FINAL = 0x80; 81 | 82 | /** 83 | * Binary mask to extract the masking flag bit of a WebSocket frame. 84 | */ 85 | public static final int MASK_MASK = 0x80; 86 | 87 | /** 88 | * Binary mask to limit a value in the range [0-3] (inclusive). 89 | */ 90 | public static final int MASK_MASKING_INDEX = 0x03; 91 | 92 | /** 93 | * Binary mask to extract the opcode bits of a WebSocket frame. 94 | */ 95 | public static final int MASK_OPCODE = 0x0F; 96 | 97 | /** 98 | * Binary mask to extract the control bit of an opcode. 99 | */ 100 | public static final int MASK_CONTROL_OPCODE = 0x08; 101 | 102 | /** 103 | * Binary mask to extract the payload size of a WebSocket frame. 104 | */ 105 | public static final int MASK_PAYLOAD_SIZE = 0x7F; 106 | 107 | /** 108 | * Binary mask to extract the reserved flag bits of a WebSocket frame. 109 | */ 110 | public static final int MASK_RESERVED = 0x70; 111 | 112 | /** 113 | * Number of masking bytes provided by the client. 114 | */ 115 | public static final int NUM_MASKING_BYTES = 4; 116 | 117 | /** 118 | * Number of octets (bytes) in a 64-bit number. 119 | */ 120 | public static final int NUM_OCTET_64 = 8; 121 | 122 | /** 123 | * Number of bits in an octet. 124 | */ 125 | public static final int OCTET = 8; 126 | 127 | /** 128 | * WebSocket Opcode for a Continuation frame. 129 | */ 130 | public static final int OPCODE_CONTINUATION = 0x00; 131 | 132 | /** 133 | * WebSocket Opcode for a Text frame. 134 | */ 135 | public static final int OPCODE_TEXT = 0x01; 136 | 137 | /** 138 | * WebSocket Opcode for a Binary frame. 139 | */ 140 | public static final int OPCODE_BINARY = 0x02; 141 | 142 | /** 143 | * Lowest WebSocket Opcode for reserved non-control frames. That is 144 | * these data frames are yet reserved (undefined). 145 | */ 146 | public static final int OPCODE_RESERVED_NON_CONTROL_LOW = 0x03; 147 | 148 | /** 149 | * Highest WebSocket Opcode for reserved non-control frames. That is 150 | * these data frames are yet reserved (undefined). 151 | */ 152 | public static final int OPCODE_RESERVED_NON_CONTROL_HIGH = 0x07; 153 | 154 | /** 155 | * WebSocket Opcode for a Close control frame. 156 | */ 157 | public static final int OPCODE_CLOSE = 0x08; 158 | 159 | /** 160 | * WebSocket Opcode for a Ping control frame. 161 | */ 162 | public static final int OPCODE_PING = 0x09; 163 | 164 | /** 165 | * WebSocket Opcode for a Pong control frame. 166 | */ 167 | public static final int OPCODE_PONG = 0x0A; 168 | 169 | /** 170 | * Lowest WebSocket Opcode for reserved control frames. That is 171 | * these control frames are yet reserved (undefined). 172 | */ 173 | public static final int OPCODE_RESERVED_CONTROL_LOW = 0x0B; 174 | 175 | /** 176 | * Highest WebSocket Opcode for reserved control frames. That is 177 | * these control frames are yet reserved (undefined). 178 | */ 179 | public static final int OPCODE_RESERVED_CONTROL_HIGH = 0x0F; 180 | 181 | /** 182 | * Lowest WebSocket Opcode that defines a control frame. 183 | */ 184 | public static final int OPCODE_CONTROL_LOW = 0x08; 185 | 186 | /** 187 | * Highest WebSocket Opcode that defines a control frame. 188 | */ 189 | public static final int OPCODE_CONTROL_HIGH = 0x0F; 190 | 191 | /** 192 | * WebSocket Accept UUID. The UUID to be appended to the client-provided 193 | * security nonce, in order to complete the WebSocket handshake. 194 | */ 195 | public static final String WEBSOCKET_ACCEPT_UUID = 196 | "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 197 | 198 | /** 199 | * Convert the provided byte to an unsigned int. 200 | * @param b byte to be converted 201 | * @return unsigned int (8-bit) representation of the provided byte 202 | */ 203 | public static final int asUnsignedInt(final byte b) { 204 | int x = b; 205 | x &= MASK_BYTE; 206 | return x; 207 | } 208 | 209 | /** 210 | * Convert the provided String to UTF-8 encoded bytes. 211 | * @param s String to be converted to a UTF-8 representation 212 | * @return byte array containing the UTF-8 representation of the 213 | * provided String data 214 | */ 215 | public static final byte[] asUTF8(final String s) { 216 | return s.getBytes(StandardCharsets.UTF_8); 217 | } 218 | 219 | /** 220 | * Check if the first String contains() the second String. This method 221 | * returns false if the first string is null. 222 | * @param s1 String to be checked if it contains 223 | * @param s2 String to check for 224 | * @return true, iff s1.contains(s2), otherwise false 225 | */ 226 | public static boolean checkContains(final String s1, final String s2) { 227 | if (s1 == null) { 228 | return false; 229 | } 230 | return s1.contains(s2); 231 | } 232 | 233 | /** 234 | * Check if the first String startsWith() the second String. This method 235 | * returns false if the first String is null. 236 | * @param s1 String to be checked if it starts with 237 | * @param s2 String to check for 238 | * @return true, iff s1.startsWith(s2), otherwise false 239 | */ 240 | public static boolean checkStartsWith(final String s1, final String s2) { 241 | if (s1 == null) { 242 | return false; 243 | } 244 | return s1.startsWith(s2); 245 | } 246 | 247 | /** 248 | * Create a WebSocket-speaking InputStream from the provided InputStream. 249 | * Note that an output peer is still required. 250 | * @param is InputStream to be decorated as a WebSocket-speaking InputStream 251 | */ 252 | public WebSocketServerInputStream(final InputStream is) { 253 | checkNotNull(is, "is == null"); 254 | this.inputStream = is; 255 | } 256 | 257 | /** 258 | * Create a WebSocket-speaking InputStream from the provided InputStream 259 | * and output peer WebSocketOutputStream. 260 | * @param is InputStream to be decorated as a WebSocket-speaking InputStream 261 | * @param wsos WebSocketOutputStream to be the output peer 262 | */ 263 | public WebSocketServerInputStream(final InputStream is, 264 | final WebSocketServerOutputStream wsos) { 265 | checkNotNull(is, "is == null"); 266 | checkNotNull(wsos, "wsos == null"); 267 | this.inputStream = is; 268 | this.outputPeer = wsos; 269 | } 270 | 271 | /** 272 | * Obtain a reference to the peer object used for output on this stream. 273 | * @return WebSocketServerOutputStream used for output on this stream 274 | */ 275 | public final WebSocketServerOutputStream getOutputPeer() { 276 | return outputPeer; 277 | } 278 | 279 | /** 280 | * Determine if this connection has sent a WebSocket Close frame. 281 | * @return true, if this connection has sent a WebSocket Close frame, 282 | * otherwise false. 283 | */ 284 | private boolean isCloseSent() { 285 | return outputPeer.isCloseSent(); 286 | } 287 | 288 | /** 289 | * Determine if the WebSocket connection is closed. 290 | * @return true, if the WebSocket connection is closed, otherwise false. 291 | */ 292 | public final boolean isClosed() { 293 | return closeReceived && isCloseSent(); 294 | } 295 | 296 | /** 297 | * Determine if the WebSocket connection has failed. 298 | * @return true, if the WebSocket connection has failed, otherwise false. 299 | */ 300 | public final boolean isFailed() { 301 | return failed; 302 | } 303 | 304 | /** 305 | * Determine if the WebSocket handshake has completed successfully. 306 | * @return true, if the WebSocket handshake has completed successfully, 307 | * otherwise false. 308 | */ 309 | public final boolean isHandshakeComplete() { 310 | return handshakeComplete; 311 | } 312 | 313 | /** 314 | * Reads the next byte of data from the input stream. The value byte is 315 | * returned as an int in the range 0 to 255. If no byte is available 316 | * because the end of the stream has been reached, the value -1 is 317 | * returned. This method blocks until input data is available, the end 318 | * of the stream is detected, or an exception is thrown. 319 | * @return the next byte of data, or -1 if the end of the stream is 320 | * reached. 321 | * @throws IOException if an I/O error occurs. 322 | */ 323 | @Override 324 | public final int read() throws IOException { 325 | if (isClosed() || isFailed()) { 326 | return EOF; 327 | } 328 | if (!handshakeComplete) { 329 | shakeHands(); 330 | if (!handshakeComplete) { 331 | failTheWebSocketConnection(); 332 | return EOF; 333 | } 334 | } 335 | return nextWebSocketByte(); 336 | } 337 | 338 | /** 339 | * Set the output peer for this InputStream. A WebSocketServerOutputStream 340 | * object is used to communicate back to the source of this InputStream. 341 | * This method allows a client to specify the object that does this. This 342 | * is especially useful if the client did not do so during construction. 343 | * @param op WebSocketServerOutputStream object used to communicate back 344 | * to the source of this InputStream 345 | */ 346 | public final void setOutputPeer(final WebSocketServerOutputStream op) { 347 | this.outputPeer = op; 348 | } 349 | 350 | //----------------------------------------------------------------------- 351 | 352 | /** 353 | * Obtain the next byte of data from the WebSocket. 354 | * @return the next byte of data from the WebSocket 355 | * @throws IOException if anything goes wrong with the underlying stream 356 | */ 357 | private int nextWebSocketByte() throws IOException { 358 | while (payloadLength == 0L) { 359 | nextWebSocketFrame(); 360 | if (isClosed() || isFailed()) { 361 | return EOF; 362 | } 363 | } 364 | int data = inputStream.read() ^ maskingBytes[maskingIndex]; 365 | payloadLength--; 366 | maskingIndex++; 367 | maskingIndex &= MASK_MASKING_INDEX; 368 | return data; 369 | } 370 | 371 | /** 372 | * Process the next WebSocket frame. This method reads the header 373 | * information about the frame. It sets up non-control frames to 374 | * provide their data, and handles WebSocket protocol-specifics for 375 | * the control frames. 376 | * @throws IOException if anything goes wrong with the underlying stream 377 | */ 378 | private void nextWebSocketFrame() throws IOException { 379 | // byte 0: flags and opcode 380 | int flagOps = inputStream.read(); 381 | if ((flagOps & MASK_RESERVED) != 0x00) { 382 | failTheWebSocketConnection(); 383 | return; 384 | } 385 | int opcode = flagOps & MASK_OPCODE; 386 | if (opcode >= OPCODE_RESERVED_NON_CONTROL_LOW 387 | && opcode <= OPCODE_RESERVED_NON_CONTROL_HIGH) { 388 | failTheWebSocketConnection(); 389 | return; 390 | } 391 | if (opcode >= OPCODE_RESERVED_CONTROL_LOW) { 392 | failTheWebSocketConnection(); 393 | return; 394 | } 395 | boolean finalFragment = (flagOps & MASK_FINAL) == MASK_FINAL; 396 | boolean controlOpcode = 397 | (flagOps & MASK_CONTROL_OPCODE) == MASK_CONTROL_OPCODE; 398 | if (controlOpcode && !finalFragment) { 399 | failTheWebSocketConnection(); 400 | return; 401 | } 402 | // byte 1: masking and payload length 403 | int maskPayload = inputStream.read(); 404 | boolean masked = (maskPayload & MASK_MASK) == MASK_MASK; 405 | if (!masked) { 406 | failTheWebSocketConnection(); 407 | return; 408 | } 409 | int payloadSize = maskPayload & MASK_PAYLOAD_SIZE; 410 | // byte 2-9: extended payload length, if specified 411 | if (payloadSize == LENGTH_16) { 412 | if (controlOpcode) { 413 | failTheWebSocketConnection(); 414 | return; 415 | } 416 | payloadLength = (inputStream.read() << OCTET) 417 | | (inputStream.read()); 418 | if (payloadLength < LENGTH_16_MIN) { 419 | failTheWebSocketConnection(); 420 | return; 421 | } 422 | } else if (payloadSize == LENGTH_64) { 423 | if (controlOpcode) { 424 | failTheWebSocketConnection(); 425 | return; 426 | } 427 | payloadLength = 0L; 428 | for (int i = 0; i < NUM_OCTET_64; i++) { 429 | payloadLength |= 430 | inputStream.read() << (NUM_OCTET_64 - 1 - i) * OCTET; 431 | } 432 | if (payloadLength < LENGTH_64_MIN) { 433 | failTheWebSocketConnection(); 434 | return; 435 | } 436 | } else { 437 | payloadLength = payloadSize; 438 | } 439 | // byte 10-13: masking key 440 | for (int i = 0; i < NUM_MASKING_BYTES; i++) { 441 | maskingBytes[i] = inputStream.read(); 442 | } 443 | maskingIndex = 0; 444 | // if this is a control opcode; handle the control frame 445 | if (opcode == OPCODE_CLOSE) { 446 | handleCloseFrame(); 447 | } 448 | if (opcode == OPCODE_PING) { 449 | handlePingFrame(); 450 | } 451 | if (opcode == OPCODE_PONG) { 452 | handlePongFrame(); 453 | } 454 | } 455 | 456 | /** 457 | * Perform the initial WebSocket handshake. WebSockets connect with an 458 | * HTTP Request to upgrade the connection to a WebSocket connection. This 459 | * method ensures that the request is correctly formed, and provides 460 | * the appropriate response to the client. After this method is called, 461 | * further communication is performed solely with WebSocket frames. 462 | * @throws IOException if anything goes wrong with the underlying stream 463 | */ 464 | private void shakeHands() throws IOException { 465 | HttpRequest req = new HttpRequest(inputStream); 466 | String requestLine = req.get(HttpRequest.REQUEST_LINE); 467 | handshakeComplete = checkStartsWith(requestLine, "GET /") 468 | && checkContains(requestLine, "HTTP/") 469 | && req.get("Host") != null 470 | && checkContains(req.get("Upgrade"), "websocket") 471 | && checkContains(req.get("Connection"), "Upgrade") 472 | && "13".equals(req.get("Sec-WebSocket-Version")) 473 | && req.get("Sec-WebSocket-Key") != null; 474 | String nonce = req.get("Sec-WebSocket-Key"); 475 | if (handshakeComplete) { 476 | byte[] nonceBytes = BaseEncoding.base64().decode(nonce); 477 | if (nonceBytes.length != HANDSHAKE_NONCE_LENGTH) { 478 | handshakeComplete = false; 479 | } 480 | } 481 | // if we have met all the requirements 482 | if (handshakeComplete) { 483 | outputPeer.write(asUTF8("HTTP/1.1 101 Switching Protocols\r\n")); 484 | outputPeer.write(asUTF8("Upgrade: websocket\r\n")); 485 | outputPeer.write(asUTF8("Connection: upgrade\r\n")); 486 | outputPeer.write(asUTF8("Sec-WebSocket-Accept: ")); 487 | HashFunction hf = Hashing.sha1(); 488 | HashCode hc = hf.newHasher() 489 | .putString(nonce, StandardCharsets.UTF_8) 490 | .putString(WEBSOCKET_ACCEPT_UUID, StandardCharsets.UTF_8) 491 | .hash(); 492 | String acceptKey = BaseEncoding.base64().encode(hc.asBytes()); 493 | outputPeer.write(asUTF8(acceptKey)); 494 | outputPeer.write(asUTF8("\r\n\r\n")); 495 | } 496 | outputPeer.setHandshakeComplete(handshakeComplete); 497 | } 498 | 499 | /** 500 | * Sets the WebSocketServerInputStream to a FAILED state. In this state, 501 | * no further processing of data takes place. Mostly it is used to 502 | * prevent actions that rely upon faulty WebSocket implementations. 503 | */ 504 | private void failTheWebSocketConnection() { 505 | failed = true; 506 | } 507 | 508 | /** 509 | * Handle an incoming Close control frame. If we haven't sent a Close 510 | * frame to the client, we do so. We then close the underlying socket. 511 | * @throws IOException if anything goes wrong with the underlying stream 512 | */ 513 | private void handleCloseFrame() throws IOException { 514 | // the client has sent us a close frame 515 | closeReceived = true; 516 | // if we already sent a close frame before 517 | if (isCloseSent()) { 518 | // then we received an acknowledgement close frame 519 | // from the client, so we need to close the underlying 520 | // TCP socket now 521 | this.close(); 522 | return; 523 | } 524 | // otherwise, the client has sent us a close frame 525 | // and we will acknowledge that close frame now 526 | byte[] closePayload = consumePayload(); 527 | if (closePayload.length >= 2) { 528 | int highByte = asUnsignedInt(closePayload[0]); 529 | int lowByte = asUnsignedInt(closePayload[1]); 530 | int closeStatusCode = (highByte << OCTET) | lowByte; 531 | outputPeer.writeClose(closeStatusCode); 532 | } else { 533 | outputPeer.writeClose(); 534 | } 535 | // we need to close the underlying TCP socket now 536 | this.close(); 537 | } 538 | 539 | /** 540 | * Handle an incoming Ping control frame. The WebSocket standard indicates 541 | * echoing a Pong frame back with an identical payload. That is what this 542 | * method does. 543 | * @throws IOException if anything goes wrong with the underlying stream 544 | */ 545 | private void handlePingFrame() throws IOException { 546 | // read all of the ping payload 547 | byte[] pingPayload = consumePayload(); 548 | outputPeer.writePong(pingPayload); 549 | } 550 | 551 | /** 552 | * Handle an incoming Pong control frame. This method simply consumes 553 | * the payload (if any) and disregards the frame. 554 | * @throws IOException if anything goes wrong with the underlying stream 555 | */ 556 | private void handlePongFrame() throws IOException { 557 | // read all of the pong payload 558 | consumePayload(); 559 | } 560 | 561 | /** 562 | * Consume the entire payload of the frame. Note that the state of the 563 | * field payloadLength is used (and altered) in this utility 564 | * method. 565 | * @return byte[] containing the bytes of the frame payload 566 | * @throws IOException if anything goes wrong with the underlying stream 567 | */ 568 | private byte[] consumePayload() throws IOException { 569 | byte[] payload = new byte[(int) payloadLength]; 570 | int count = 0; 571 | while (payloadLength > 0L) { 572 | payload[count] = (byte) this.read(); 573 | count++; 574 | } 575 | return payload; 576 | } 577 | 578 | /** 579 | * Flag: Has the WebSocket connection received a CLOSE frame? 580 | */ 581 | private boolean closeReceived = false; 582 | 583 | /** 584 | * Flag: Has the WebSocket connection failed for any reason? 585 | */ 586 | private boolean failed = false; 587 | 588 | /** 589 | * Flag: Is the WebSocket handshake complete? 590 | */ 591 | private boolean handshakeComplete = false; 592 | 593 | /** 594 | * InputStream to be decorated as a WebSocket-speaking InputStream. 595 | */ 596 | private InputStream inputStream = null; 597 | 598 | /** 599 | * Bytes of the latest masking key provided by the client. 600 | */ 601 | private final int[] maskingBytes = new int[NUM_MASKING_BYTES]; 602 | 603 | /** 604 | * Index of the next maskingByte to be used on payload data. 605 | */ 606 | private int maskingIndex = 0; 607 | 608 | /** 609 | * The companion OutputStream for this WebSocketInputStream. 610 | */ 611 | private WebSocketServerOutputStream outputPeer = null; 612 | 613 | /** 614 | * Number of payload bytes that we still expecting before the next 615 | * WebSocket frame. 616 | */ 617 | private long payloadLength = 0L; 618 | } 619 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/io/WebSocketServerOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocketServerOutputStream.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.io; 20 | 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | 24 | import static com.google.common.base.Preconditions.checkNotNull; 25 | import java.nio.charset.StandardCharsets; 26 | 27 | /** 28 | * WebSocketServerOutputStream decorates an OutputStream to handle WebSocket 29 | * frames as specified in RFC 6455. 30 | * @author pmeade 31 | */ 32 | public class WebSocketServerOutputStream extends OutputStream { 33 | /** 34 | * Payload length indicating that the payload's true length is a 35 | * yet-to-be-provided unsigned 16-bit integer. 36 | */ 37 | public static final int LENGTH_16 = 0x7E; 38 | 39 | /** 40 | * A payload specified with 16 bits must have at least this 41 | * length in order to be considered valid. 42 | */ 43 | public static final int LENGTH_16_MIN = 126; 44 | 45 | /** 46 | * Payload length indicating that the payload's true length is a 47 | * yet-to-be-provided unsigned 64-bit integer (MSB = 0). 48 | */ 49 | public static final int LENGTH_64 = 0x7F; 50 | 51 | /** 52 | * A payload specified with 64 bits must have at least this 53 | * length in order to be considered valid. 54 | */ 55 | public static final int LENGTH_64_MIN = 0x10000; 56 | 57 | /** 58 | * Binary mask to remove all but the bits of octet 3. We also remove the 59 | * sign bit (0x80000000) if any, to prevent it from being shifted down. 60 | */ 61 | public static final int MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN = 0x7f000000; 62 | 63 | /** 64 | * Binary mask to remove all but the bits of octet 2. 65 | */ 66 | public static final int MASK_HIGH_WORD_LOW_BYTE = 0x00ff0000; 67 | 68 | /** 69 | * Binary mask to remove all but the bits of octet 1. 70 | */ 71 | public static final int MASK_LOW_WORD_HIGH_BYTE = 0x0000ff00; 72 | 73 | /** 74 | * Binary mask to remove all but the lowest 8 bits (octet 0). 75 | */ 76 | public static final int MASK_LOW_WORD_LOW_BYTE = 0x000000ff; 77 | 78 | /** 79 | * Number of bits required to shift octet 1 into the lowest 8 bits. 80 | */ 81 | public static final int OCTET_ONE = 8; 82 | 83 | /** 84 | * Number of bits required to shift octet 2 into the lowest 8 bits. 85 | */ 86 | public static final int OCTET_TWO = 16; 87 | 88 | /** 89 | * Number of bits required to shift octet 3 into the lowest 8 bits. 90 | */ 91 | public static final int OCTET_THREE = 24; 92 | 93 | /** 94 | * WebSocket defined opcode for a Binary frame. Includes high bit (0x80) 95 | * to indicate that the frame is the final/complete frame. 96 | */ 97 | public static final int OPCODE_FRAME_BINARY = 0x82; 98 | 99 | /** 100 | * WebSocket defined opcode for a Close frame. Includes high bit (0x80) 101 | * to indicate that the frame is the final/complete frame. 102 | */ 103 | public static final int OPCODE_FRAME_CLOSE = 0x88; 104 | 105 | /** 106 | * WebSocket defined opcode for a Pong frame. Includes high bit (0x80) 107 | * to indicate that the frame is the final/complete frame. 108 | */ 109 | public static final int OPCODE_FRAME_PONG = 0x8A; 110 | 111 | /** 112 | * WebSocket defined opcode for a Text frame. Includes high bit (0x80) 113 | * to indicate that the frame is the final/complete frame. 114 | */ 115 | public static final int OPCODE_FRAME_TEXT = 0x81; 116 | 117 | /** 118 | * Create a WebSocket-speaking OutputStream from the provided OutputStream. 119 | * @param os OutputStream to be decorated as a WebSocketServerOutputStream 120 | */ 121 | public WebSocketServerOutputStream(final OutputStream os) { 122 | checkNotNull(os); 123 | this.outputStream = os; 124 | } 125 | 126 | /** 127 | * Writes the specified byte to this output stream. The general contract 128 | * for write is that one byte is written to the output stream. The byte to 129 | * be written is the eight low-order bits of the argument b. The 24 130 | * high-order bits of b are ignored. 131 | *

Subclasses of OutputStream must provide an implementation for this 132 | * method. 133 | * @param b the byte. 134 | * @throws IOException if an I/O error occurs. In particular, an 135 | * IOException may be thrown if the output stream has 136 | * been closed. 137 | */ 138 | @Override 139 | public final void write(final int b) throws IOException { 140 | if (handshakeComplete) { 141 | byte[] ba = new byte[] {(byte) b }; 142 | writeBinary(ba); 143 | } else { 144 | outputStream.write(b); 145 | } 146 | } 147 | 148 | /** 149 | * Writes len bytes from the specified byte array starting at offset off to 150 | * this output stream. The general contract for write(b, off, len) is that 151 | * some of the bytes in the array b are written to the output stream in 152 | * order; element b[off] is the first byte written and b[off+len-1] is the 153 | * last byte written by this operation. 154 | *

The write method of OutputStream calls the write method of one 155 | * argument on each of the bytes to be written out. Subclasses are 156 | * encouraged to override this method and provide a more efficient 157 | * implementation. 158 | *

If b is null, a NullPointerException is thrown. 159 | *

If off is negative, or len is negative, or off+len is greater than 160 | * the length of the array b, then an IndexOutOfBoundsException is thrown. 161 | * @param b the data. 162 | * @param off the start offset in the data. 163 | * @param len the number of bytes to write. 164 | * @throws IOException if an I/O error occurs. In particular, an 165 | * IOException is thrown if the output stream is 166 | * closed. 167 | */ 168 | @Override 169 | public final void write(final byte[] b, final int off, final int len) 170 | throws IOException { 171 | if (handshakeComplete) { 172 | byte[] dst = new byte[len]; 173 | System.arraycopy(b, off, dst, 0, len); 174 | writeBinary(dst); 175 | } else { 176 | super.write(b, off, len); 177 | } 178 | } 179 | 180 | /** 181 | * Writes b.length bytes from the specified byte array to this output 182 | * stream. The general contract for write(b) is that it should have 183 | * exactly the same effect as the call write(b, 0, b.length). 184 | * @param b the data. 185 | * @throws IOException if an I/O error occurs. 186 | */ 187 | @Override 188 | public final void write(final byte[] b) throws IOException { 189 | if (handshakeComplete) { 190 | writeBinary(b); 191 | } else { 192 | super.write(b); 193 | } 194 | } 195 | 196 | /** 197 | * Determine if a Close control frame has been sent over the WebSocket. 198 | * @return true, iff a Close control frame has been sent, 199 | * otherwise fasle 200 | */ 201 | public final boolean isCloseSent() { 202 | return closeSent; 203 | } 204 | 205 | /** 206 | * Determine if the WebSocket handshake has completed successfully. 207 | * @return true, if the WebSocket handshake has completed successfully, 208 | * otherwise false 209 | */ 210 | public final boolean isHandshakeComplete() { 211 | return handshakeComplete; 212 | } 213 | 214 | /** 215 | * Tell this WebSocketServerOutputStream if the WebSocket handshake 216 | * has completed successfully. 217 | * @param complete true, if the WebSocket handshake has completed 218 | * successfully, otherwise false 219 | */ 220 | public final void setHandshakeComplete(final boolean complete) { 221 | this.handshakeComplete = complete; 222 | } 223 | 224 | /** 225 | * Write the provided binary data to the WebSocket. 226 | * @param bytes byte array containing the binary data to be writen 227 | * @throws IOException if an I/O error occurs 228 | */ 229 | public final void writeBinary(final byte[] bytes) throws IOException { 230 | int binLength = bytes.length; 231 | outputStream.write(OPCODE_FRAME_BINARY); // final binary-frame 232 | if (binLength < LENGTH_16_MIN) { 233 | outputStream.write(binLength); // small payload length 234 | } else if (binLength < LENGTH_64_MIN) { 235 | outputStream.write(LENGTH_16); // medium payload flag 236 | outputStream.write( 237 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE); 238 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE); 239 | } else { 240 | outputStream.write(LENGTH_64); // large payload flag 241 | outputStream.write(0x00); // upper bytes 242 | outputStream.write(0x00); // upper bytes 243 | outputStream.write(0x00); // upper bytes 244 | outputStream.write(0x00); // upper bytes 245 | outputStream.write( 246 | (binLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE); 247 | outputStream.write( 248 | (binLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO); 249 | outputStream.write( 250 | (binLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE); 251 | outputStream.write(binLength & MASK_LOW_WORD_LOW_BYTE); 252 | } 253 | outputStream.write(bytes); // binary payload 254 | } 255 | 256 | /** 257 | * Write a Close control frame to the WebSocket. 258 | * @throws IOException if an I/O error occurs 259 | */ 260 | public final void writeClose() throws IOException { 261 | if (!closeSent) { 262 | closeSent = true; 263 | outputStream.write(new byte[] { 264 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x00 265 | }); 266 | } 267 | } 268 | 269 | /** 270 | * Write a Close control frame to the WebSocket. 271 | * @param statusCode status code indicating the reason for the closure 272 | * of the WebSocket; constants defined in RFC 6455 273 | * @throws IOException if an I/O error occurs 274 | */ 275 | public final void writeClose(final int statusCode) throws IOException { 276 | if (!closeSent) { 277 | closeSent = true; 278 | outputStream.write(new byte[] { 279 | (byte) OPCODE_FRAME_CLOSE, (byte) 0x02, 280 | (byte) ((statusCode & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE), 281 | (byte) (statusCode & MASK_LOW_WORD_LOW_BYTE) 282 | }); 283 | } 284 | } 285 | 286 | /** 287 | * Write a Pong control frame to the WebSocket. Uses the provided data 288 | * as the payload data of the control frame. 289 | * @param pongPayload byte array containing payload data for the pong frame 290 | * @throws IOException if an I/O error occurs 291 | */ 292 | public final void writePong(final byte[] pongPayload) throws IOException { 293 | outputStream.write(new byte[] { 294 | (byte) OPCODE_FRAME_PONG, (byte) (pongPayload.length) 295 | }); 296 | outputStream.write(pongPayload); 297 | } 298 | 299 | /** 300 | * Write the provided String to the WebSocket in UTF-8 format. 301 | * @param string String to be written to the WebSocket 302 | * @throws IOException if an I/O error occurs 303 | */ 304 | public final void writeString(final String string) throws IOException { 305 | byte[] utfBytes = string.getBytes(StandardCharsets.UTF_8); 306 | int utfLength = utfBytes.length; 307 | outputStream.write(OPCODE_FRAME_TEXT); // final text-frame 308 | if (utfLength < LENGTH_16_MIN) { 309 | outputStream.write(utfLength); // small payload length 310 | } else if (utfLength < LENGTH_64_MIN) { 311 | outputStream.write(LENGTH_16); // medium payload flag 312 | outputStream.write( 313 | (utfLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE); 314 | outputStream.write(utfLength & MASK_LOW_WORD_LOW_BYTE); 315 | } else { 316 | outputStream.write(LENGTH_64); // large payload flag 317 | outputStream.write(0x00); // upper bytes 318 | outputStream.write(0x00); // upper bytes 319 | outputStream.write(0x00); // upper bytes 320 | outputStream.write(0x00); // upper bytes 321 | outputStream.write( 322 | (utfLength & MASK_HIGH_WORD_HIGH_BYTE_NO_SIGN) >> OCTET_THREE); 323 | outputStream.write( 324 | (utfLength & MASK_HIGH_WORD_LOW_BYTE) >> OCTET_TWO); 325 | outputStream.write( 326 | (utfLength & MASK_LOW_WORD_HIGH_BYTE) >> OCTET_ONE); 327 | outputStream.write(utfLength & MASK_LOW_WORD_LOW_BYTE); 328 | } 329 | outputStream.write(utfBytes); // text payload 330 | } 331 | 332 | /** 333 | * Flag: Has the WebSocket connection sent a CLOSE frame? 334 | */ 335 | private boolean closeSent = false; 336 | 337 | /** 338 | * Flag: Is the WebSocket handshake complete? 339 | */ 340 | private boolean handshakeComplete = false; 341 | 342 | /** 343 | * OutputStream to be decorated as a WebSocket-speaking OutputStream. 344 | */ 345 | private final OutputStream outputStream; 346 | } 347 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/io/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * package-info.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * Provides for input and output through WebSocket protocol streams. 21 | */ 22 | package com.pmeade.websocket.io; 23 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/net/WebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocket.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.net; 20 | 21 | import com.pmeade.websocket.io.WebSocketServerInputStream; 22 | import com.pmeade.websocket.io.WebSocketServerOutputStream; 23 | import java.io.IOException; 24 | import java.net.InetAddress; 25 | import java.net.Socket; 26 | import java.net.SocketAddress; 27 | import java.net.SocketException; 28 | import java.nio.channels.SocketChannel; 29 | 30 | /** 31 | * WebSocket decorates Socket to provide the server side of a Socket that 32 | * speaks the WebSocket (RFC 6455) protocol. 33 | * @author veloxi 34 | */ 35 | public class WebSocket extends Socket { 36 | /** 37 | * Construct a WebSocket. WebSocket decorates a Socket for WebSocket 38 | * (RFC 6455) behavior. 39 | * @param s Socket to be decorated with WebSocket behavior. 40 | */ 41 | public WebSocket(final Socket s) { 42 | this.socket = s; 43 | } 44 | 45 | @Override 46 | public final void connect(final SocketAddress endpoint) throws IOException { 47 | socket.connect(endpoint); 48 | } 49 | 50 | @Override 51 | public final void connect(final SocketAddress endpoint, final int timeout) 52 | throws IOException { 53 | socket.connect(endpoint, timeout); 54 | } 55 | 56 | @Override 57 | public final void bind(final SocketAddress bindpoint) throws IOException { 58 | socket.bind(bindpoint); 59 | } 60 | 61 | @Override 62 | public final InetAddress getInetAddress() { 63 | return socket.getInetAddress(); 64 | } 65 | 66 | @Override 67 | public final InetAddress getLocalAddress() { 68 | return socket.getLocalAddress(); 69 | } 70 | 71 | @Override 72 | public final int getPort() { 73 | return socket.getPort(); 74 | } 75 | 76 | @Override 77 | public final int getLocalPort() { 78 | return socket.getLocalPort(); 79 | } 80 | 81 | @Override 82 | public final SocketAddress getRemoteSocketAddress() { 83 | return socket.getRemoteSocketAddress(); 84 | } 85 | 86 | @Override 87 | public final SocketAddress getLocalSocketAddress() { 88 | return socket.getLocalSocketAddress(); 89 | } 90 | 91 | @Override 92 | public final SocketChannel getChannel() { 93 | throw new UnsupportedOperationException(); 94 | } 95 | 96 | @Override 97 | public final WebSocketServerInputStream getInputStream() 98 | throws IOException { 99 | if (wssos == null) { 100 | this.getOutputStream(); 101 | } 102 | if (wssis == null) { 103 | wssis = new WebSocketServerInputStream( 104 | socket.getInputStream(), wssos); 105 | } 106 | return wssis; 107 | } 108 | 109 | @Override 110 | public final WebSocketServerOutputStream getOutputStream() 111 | throws IOException { 112 | if (wssos == null) { 113 | wssos = new WebSocketServerOutputStream(socket.getOutputStream()); 114 | } 115 | return wssos; 116 | } 117 | 118 | @Override 119 | public final void setTcpNoDelay(final boolean on) throws SocketException { 120 | socket.setTcpNoDelay(on); 121 | } 122 | 123 | @Override 124 | public final boolean getTcpNoDelay() throws SocketException { 125 | return socket.getTcpNoDelay(); 126 | } 127 | 128 | @Override 129 | public final void setSoLinger(final boolean on, final int linger) 130 | throws SocketException { 131 | socket.setSoLinger(on, linger); 132 | } 133 | 134 | @Override 135 | public final int getSoLinger() throws SocketException { 136 | return socket.getSoLinger(); 137 | } 138 | 139 | @Override 140 | public final void sendUrgentData(final int data) 141 | throws IOException { 142 | socket.sendUrgentData(data); 143 | } 144 | 145 | @Override 146 | public final void setOOBInline(final boolean on) 147 | throws SocketException { 148 | socket.setOOBInline(on); 149 | } 150 | 151 | @Override 152 | public final boolean getOOBInline() throws SocketException { 153 | return socket.getOOBInline(); 154 | } 155 | 156 | @Override 157 | public final synchronized void setSoTimeout(final int timeout) 158 | throws SocketException { 159 | socket.setSoTimeout(timeout); 160 | } 161 | 162 | @Override 163 | public final synchronized int getSoTimeout() throws SocketException { 164 | return socket.getSoTimeout(); 165 | } 166 | 167 | @Override 168 | public final synchronized void setSendBufferSize(final int size) 169 | throws SocketException { 170 | socket.setSendBufferSize(size); 171 | } 172 | 173 | @Override 174 | public final synchronized int getSendBufferSize() throws SocketException { 175 | return socket.getSendBufferSize(); 176 | } 177 | 178 | @Override 179 | public final synchronized void setReceiveBufferSize(final int size) 180 | throws SocketException { 181 | socket.setReceiveBufferSize(size); 182 | } 183 | 184 | @Override 185 | public final synchronized int getReceiveBufferSize() 186 | throws SocketException { 187 | return socket.getReceiveBufferSize(); 188 | } 189 | 190 | @Override 191 | public final void setKeepAlive(final boolean on) throws SocketException { 192 | socket.setKeepAlive(on); 193 | } 194 | 195 | @Override 196 | public final boolean getKeepAlive() throws SocketException { 197 | return socket.getKeepAlive(); 198 | } 199 | 200 | @Override 201 | public final void setTrafficClass(final int tc) throws SocketException { 202 | socket.setTrafficClass(tc); 203 | } 204 | 205 | @Override 206 | public final int getTrafficClass() throws SocketException { 207 | return socket.getTrafficClass(); 208 | } 209 | 210 | @Override 211 | public final void setReuseAddress(final boolean on) throws SocketException { 212 | socket.setReuseAddress(on); 213 | } 214 | 215 | @Override 216 | public final boolean getReuseAddress() throws SocketException { 217 | return socket.getReuseAddress(); 218 | } 219 | 220 | @Override 221 | public final synchronized void close() throws IOException { 222 | socket.close(); 223 | } 224 | 225 | @Override 226 | public final void shutdownInput() throws IOException { 227 | socket.shutdownInput(); 228 | } 229 | 230 | @Override 231 | public final void shutdownOutput() throws IOException { 232 | socket.shutdownOutput(); 233 | } 234 | 235 | @Override 236 | public final String toString() { 237 | return socket.toString(); 238 | } 239 | 240 | @Override 241 | public final boolean isConnected() { 242 | return socket.isConnected(); 243 | } 244 | 245 | @Override 246 | public final boolean isBound() { 247 | return socket.isBound(); 248 | } 249 | 250 | @Override 251 | public final boolean isClosed() { 252 | return socket.isClosed(); 253 | } 254 | 255 | @Override 256 | public final boolean isInputShutdown() { 257 | return socket.isInputShutdown(); 258 | } 259 | 260 | @Override 261 | public final boolean isOutputShutdown() { 262 | return socket.isOutputShutdown(); 263 | } 264 | 265 | @Override 266 | public final void setPerformancePreferences( 267 | final int connectionTime, 268 | final int latency, 269 | final int bandwidth) { 270 | socket.setPerformancePreferences(connectionTime, latency, bandwidth); 271 | } 272 | 273 | /** 274 | * Socket to be decorated for WebSocket behavior. 275 | */ 276 | private final Socket socket; 277 | 278 | /** 279 | * WebSocketServerInputStream that decorates the InputStream for 280 | * this Socket. Created on the first call to getInputStream(). 281 | */ 282 | private WebSocketServerInputStream wssis = null; 283 | 284 | /** 285 | * WebSocketServerOutputStream that decorates the OutputStream for 286 | * this Socket. Created on the first call to getInputStream() 287 | * or getOutputStream(). 288 | */ 289 | private WebSocketServerOutputStream wssos = null; 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/net/WebSocketServerSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocketServerSocket.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.net; 20 | 21 | import java.io.IOException; 22 | import java.net.InetAddress; 23 | import java.net.ServerSocket; 24 | import java.net.SocketAddress; 25 | import java.net.SocketException; 26 | import java.nio.channels.ServerSocketChannel; 27 | 28 | /** 29 | * WebSocketServerSocket decorates a ServerSocket to accept connections 30 | * that use the WebSocket protocol as specified in RFC 6455. 31 | * @author veloxi 32 | */ 33 | public class WebSocketServerSocket extends ServerSocket { 34 | /** 35 | * Construct a WebSocketServerSocket. WebSocketServerSocket provides a 36 | * ServerSocket with WebSocket (RFC 6455) behavior. 37 | * @param ss ServerSocket to be decorated with WebSocket behavior 38 | * @throws IOException if an I/O error occurs 39 | */ 40 | public WebSocketServerSocket(final ServerSocket ss) throws IOException { 41 | this.serverSocket = ss; 42 | } 43 | 44 | @Override 45 | public final void bind(final SocketAddress endpoint) throws IOException { 46 | serverSocket.bind(endpoint); 47 | } 48 | 49 | @Override 50 | public final void bind(final SocketAddress endpoint, final int backlog) 51 | throws IOException { 52 | serverSocket.bind(endpoint, backlog); 53 | } 54 | 55 | @Override 56 | public final InetAddress getInetAddress() { 57 | return serverSocket.getInetAddress(); 58 | } 59 | 60 | @Override 61 | public final int getLocalPort() { 62 | return serverSocket.getLocalPort(); 63 | } 64 | 65 | @Override 66 | public final SocketAddress getLocalSocketAddress() { 67 | return serverSocket.getLocalSocketAddress(); 68 | } 69 | 70 | @Override 71 | public final WebSocket accept() throws IOException { 72 | return new WebSocket(serverSocket.accept()); 73 | } 74 | 75 | @Override 76 | public final void close() throws IOException { 77 | serverSocket.close(); 78 | } 79 | 80 | @Override 81 | public final ServerSocketChannel getChannel() { 82 | throw new UnsupportedOperationException(); 83 | } 84 | 85 | @Override 86 | public final boolean isBound() { 87 | return serverSocket.isBound(); 88 | } 89 | 90 | @Override 91 | public final boolean isClosed() { 92 | return serverSocket.isClosed(); 93 | } 94 | 95 | @Override 96 | public final synchronized void setSoTimeout(final int timeout) 97 | throws SocketException { 98 | serverSocket.setSoTimeout(timeout); 99 | } 100 | 101 | @Override 102 | public final synchronized int getSoTimeout() throws IOException { 103 | return serverSocket.getSoTimeout(); 104 | } 105 | 106 | @Override 107 | public final void setReuseAddress(final boolean on) 108 | throws SocketException { 109 | serverSocket.setReuseAddress(on); 110 | } 111 | 112 | @Override 113 | public final boolean getReuseAddress() throws SocketException { 114 | return serverSocket.getReuseAddress(); 115 | } 116 | 117 | @Override 118 | public final String toString() { 119 | return serverSocket.toString(); 120 | } 121 | 122 | @Override 123 | public final synchronized void setReceiveBufferSize(final int size) 124 | throws SocketException { 125 | serverSocket.setReceiveBufferSize(size); 126 | } 127 | 128 | @Override 129 | public final synchronized int getReceiveBufferSize() 130 | throws SocketException { 131 | return serverSocket.getReceiveBufferSize(); 132 | } 133 | 134 | @Override 135 | public final void setPerformancePreferences( 136 | final int connectionTime, 137 | final int latency, 138 | final int bandwidth) { 139 | serverSocket.setPerformancePreferences( 140 | connectionTime, latency, bandwidth); 141 | } 142 | 143 | /** 144 | * ServerSocket to be decorated with WebSocket behavior. 145 | */ 146 | private final ServerSocket serverSocket; 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/pmeade/websocket/net/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * package-info.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * Provides a ServerSocket that understands the WebSocket protocol. 21 | */ 22 | package com.pmeade.websocket.net; 23 | -------------------------------------------------------------------------------- /src/test/java/com/pmeade/websocket/http/HttpRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * HttpRequestTest.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.http; 20 | 21 | import com.google.common.base.Joiner; 22 | import com.google.common.collect.ImmutableMap; 23 | import java.io.ByteArrayInputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.util.Arrays; 27 | import java.util.Collection; 28 | import java.util.Map; 29 | import java.util.Set; 30 | import org.junit.After; 31 | import org.junit.AfterClass; 32 | import org.junit.Before; 33 | import org.junit.BeforeClass; 34 | import org.junit.Test; 35 | import static org.junit.Assert.*; 36 | 37 | /** 38 | * @author blinkdog 39 | */ 40 | public class HttpRequestTest 41 | { 42 | private static final String CLIENT_HANDSHAKE = (Joiner.on("\r\n").join(Arrays.asList( 43 | "GET / HTTP/1.1", 44 | "Host: localhost:8080", 45 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", 46 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 47 | "Accept-Language: en-US,en;q=0.5", 48 | "Accept-Encoding: gzip, deflate", 49 | "Sec-WebSocket-Version: 13", 50 | "Origin: null", 51 | "Sec-WebSocket-Key: V76L7ym8nB/U/K96iWDjKg==", 52 | "Connection: keep-alive, Upgrade", 53 | "Pragma: no-cache", 54 | "Cache-Control: no-cache", 55 | "Upgrade: websocket", 56 | "" 57 | ))) + "\r\n"; 58 | 59 | private static final String MESSED_UP_HEADERS = (Joiner.on("\r\n").join(Arrays.asList( 60 | "GET / HTTP/1.1", 61 | "Host: localhost:8080", 62 | "User-Agent Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", 63 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 64 | "Accept-Language en-US,en;q=0.5", 65 | "Accept-Encoding: gzip, deflate", 66 | "Sec-WebSocket-Version 13", 67 | "Origin: null", 68 | "Sec-WebSocket-Key V76L7ym8nB/U/K96iWDjKg==", 69 | "Connection: keep-alive, Upgrade", 70 | "Pragma no-cache", 71 | "Cache-Control: no-cache", 72 | "Upgrade websocket", 73 | "" 74 | ))) + "\r\n"; 75 | 76 | private static final String INCOMPLETE_REQUEST = (Joiner.on("\r\n").join(Arrays.asList( 77 | "GET / HTTP/1.1", 78 | "Host: localhost:8080", 79 | "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", 80 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 81 | "Accept-Language: en-US,en;q=0.5", 82 | "Accept-Encoding: gzip, deflate", 83 | "Sec-WebSocket-Version: 13" 84 | ))) + "\r\n"; 85 | 86 | private static final String BROKEN_HEADERS = (Joiner.on("\r\n").join(Arrays.asList( 87 | "GET / HTTP/1.1", 88 | ": localhost:8080", 89 | ":::: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", 90 | " : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 91 | "JustABunchOfStuff:", 92 | ":JustABunchOfStuff", 93 | ":::JustABunchOfStuff:::", 94 | " : : : JustABunchOfStuff : : : ", 95 | "dflkjdlk j d lkjdlkjflkdjf lkj dl kfjdlkjfdlkjflkdjflkdjlkfjdlkjfdlkjflkdjflkdjlfkjdlkfjdlkfjdlk:", 96 | "" 97 | ))) + "\r\n"; 98 | 99 | public HttpRequestTest() { 100 | } 101 | 102 | @BeforeClass 103 | public static void setUpClass() { 104 | } 105 | 106 | @AfterClass 107 | public static void tearDownClass() { 108 | } 109 | 110 | @Before 111 | public void setUp() { 112 | } 113 | 114 | @After 115 | public void tearDown() { 116 | } 117 | 118 | @Test 119 | public void testAlwaysSucceed() { 120 | assertTrue(true); 121 | } 122 | 123 | @Test 124 | public void testConstructNullReader() { 125 | try { 126 | HttpRequest hr = new HttpRequest(null); 127 | fail(); 128 | } catch(NullPointerException e) { 129 | // expected 130 | } 131 | } 132 | 133 | @Test 134 | public void testRequestLineConstantDefined() { 135 | assertNotNull(HttpRequest.REQUEST_LINE); 136 | } 137 | 138 | @Test 139 | public void testConstructWithEmptyReader() { 140 | ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes()); 141 | HttpRequest hr = new HttpRequest(bais); 142 | assertEquals(1, hr.size()); 143 | assertEquals(null, hr.get(HttpRequest.REQUEST_LINE)); 144 | } 145 | 146 | @Test 147 | public void testWithClientHandshake() { 148 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes()); 149 | HttpRequest hr = new HttpRequest(bais); 150 | assertEquals(25, hr.size()); 151 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 152 | assertEquals("localhost:8080", hr.get("Host")); 153 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent")); 154 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept")); 155 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language")); 156 | assertEquals("gzip, deflate", hr.get("Accept-Encoding")); 157 | assertEquals("13", hr.get("Sec-WebSocket-Version")); 158 | assertEquals("null", hr.get("Origin")); 159 | assertEquals("V76L7ym8nB/U/K96iWDjKg==", hr.get("Sec-WebSocket-Key")); 160 | assertEquals("keep-alive, Upgrade", hr.get("Connection")); 161 | assertEquals("no-cache", hr.get("Pragma")); 162 | assertEquals("no-cache", hr.get("Cache-Control")); 163 | assertEquals("websocket", hr.get("Upgrade")); 164 | } 165 | 166 | @Test 167 | public void testConstructWithBlankLines() { 168 | ByteArrayInputStream bais = new ByteArrayInputStream("\r\n\r\n\r\n".getBytes()); 169 | HttpRequest hr = new HttpRequest(bais); 170 | assertEquals(1, hr.size()); 171 | assertEquals(null, hr.get(HttpRequest.REQUEST_LINE)); 172 | } 173 | 174 | @Test 175 | public void testWithMessedUpHeaders() { 176 | ByteArrayInputStream bais = new ByteArrayInputStream(MESSED_UP_HEADERS.getBytes()); 177 | HttpRequest hr = new HttpRequest(bais); 178 | assertEquals(15, hr.size()); 179 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 180 | assertEquals("localhost:8080", hr.get("Host")); 181 | assertEquals(null, hr.get("User-Agent")); 182 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept")); 183 | assertEquals(null, hr.get("Accept-Language")); 184 | assertEquals("gzip, deflate", hr.get("Accept-Encoding")); 185 | assertEquals(null, hr.get("Sec-WebSocket-Version")); 186 | assertEquals("null", hr.get("Origin")); 187 | assertEquals(null, hr.get("Sec-WebSocket-Key")); 188 | assertEquals("keep-alive, Upgrade", hr.get("Connection")); 189 | assertEquals(null, hr.get("Pragma")); 190 | assertEquals("no-cache", hr.get("Cache-Control")); 191 | assertEquals(null, hr.get("Upgrade")); 192 | } 193 | 194 | @Test 195 | public void testWithIncompleteRequest() { 196 | ByteArrayInputStream bais = new ByteArrayInputStream(INCOMPLETE_REQUEST.getBytes()); 197 | HttpRequest hr = new HttpRequest(bais); 198 | assertEquals(13, hr.size()); 199 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 200 | assertEquals("localhost:8080", hr.get("Host")); 201 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent")); 202 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept")); 203 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language")); 204 | assertEquals("gzip, deflate", hr.get("Accept-Encoding")); 205 | assertEquals("13", hr.get("Sec-WebSocket-Version")); 206 | } 207 | 208 | @Test 209 | public void testWithIncompleteRequestThatThrows() { 210 | InputStream bytesThenThrow = new InputStream() { 211 | final byte[] bytes = INCOMPLETE_REQUEST.getBytes(); 212 | final ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 213 | int count = 0; 214 | @Override 215 | public int read() throws IOException { 216 | if(count < bytes.length) { 217 | int data = (int)bytes[count]; 218 | count++; 219 | return data; 220 | } else { 221 | throw new IOException("Oh frell..."); 222 | } 223 | } 224 | }; 225 | HttpRequest hr = new HttpRequest(bytesThenThrow); 226 | assertEquals(13, hr.size()); 227 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 228 | assertEquals("localhost:8080", hr.get("Host")); 229 | assertEquals("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0", hr.get("User-Agent")); 230 | assertEquals("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", hr.get("Accept")); 231 | assertEquals("en-US,en;q=0.5", hr.get("Accept-Language")); 232 | assertEquals("gzip, deflate", hr.get("Accept-Encoding")); 233 | assertEquals("13", hr.get("Sec-WebSocket-Version")); 234 | } 235 | 236 | @Test 237 | public void testMapMutatorsFailWithException() { 238 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes()); 239 | HttpRequest hr = new HttpRequest(bais); 240 | assertEquals(25, hr.size()); 241 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 242 | assertEquals("localhost:8080", hr.get("Host")); 243 | 244 | try { 245 | hr.put("Custom-Header", "Custom Data For Header"); 246 | fail(); 247 | } catch(UnsupportedOperationException e) { 248 | // expected 249 | } 250 | 251 | try { 252 | hr.remove("Host"); 253 | fail(); 254 | } catch(UnsupportedOperationException e) { 255 | // expected 256 | } 257 | 258 | try { 259 | ImmutableMap myMap = ImmutableMap.builder() 260 | .put("Header-One", "Data One") 261 | .put("Header-Two", "Data Two") 262 | .put("Header-Three", "Data Three") 263 | .build(); 264 | hr.putAll(myMap); 265 | fail(); 266 | } catch(UnsupportedOperationException e) { 267 | // expected 268 | } 269 | 270 | try { 271 | hr.clear(); 272 | fail(); 273 | } catch(UnsupportedOperationException e) { 274 | // expected 275 | } 276 | } 277 | 278 | @Test 279 | public void testMapAccessorsWork() { 280 | ByteArrayInputStream bais = new ByteArrayInputStream(CLIENT_HANDSHAKE.getBytes()); 281 | HttpRequest hr = new HttpRequest(bais); 282 | assertEquals(25, hr.size()); 283 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 284 | assertEquals("localhost:8080", hr.get("Host")); 285 | 286 | assertFalse(hr.isEmpty()); 287 | 288 | assertTrue(hr.containsKey("Host")); 289 | assertFalse(hr.containsKey("Dalek-Malware")); 290 | 291 | assertTrue(hr.containsValue("localhost:8080")); 292 | assertFalse(hr.containsValue("Exterminate!")); 293 | 294 | Set keys = hr.keySet(); 295 | assertNotNull(keys); 296 | assertEquals(25, keys.size()); 297 | 298 | Collection values = hr.values(); 299 | assertNotNull(values); 300 | assertEquals(25, values.size()); 301 | 302 | Set> entrySet = hr.entrySet(); 303 | assertNotNull(entrySet); 304 | assertEquals(25, entrySet.size()); 305 | } 306 | 307 | @Test 308 | public void testWithBrokenHeaders() { 309 | ByteArrayInputStream bais = new ByteArrayInputStream(BROKEN_HEADERS.getBytes()); 310 | HttpRequest hr = new HttpRequest(bais); 311 | assertEquals(1, hr.size()); 312 | assertEquals("GET / HTTP/1.1", hr.get(HttpRequest.REQUEST_LINE)); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/test/java/com/pmeade/websocket/io/LineInputStreamTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * LineInputStreamTest.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.io; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.ByteArrayInputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import org.junit.After; 26 | import org.junit.AfterClass; 27 | import org.junit.Before; 28 | import org.junit.BeforeClass; 29 | import org.junit.Test; 30 | 31 | import static org.junit.Assert.*; 32 | 33 | /** 34 | * @author blinkdog 35 | */ 36 | public class LineInputStreamTest 37 | { 38 | public LineInputStreamTest() { 39 | } 40 | 41 | @BeforeClass 42 | public static void setUpClass() { 43 | } 44 | 45 | @AfterClass 46 | public static void tearDownClass() { 47 | } 48 | 49 | @Before 50 | public void setUp() { 51 | } 52 | 53 | @After 54 | public void tearDown() { 55 | } 56 | 57 | @Test 58 | public void testAlwaysSucceed() { 59 | assertTrue(true); 60 | } 61 | 62 | @Test 63 | public void testRequireDecoratee() { 64 | try { 65 | LineInputStream lis = new LineInputStream(null); 66 | fail(); 67 | } catch (NullPointerException e) { 68 | // expected 69 | } 70 | } 71 | 72 | @Test 73 | public void testConstructWithInputStream() throws Exception { 74 | ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes()); 75 | LineInputStream lis = new LineInputStream(bais); 76 | assertNotNull(lis); 77 | assertNull(lis.readLine()); 78 | } 79 | 80 | @Test 81 | public void testOneLineNoLineEndings() throws Exception { 82 | ByteArrayInputStream bais = new ByteArrayInputStream("Dalek".getBytes()); 83 | LineInputStream lis = new LineInputStream(bais); 84 | assertNotNull(lis); 85 | assertEquals("Dalek", lis.readLine()); 86 | assertEquals(null, lis.readLine()); 87 | } 88 | 89 | @Test 90 | public void testTwoLinesOneLineEnding() throws Exception { 91 | String[] inputs = { 92 | "Cyberman\r\n", 93 | "Dalek" 94 | }; 95 | StringBuilder sb = new StringBuilder(); 96 | for(String input : inputs) { 97 | sb.append(input); 98 | } 99 | byte[] bytes = sb.toString().getBytes(); 100 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 101 | LineInputStream lis = new LineInputStream(bais); 102 | assertNotNull(lis); 103 | assertEquals("Cyberman", lis.readLine()); 104 | assertEquals("Dalek", lis.readLine()); 105 | assertEquals(null, lis.readLine()); 106 | } 107 | 108 | @Test 109 | public void testSeveralLinesAllEndings() throws Exception { 110 | String[] inputs = { 111 | "Alice\r\n", 112 | "Bob\r\n", 113 | "Carol\r\n", 114 | "Dave\r\n", 115 | "Eve\r\n", 116 | "Mallory\r\n", 117 | "Trent\r\n" 118 | }; 119 | StringBuilder sb = new StringBuilder(); 120 | for(String input : inputs) { 121 | sb.append(input); 122 | } 123 | byte[] bytes = sb.toString().getBytes(); 124 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 125 | LineInputStream lis = new LineInputStream(bais); 126 | assertNotNull(lis); 127 | assertEquals("Alice", lis.readLine()); 128 | assertEquals("Bob", lis.readLine()); 129 | assertEquals("Carol", lis.readLine()); 130 | assertEquals("Dave", lis.readLine()); 131 | assertEquals("Eve", lis.readLine()); 132 | assertEquals("Mallory", lis.readLine()); 133 | assertEquals("Trent", lis.readLine()); 134 | assertEquals(null, lis.readLine()); 135 | } 136 | 137 | @Test 138 | public void testSeveralLinesMixedEndings() throws Exception { 139 | String[] inputs = { 140 | "Alice\r", 141 | "Bob\n", 142 | "Carol\r\n", 143 | "Dave\r", 144 | "Eve\n", 145 | "Mallory\r\n", 146 | "Trent\r\n", 147 | "Eve\n", 148 | "Dave\r", 149 | "Carol\r\n", 150 | "Eve\n", 151 | }; 152 | StringBuilder sb = new StringBuilder(); 153 | for(String input : inputs) { 154 | sb.append(input); 155 | } 156 | byte[] bytes = sb.toString().getBytes(); 157 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 158 | LineInputStream lis = new LineInputStream(bais); 159 | assertNotNull(lis); 160 | assertEquals("Alice\rBob\nCarol", lis.readLine()); 161 | assertEquals("Dave\rEve\nMallory", lis.readLine()); 162 | assertEquals("Trent", lis.readLine()); 163 | assertEquals("Eve\nDave\rCarol", lis.readLine()); 164 | assertEquals("Eve\n", lis.readLine()); 165 | assertEquals(null, lis.readLine()); 166 | } 167 | 168 | @Test 169 | public void testPlainRead() throws Exception { 170 | ByteArrayInputStream bais = new ByteArrayInputStream("Dalek".getBytes()); 171 | LineInputStream lis = new LineInputStream(bais); 172 | assertNotNull(lis); 173 | assertEquals((int)'D', lis.read()); 174 | assertEquals((int)'a', lis.read()); 175 | assertEquals((int)'l', lis.read()); 176 | assertEquals((int)'e', lis.read()); 177 | assertEquals((int)'k', lis.read()); 178 | assertEquals(-1, lis.read()); 179 | } 180 | 181 | @Test 182 | public void testDecoratedIOExceptionNotSuppressed() throws Exception { 183 | InputStream is = new InputStream() { 184 | @Override 185 | public int read() throws IOException { 186 | throw new IOException("Exterminate!"); 187 | } 188 | }; 189 | LineInputStream lis = new LineInputStream(is); 190 | assertNotNull(lis); 191 | try { 192 | lis.read(); 193 | fail(); 194 | } catch (IOException e) { 195 | // expected 196 | } 197 | } 198 | 199 | @Test 200 | public void testSeveralLinesSomeEmpty() throws Exception { 201 | String[] inputs = { 202 | "Alice\r\n", 203 | "Bob\r\n", 204 | "\r\n", 205 | "Dave\r\n", 206 | "Eve\r\n", 207 | "\r\n", 208 | "Trent\r\n" 209 | }; 210 | StringBuilder sb = new StringBuilder(); 211 | for(String input : inputs) { 212 | sb.append(input); 213 | } 214 | byte[] bytes = sb.toString().getBytes(); 215 | ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 216 | LineInputStream lis = new LineInputStream(bais); 217 | assertNotNull(lis); 218 | assertEquals("Alice", lis.readLine()); 219 | assertEquals("Bob", lis.readLine()); 220 | assertEquals("", lis.readLine()); 221 | assertEquals("Dave", lis.readLine()); 222 | assertEquals("Eve", lis.readLine()); 223 | assertEquals("", lis.readLine()); 224 | assertEquals("Trent", lis.readLine()); 225 | assertEquals(null, lis.readLine()); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/test/java/com/pmeade/websocket/io/WebSocketServerOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocketServerOutputStreamTest.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.io; 20 | 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.OutputStream; 23 | import org.junit.After; 24 | import org.junit.AfterClass; 25 | import org.junit.Before; 26 | import org.junit.BeforeClass; 27 | import org.junit.Test; 28 | import static org.junit.Assert.*; 29 | 30 | /** 31 | * @author pmeade 32 | */ 33 | public class WebSocketServerOutputStreamTest 34 | { 35 | public WebSocketServerOutputStreamTest() { 36 | } 37 | 38 | @BeforeClass 39 | public static void setUpClass() { 40 | } 41 | 42 | @AfterClass 43 | public static void tearDownClass() { 44 | } 45 | 46 | @Before 47 | public void setUp() { 48 | } 49 | 50 | @After 51 | public void tearDown() { 52 | } 53 | 54 | @Test 55 | public void testAlwaysSucceed() { 56 | assertTrue(true); 57 | } 58 | 59 | @Test 60 | public void testExtendsOutputStream() { 61 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 62 | WebSocketServerOutputStream wsos = new WebSocketServerOutputStream(baos); 63 | assertTrue(wsos instanceof OutputStream); 64 | } 65 | 66 | @Test 67 | public void testRequiresDecorableOutputStream() { 68 | try { 69 | WebSocketServerOutputStream webSocketOutputStream = new WebSocketServerOutputStream(null); 70 | fail(); 71 | } catch(NullPointerException e) { 72 | // expected 73 | } 74 | } 75 | 76 | @Test 77 | public void testSetHandshakeComplete() { 78 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 79 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 80 | assertFalse(wssos.isHandshakeComplete()); 81 | wssos.setHandshakeComplete(true); 82 | assertTrue(wssos.isHandshakeComplete()); 83 | } 84 | 85 | @Test 86 | public void testWriteArrayTransparentBeforeHandshake() throws Exception { 87 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 88 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 89 | assertFalse(wssos.isHandshakeComplete()); 90 | wssos.write(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }); 91 | byte[] result = baos.toByteArray(); 92 | assertEquals(0x01, result[0]); 93 | assertEquals(0x02, result[1]); 94 | assertEquals(0x03, result[2]); 95 | assertEquals(0x04, result[3]); 96 | assertEquals(0x05, result[4]); 97 | } 98 | 99 | @Test 100 | public void testWriteArrayNotTransparentAfterHandshake() throws Exception { 101 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 102 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 103 | assertFalse(wssos.isHandshakeComplete()); 104 | wssos.setHandshakeComplete(true); 105 | assertTrue(wssos.isHandshakeComplete()); 106 | wssos.write(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }); 107 | byte[] result = baos.toByteArray(); 108 | assertEquals((byte)0x82, result[0]); 109 | assertEquals(0x05, result[1]); 110 | assertEquals(0x01, result[2]); 111 | assertEquals(0x02, result[3]); 112 | assertEquals(0x03, result[4]); 113 | assertEquals(0x04, result[5]); 114 | assertEquals(0x05, result[6]); 115 | } 116 | 117 | @Test 118 | public void testWriteByteTransparentBeforeHandshake() throws Exception { 119 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 120 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 121 | assertFalse(wssos.isHandshakeComplete()); 122 | wssos.write(0x05); 123 | byte[] result = baos.toByteArray(); 124 | assertEquals(0x05, result[0]); 125 | } 126 | 127 | @Test 128 | public void testWriteByteNotTransparentAfterHandshake() throws Exception { 129 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 130 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 131 | assertFalse(wssos.isHandshakeComplete()); 132 | wssos.setHandshakeComplete(true); 133 | assertTrue(wssos.isHandshakeComplete()); 134 | wssos.write(0x05); 135 | byte[] result = baos.toByteArray(); 136 | assertEquals((byte)0x82, result[0]); 137 | assertEquals(0x01, result[1]); 138 | assertEquals(0x05, result[2]); 139 | } 140 | 141 | @Test 142 | public void testWriteArrayOffsetTransparentBeforeHandshake() throws Exception { 143 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 144 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 145 | assertFalse(wssos.isHandshakeComplete()); 146 | final byte[] DATA = new byte[] { 147 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 148 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f 149 | }; 150 | wssos.write(DATA, 2, 4); 151 | byte[] result = baos.toByteArray(); 152 | assertEquals(0x02, result[0]); 153 | assertEquals(0x03, result[1]); 154 | assertEquals(0x04, result[2]); 155 | assertEquals(0x05, result[3]); 156 | } 157 | 158 | @Test 159 | public void testWriteArrayOffsetNotTransparentAfterHandshake() throws Exception { 160 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 161 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 162 | assertFalse(wssos.isHandshakeComplete()); 163 | wssos.setHandshakeComplete(true); 164 | assertTrue(wssos.isHandshakeComplete()); 165 | final byte[] DATA = new byte[] { 166 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 167 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f 168 | }; 169 | wssos.write(DATA, 2, 4); 170 | byte[] result = baos.toByteArray(); 171 | assertEquals((byte)0x82, result[0]); 172 | assertEquals(0x04, result[1]); 173 | assertEquals(0x02, result[2]); 174 | assertEquals(0x03, result[3]); 175 | assertEquals(0x04, result[4]); 176 | assertEquals(0x05, result[5]); 177 | } 178 | 179 | @Test 180 | public void testWriteCloseTwiceOutputsOneFrame() throws Exception { 181 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 182 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 183 | assertFalse(wssos.isHandshakeComplete()); 184 | wssos.setHandshakeComplete(true); 185 | assertTrue(wssos.isHandshakeComplete()); 186 | wssos.writeClose(); 187 | wssos.writeClose(); 188 | byte[] result = baos.toByteArray(); 189 | assertEquals(2, result.length); 190 | assertEquals((byte)0x88, result[0]); 191 | assertEquals(0x00, result[1]); 192 | } 193 | 194 | @Test 195 | public void testWriteCloseStatusCodeTwiceOutputsOneFrame() throws Exception { 196 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 197 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 198 | assertFalse(wssos.isHandshakeComplete()); 199 | wssos.setHandshakeComplete(true); 200 | assertTrue(wssos.isHandshakeComplete()); 201 | wssos.writeClose(0x1000); 202 | wssos.writeClose(0x1000); 203 | byte[] result = baos.toByteArray(); 204 | assertEquals(4, result.length); 205 | assertEquals((byte)0x88, result[0]); 206 | assertEquals(0x02, result[1]); 207 | assertEquals(0x10, result[2]); 208 | assertEquals(0x00, result[3]); 209 | } 210 | 211 | @Test 212 | public void testWriteStringSmallPayload() throws Exception { 213 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 214 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 215 | assertFalse(wssos.isHandshakeComplete()); 216 | wssos.setHandshakeComplete(true); 217 | assertTrue(wssos.isHandshakeComplete()); 218 | wssos.writeString("\u00a9"); 219 | byte[] result = baos.toByteArray(); 220 | assertEquals(4, result.length); 221 | assertEquals((byte)0x81, result[0]); 222 | assertEquals(0x02, result[1]); 223 | assertEquals((byte)0xc2, result[2]); 224 | assertEquals((byte)0xa9, result[3]); 225 | } 226 | 227 | @Test 228 | public void testWriteStringMediumPayload() throws Exception { 229 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 230 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 231 | assertFalse(wssos.isHandshakeComplete()); 232 | wssos.setHandshakeComplete(true); 233 | assertTrue(wssos.isHandshakeComplete()); 234 | StringBuilder sb = new StringBuilder(); 235 | for(int i=0; i<0x100; i++) { 236 | sb.append("\u00a9"); 237 | } 238 | wssos.writeString(sb.toString()); 239 | byte[] result = baos.toByteArray(); 240 | assertEquals(516, result.length); 241 | assertEquals((byte)0x81, result[0]); 242 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_16, result[1]); 243 | assertEquals((byte)0x02, result[2]); 244 | assertEquals((byte)0x00, result[3]); 245 | assertEquals((byte)0xc2, result[4]); 246 | assertEquals((byte)0xa9, result[5]); 247 | assertEquals((byte)0xc2, result[6]); 248 | assertEquals((byte)0xa9, result[7]); 249 | } 250 | 251 | @Test 252 | public void testWriteStringLargePayload() throws Exception { 253 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 254 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 255 | assertFalse(wssos.isHandshakeComplete()); 256 | wssos.setHandshakeComplete(true); 257 | assertTrue(wssos.isHandshakeComplete()); 258 | StringBuilder sb = new StringBuilder(); 259 | for(int i=0; i<0x10000; i++) { 260 | sb.append("\u00a9"); 261 | } 262 | wssos.writeString(sb.toString()); 263 | byte[] result = baos.toByteArray(); 264 | assertEquals(0x20000 + 10, result.length); 265 | assertEquals((byte)0x81, result[0]); 266 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_64, result[1]); 267 | assertEquals((byte)0x00, result[2]); 268 | assertEquals((byte)0x00, result[3]); 269 | assertEquals((byte)0x00, result[4]); 270 | assertEquals((byte)0x00, result[5]); 271 | assertEquals((byte)0x00, result[6]); 272 | assertEquals((byte)0x02, result[7]); 273 | assertEquals((byte)0x00, result[8]); 274 | assertEquals((byte)0x00, result[9]); 275 | assertEquals((byte)0xc2, result[10]); 276 | assertEquals((byte)0xa9, result[11]); 277 | assertEquals((byte)0xc2, result[12]); 278 | assertEquals((byte)0xa9, result[13]); 279 | assertEquals((byte)0xc2, result[14]); 280 | assertEquals((byte)0xa9, result[15]); 281 | } 282 | 283 | @Test 284 | public void testWriteBinaryMediumPayload() throws Exception { 285 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 286 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 287 | assertFalse(wssos.isHandshakeComplete()); 288 | wssos.setHandshakeComplete(true); 289 | assertTrue(wssos.isHandshakeComplete()); 290 | byte[] DATA = new byte[0x200]; 291 | for(int i=0; i<0x200; i++) { 292 | DATA[i] = (byte)(i & 0xff); 293 | } 294 | wssos.write(DATA); 295 | byte[] result = baos.toByteArray(); 296 | assertEquals(0x200 + 4, result.length); 297 | assertEquals((byte)0x82, result[0]); 298 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_16, result[1]); 299 | assertEquals((byte)0x02, result[2]); 300 | assertEquals((byte)0x00, result[3]); 301 | assertEquals((byte)0x00, result[4]); 302 | assertEquals((byte)0x01, result[5]); 303 | assertEquals((byte)0x02, result[6]); 304 | assertEquals((byte)0x03, result[7]); 305 | } 306 | 307 | @Test 308 | public void testWriteBinaryLargePayload() throws Exception { 309 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 310 | WebSocketServerOutputStream wssos = new WebSocketServerOutputStream(baos); 311 | assertFalse(wssos.isHandshakeComplete()); 312 | wssos.setHandshakeComplete(true); 313 | assertTrue(wssos.isHandshakeComplete()); 314 | byte[] DATA = new byte[0x20000]; 315 | for(int i=0; i<0x20000; i++) { 316 | DATA[i] = (byte)(i & 0xff); 317 | } 318 | wssos.write(DATA); 319 | byte[] result = baos.toByteArray(); 320 | assertEquals(0x20000 + 10, result.length); 321 | assertEquals((byte)0x82, result[0]); 322 | assertEquals((byte)WebSocketServerOutputStream.LENGTH_64, result[1]); 323 | assertEquals((byte)0x00, result[2]); 324 | assertEquals((byte)0x00, result[3]); 325 | assertEquals((byte)0x00, result[4]); 326 | assertEquals((byte)0x00, result[5]); 327 | assertEquals((byte)0x00, result[6]); 328 | assertEquals((byte)0x02, result[7]); 329 | assertEquals((byte)0x00, result[8]); 330 | assertEquals((byte)0x00, result[9]); 331 | assertEquals((byte)0x00, result[10]); 332 | assertEquals((byte)0x01, result[11]); 333 | assertEquals((byte)0x02, result[12]); 334 | assertEquals((byte)0x03, result[13]); 335 | assertEquals((byte)0x04, result[14]); 336 | assertEquals((byte)0x05, result[15]); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/test/java/com/pmeade/websocket/net/WebSocketServerSocketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocketServerSocketTest.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.net; 20 | 21 | import java.net.InetAddress; 22 | import java.net.ServerSocket; 23 | import java.net.SocketAddress; 24 | import java.nio.channels.ServerSocketChannel; 25 | import org.junit.After; 26 | import org.junit.AfterClass; 27 | import org.junit.Before; 28 | import org.junit.BeforeClass; 29 | import org.junit.Test; 30 | import static org.junit.Assert.*; 31 | import static org.mockito.Mockito.*; 32 | 33 | /** 34 | * @author veloxi 35 | */ 36 | public class WebSocketServerSocketTest 37 | { 38 | private ServerSocket mockSS; 39 | private WebSocketServerSocket wsss; 40 | 41 | public WebSocketServerSocketTest() { 42 | } 43 | 44 | @BeforeClass 45 | public static void setUpClass() { 46 | } 47 | 48 | @AfterClass 49 | public static void tearDownClass() { 50 | } 51 | 52 | @Before 53 | public void setUp() throws Exception { 54 | mockSS = mock(ServerSocket.class); 55 | wsss = new WebSocketServerSocket(mockSS); 56 | } 57 | 58 | @After 59 | public void tearDown() throws Exception { 60 | } 61 | 62 | @Test 63 | public void testAlwaysSucceed() { 64 | assertTrue(true); 65 | verifyZeroInteractions(mockSS); 66 | } 67 | 68 | @Test 69 | public void testExtendsServerSocket() throws Exception { 70 | assertTrue(wsss instanceof ServerSocket); 71 | verifyZeroInteractions(mockSS); 72 | } 73 | 74 | @Test 75 | public void testBindDelegatesToServerSocket() throws Exception { 76 | SocketAddress sa = mock(SocketAddress.class); 77 | wsss.bind(sa); 78 | verify(mockSS).bind(sa); 79 | verifyZeroInteractions(sa); 80 | } 81 | 82 | @Test 83 | public void testBindWithBacklogDelegatesToServerSocket() throws Exception { 84 | SocketAddress sa = mock(SocketAddress.class); 85 | wsss.bind(sa, 12345); 86 | verify(mockSS).bind(sa, 12345); 87 | verifyZeroInteractions(sa); 88 | } 89 | 90 | @Test 91 | public void testGetInetAddressDelegatesToServerSocket() throws Exception { 92 | InetAddress ia = mock(InetAddress.class); 93 | when(mockSS.getInetAddress()).thenReturn(ia); 94 | InetAddress result = wsss.getInetAddress(); 95 | assertEquals(ia, result); 96 | verify(mockSS).getInetAddress(); 97 | verifyZeroInteractions(ia); 98 | } 99 | 100 | @Test 101 | public void testGetLocalPortDelegatesToServerSocket() throws Exception { 102 | when(mockSS.getLocalPort()).thenReturn(42); 103 | int result = wsss.getLocalPort(); 104 | assertEquals(42, result); 105 | verify(mockSS).getLocalPort(); 106 | } 107 | 108 | @Test 109 | public void testGetLocalAddressDelegatesToServerSocket() throws Exception { 110 | SocketAddress sa = mock(SocketAddress.class); 111 | when(mockSS.getLocalSocketAddress()).thenReturn(sa); 112 | SocketAddress result = wsss.getLocalSocketAddress(); 113 | assertEquals(sa, result); 114 | verify(mockSS).getLocalSocketAddress(); 115 | verifyZeroInteractions(sa); 116 | } 117 | 118 | @Test 119 | public void testGetChannelThrowsUnsupportedOperationException() throws Exception { 120 | try { 121 | ServerSocketChannel ssc = wsss.getChannel(); 122 | fail(); 123 | } catch (UnsupportedOperationException e) { 124 | // expected 125 | } 126 | verifyZeroInteractions(mockSS); 127 | } 128 | 129 | @Test 130 | public void testSetSoTimeoutDelegatesToSocket() throws Exception { 131 | wsss.setSoTimeout(123456); 132 | verify(mockSS).setSoTimeout(123456); 133 | } 134 | 135 | @Test 136 | public void testGetSoTimeoutDelegatesToSocket() throws Exception { 137 | when(mockSS.getSoTimeout()).thenReturn(123456); 138 | assertEquals(123456, wsss.getSoTimeout()); 139 | verify(mockSS).getSoTimeout(); 140 | } 141 | 142 | @Test 143 | public void testSetReceiveBufferSizeDelegatesToSocket() throws Exception { 144 | wsss.setReceiveBufferSize(654321); 145 | verify(mockSS).setReceiveBufferSize(654321); 146 | } 147 | 148 | @Test 149 | public void testGetReceiveBufferSizeDelegatesToSocket() throws Exception { 150 | when(mockSS.getReceiveBufferSize()).thenReturn(654321); 151 | assertEquals(654321, wsss.getReceiveBufferSize()); 152 | verify(mockSS).getReceiveBufferSize(); 153 | } 154 | 155 | @Test 156 | public void testSetReuseAddressDelegatesToSocket() throws Exception { 157 | wsss.setReuseAddress(true); 158 | wsss.setReuseAddress(false); 159 | verify(mockSS).setReuseAddress(true); 160 | verify(mockSS).setReuseAddress(false); 161 | } 162 | 163 | @Test 164 | public void testGetReuseAddressDelegatesToSocket() throws Exception { 165 | when(mockSS.getReuseAddress()).thenReturn(false); 166 | assertFalse(wsss.getReuseAddress()); 167 | verify(mockSS).getReuseAddress(); 168 | } 169 | 170 | @Test 171 | public void testCloseDelegatesToSocket() throws Exception { 172 | wsss.close(); 173 | verify(mockSS).close(); 174 | } 175 | 176 | @Test 177 | public void testToStringDelegatesToSocket() throws Exception { 178 | when(mockSS.toString()).thenReturn("Exterminate!"); 179 | assertEquals("Exterminate!", wsss.toString()); 180 | // toString() ... Mockito's Kryptonite 181 | // verify(mockSS).toString(); 182 | } 183 | 184 | @Test 185 | public void testIsBoundDelegatesToSocket() throws Exception { 186 | when(mockSS.isBound()).thenReturn(true); 187 | assertTrue(wsss.isBound()); 188 | verify(mockSS).isBound(); 189 | } 190 | 191 | @Test 192 | public void testIsClosedDelegatesToSocket() throws Exception { 193 | when(mockSS.isClosed()).thenReturn(true); 194 | assertTrue(wsss.isClosed()); 195 | verify(mockSS).isClosed(); 196 | } 197 | 198 | @Test 199 | public void testSetPerformancePreferencesDelegatesToSocket() throws Exception { 200 | wsss.setPerformancePreferences(123, 456, 789); 201 | verify(mockSS).setPerformancePreferences(123, 456, 789); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/test/java/com/pmeade/websocket/net/WebSocketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * WebSocketTest.java 3 | * Copyright 2014 Patrick Meade. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package com.pmeade.websocket.net; 20 | 21 | import com.pmeade.websocket.io.WebSocketServerInputStream; 22 | import com.pmeade.websocket.io.WebSocketServerOutputStream; 23 | import java.io.InputStream; 24 | import java.io.OutputStream; 25 | import java.net.InetAddress; 26 | import java.net.ServerSocket; 27 | import java.net.Socket; 28 | import java.net.SocketAddress; 29 | import java.nio.channels.SocketChannel; 30 | import org.junit.After; 31 | import org.junit.AfterClass; 32 | import org.junit.Before; 33 | import org.junit.BeforeClass; 34 | import org.junit.Test; 35 | import static org.junit.Assert.*; 36 | import static org.mockito.Mockito.*; 37 | 38 | /** 39 | * @author veloxi 40 | */ 41 | public class WebSocketTest 42 | { 43 | private Socket mockS; 44 | private ServerSocket mockSS; 45 | private Socket ws; 46 | private WebSocketServerSocket wsss; 47 | 48 | public WebSocketTest() { 49 | } 50 | 51 | @BeforeClass 52 | public static void setUpClass() { 53 | } 54 | 55 | @AfterClass 56 | public static void tearDownClass() { 57 | } 58 | 59 | @Before 60 | public void setUp() throws Exception { 61 | mockS = mock(Socket.class); 62 | mockSS = mock(ServerSocket.class); 63 | when(mockSS.accept()).thenReturn(mockS); 64 | wsss = new WebSocketServerSocket(mockSS); 65 | ws = wsss.accept(); 66 | } 67 | 68 | @After 69 | public void tearDown() throws Exception { 70 | verify(mockSS, times(1)).accept(); 71 | } 72 | 73 | @Test 74 | public void testAlwaysSucceed() { 75 | assertTrue(true); 76 | verifyZeroInteractions(mockS); 77 | } 78 | 79 | @Test 80 | public void testExtendsSocket() { 81 | Socket s = new Socket(); 82 | WebSocket webSocket = new WebSocket(s); 83 | assertTrue(webSocket instanceof Socket); 84 | verifyZeroInteractions(mockS); 85 | } 86 | 87 | @Test 88 | public void testAcceptReturnsWebSocket() throws Exception { 89 | assertTrue(ws instanceof WebSocket); 90 | verifyZeroInteractions(mockS); 91 | } 92 | 93 | @Test 94 | public void testConnectDelegatesToSocket() throws Exception { 95 | SocketAddress sa = mock(SocketAddress.class); 96 | ws.connect(sa); 97 | verify(mockS).connect(sa); 98 | verifyZeroInteractions(sa); 99 | } 100 | 101 | @Test 102 | public void testConnectWithTimeoutDelegatesToSocket() throws Exception { 103 | SocketAddress sa = mock(SocketAddress.class); 104 | ws.connect(sa, 9001); 105 | verify(mockS).connect(sa, 9001); 106 | verifyZeroInteractions(sa); 107 | } 108 | 109 | @Test 110 | public void testBindDelegatesToSocket() throws Exception { 111 | SocketAddress sa = mock(SocketAddress.class); 112 | ws.bind(sa); 113 | verify(mockS).bind(sa); 114 | verifyZeroInteractions(sa); 115 | } 116 | 117 | @Test 118 | public void testGetInetAddressDelegatesToSocket() throws Exception { 119 | InetAddress ia = mock(InetAddress.class); 120 | when(mockS.getInetAddress()).thenReturn(ia); 121 | InetAddress result = ws.getInetAddress(); 122 | assertEquals(ia, result); 123 | verify(mockS).getInetAddress(); 124 | verifyZeroInteractions(ia); 125 | } 126 | 127 | @Test 128 | public void testGetLocalAddressDelegatesToSocket() throws Exception { 129 | InetAddress ia = mock(InetAddress.class); 130 | when(mockS.getLocalAddress()).thenReturn(ia); 131 | InetAddress result = ws.getLocalAddress(); 132 | assertEquals(ia, result); 133 | verify(mockS).getLocalAddress(); 134 | verifyZeroInteractions(ia); 135 | } 136 | 137 | @Test 138 | public void testGetPortDelegatesToSocket() throws Exception { 139 | when(mockS.getPort()).thenReturn(42); 140 | int result = ws.getPort(); 141 | assertEquals(42, result); 142 | verify(mockS).getPort(); 143 | } 144 | 145 | @Test 146 | public void testGetLocalPortDelegatesToSocket() throws Exception { 147 | when(mockS.getLocalPort()).thenReturn(69); 148 | int result = ws.getLocalPort(); 149 | assertEquals(69, result); 150 | verify(mockS).getLocalPort(); 151 | } 152 | 153 | @Test 154 | public void testGetRemoteSocketAddressDelegatesToSocket() throws Exception { 155 | SocketAddress sa = mock(SocketAddress.class); 156 | when(mockS.getRemoteSocketAddress()).thenReturn(sa); 157 | SocketAddress result = ws.getRemoteSocketAddress(); 158 | assertEquals(sa, result); 159 | verify(mockS).getRemoteSocketAddress(); 160 | verifyZeroInteractions(sa); 161 | } 162 | 163 | @Test 164 | public void testGetLocalSocketAddressDelegatesToSocket() throws Exception { 165 | SocketAddress sa = mock(SocketAddress.class); 166 | when(mockS.getLocalSocketAddress()).thenReturn(sa); 167 | SocketAddress result = ws.getLocalSocketAddress(); 168 | assertEquals(sa, result); 169 | verify(mockS).getLocalSocketAddress(); 170 | verifyZeroInteractions(sa); 171 | } 172 | 173 | @Test 174 | public void testGetChannelThrowsUnsupportedOperationException() throws Exception { 175 | try { 176 | SocketChannel sc = ws.getChannel(); 177 | fail(); 178 | } catch (UnsupportedOperationException e) { 179 | // expected 180 | } 181 | verifyZeroInteractions(mockS); 182 | } 183 | 184 | @Test 185 | public void testGetInputStreamReturnsWebSocketServerInputStream() throws Exception { 186 | InputStream is = mock(InputStream.class); 187 | OutputStream os = mock(OutputStream.class); 188 | when(mockS.getInputStream()).thenReturn(is); 189 | when(mockS.getOutputStream()).thenReturn(os); 190 | InputStream result = ws.getInputStream(); 191 | assertNotEquals(is, result); 192 | assertTrue(result instanceof WebSocketServerInputStream); 193 | verify(mockS).getInputStream(); 194 | verify(mockS).getOutputStream(); 195 | verifyZeroInteractions(is); 196 | verifyZeroInteractions(os); 197 | } 198 | 199 | @Test 200 | public void testWebSocketServerInputStreamHasOutputPeer() throws Exception { 201 | InputStream is = mock(InputStream.class); 202 | OutputStream os = mock(OutputStream.class); 203 | when(mockS.getInputStream()).thenReturn(is); 204 | when(mockS.getOutputStream()).thenReturn(os); 205 | InputStream result = ws.getInputStream(); 206 | assertNotEquals(is, result); 207 | assertTrue(result instanceof WebSocketServerInputStream); 208 | WebSocketServerInputStream wssis = (WebSocketServerInputStream) result; 209 | assertNotNull(wssis.getOutputPeer()); 210 | verify(mockS).getInputStream(); 211 | verify(mockS).getOutputStream(); 212 | verifyZeroInteractions(is); 213 | verifyZeroInteractions(os); 214 | } 215 | 216 | @Test 217 | public void testGetInputStreamTwiceReturnsSameObject() throws Exception { 218 | InputStream is = mock(InputStream.class); 219 | OutputStream os = mock(OutputStream.class); 220 | when(mockS.getInputStream()).thenReturn(is); 221 | when(mockS.getOutputStream()).thenReturn(os); 222 | InputStream result = ws.getInputStream(); 223 | assertNotEquals(is, result); 224 | assertTrue(result instanceof WebSocketServerInputStream); 225 | WebSocketServerInputStream wssis = (WebSocketServerInputStream) result; 226 | assertNotNull(wssis.getOutputPeer()); 227 | InputStream result2 = ws.getInputStream(); 228 | assertNotNull(result2); 229 | assertEquals(result, result2); 230 | assertEquals(wssis, result2); 231 | verify(mockS, times(1)).getInputStream(); 232 | verify(mockS, times(1)).getOutputStream(); 233 | verifyZeroInteractions(is); 234 | verifyZeroInteractions(os); 235 | } 236 | 237 | @Test 238 | public void testGetOutputStreamReturnsWebSocketServerOutputStream() throws Exception { 239 | OutputStream os = mock(OutputStream.class); 240 | when(mockS.getOutputStream()).thenReturn(os); 241 | OutputStream result = ws.getOutputStream(); 242 | assertNotEquals(os, result); 243 | assertTrue(result instanceof WebSocketServerOutputStream); 244 | verify(mockS).getOutputStream(); 245 | verifyZeroInteractions(os); 246 | } 247 | 248 | @Test 249 | public void testGetOutputStreamTwiceReturnsSameObject() throws Exception { 250 | OutputStream os = mock(OutputStream.class); 251 | when(mockS.getOutputStream()).thenReturn(os); 252 | OutputStream result = ws.getOutputStream(); 253 | assertNotEquals(os, result); 254 | assertTrue(result instanceof WebSocketServerOutputStream); 255 | WebSocketServerOutputStream wssos = (WebSocketServerOutputStream) result; 256 | OutputStream result2 = ws.getOutputStream(); 257 | assertNotNull(result2); 258 | assertEquals(result, result2); 259 | assertEquals(wssos, result2); 260 | verify(mockS, times(1)).getOutputStream(); 261 | verifyZeroInteractions(os); 262 | } 263 | 264 | @Test 265 | public void testSetTcpNoDelayDelegatesToSocket() throws Exception { 266 | ws.setTcpNoDelay(true); 267 | ws.setTcpNoDelay(false); 268 | verify(mockS).setTcpNoDelay(true); 269 | verify(mockS).setTcpNoDelay(false); 270 | } 271 | 272 | @Test 273 | public void testGetTcpNoDelayDelegatesToSocket() throws Exception { 274 | when(mockS.getTcpNoDelay()).thenReturn(false); 275 | assertFalse(ws.getTcpNoDelay()); 276 | verify(mockS).getTcpNoDelay(); 277 | } 278 | 279 | @Test 280 | public void testGetTcpNoDelayDelegatesToSocket2() throws Exception { 281 | when(mockS.getTcpNoDelay()).thenReturn(true); 282 | assertTrue(ws.getTcpNoDelay()); 283 | verify(mockS).getTcpNoDelay(); 284 | } 285 | 286 | @Test 287 | public void testSetSoLignerDelegatesToSocket() throws Exception { 288 | ws.setSoLinger(true, 1337); 289 | verify(mockS).setSoLinger(true, 1337); 290 | } 291 | 292 | @Test 293 | public void testGetSoLingerDelegatesToSocket() throws Exception { 294 | when(mockS.getSoLinger()).thenReturn(1337); 295 | assertEquals(1337, ws.getSoLinger()); 296 | verify(mockS).getSoLinger(); 297 | } 298 | 299 | @Test 300 | public void testSendUrgentDataDelegatesToSocket() throws Exception { 301 | ws.sendUrgentData(49152); 302 | verify(mockS).sendUrgentData(49152); 303 | } 304 | 305 | @Test 306 | public void testSetOobInlineDelegatesToSocket() throws Exception { 307 | ws.setOOBInline(true); 308 | verify(mockS).setOOBInline(true); 309 | } 310 | 311 | @Test 312 | public void testGetOobInlineDelegatesToSocket() throws Exception { 313 | when(mockS.getOOBInline()).thenReturn(true); 314 | assertTrue(ws.getOOBInline()); 315 | verify(mockS).getOOBInline(); 316 | } 317 | 318 | @Test 319 | public void testSetSoTimeoutDelegatesToSocket() throws Exception { 320 | ws.setSoTimeout(123456); 321 | verify(mockS).setSoTimeout(123456); 322 | } 323 | 324 | @Test 325 | public void testGetSoTimeoutDelegatesToSocket() throws Exception { 326 | when(mockS.getSoTimeout()).thenReturn(123456); 327 | assertEquals(123456, ws.getSoTimeout()); 328 | verify(mockS).getSoTimeout(); 329 | } 330 | 331 | @Test 332 | public void testSetSendBufferSizeDelegatesToSocket() throws Exception { 333 | ws.setSendBufferSize(654321); 334 | verify(mockS).setSendBufferSize(654321); 335 | } 336 | 337 | @Test 338 | public void testGetSendBufferSizeDelegatesToSocket() throws Exception { 339 | when(mockS.getSendBufferSize()).thenReturn(654321); 340 | assertEquals(654321, ws.getSendBufferSize()); 341 | verify(mockS).getSendBufferSize(); 342 | } 343 | 344 | @Test 345 | public void testSetReceiveBufferSizeDelegatesToSocket() throws Exception { 346 | ws.setReceiveBufferSize(654321); 347 | verify(mockS).setReceiveBufferSize(654321); 348 | } 349 | 350 | @Test 351 | public void testGetReceiveBufferSizeDelegatesToSocket() throws Exception { 352 | when(mockS.getReceiveBufferSize()).thenReturn(654321); 353 | assertEquals(654321, ws.getReceiveBufferSize()); 354 | verify(mockS).getReceiveBufferSize(); 355 | } 356 | 357 | @Test 358 | public void testSetKeepAliveDelegatesToSocket() throws Exception { 359 | ws.setKeepAlive(true); 360 | ws.setKeepAlive(false); 361 | verify(mockS).setKeepAlive(true); 362 | verify(mockS).setKeepAlive(false); 363 | } 364 | 365 | @Test 366 | public void testGetKeepAliveDelegatesToSocket() throws Exception { 367 | when(mockS.getKeepAlive()).thenReturn(false); 368 | assertFalse(ws.getKeepAlive()); 369 | verify(mockS).getKeepAlive(); 370 | } 371 | 372 | @Test 373 | public void testSetTrafficClassDelegatesToSocket() throws Exception { 374 | ws.setTrafficClass(5); 375 | verify(mockS).setTrafficClass(5); 376 | } 377 | 378 | @Test 379 | public void testGetTrafficClassDelegatesToSocket() throws Exception { 380 | when(mockS.getTrafficClass()).thenReturn(6); 381 | assertEquals(6, ws.getTrafficClass()); 382 | verify(mockS).getTrafficClass(); 383 | } 384 | 385 | @Test 386 | public void testSetReuseAddressDelegatesToSocket() throws Exception { 387 | ws.setReuseAddress(true); 388 | ws.setReuseAddress(false); 389 | verify(mockS).setReuseAddress(true); 390 | verify(mockS).setReuseAddress(false); 391 | } 392 | 393 | @Test 394 | public void testGetReuseAddressDelegatesToSocket() throws Exception { 395 | when(mockS.getReuseAddress()).thenReturn(false); 396 | assertFalse(ws.getReuseAddress()); 397 | verify(mockS).getReuseAddress(); 398 | } 399 | 400 | @Test 401 | public void testCloseDelegatesToSocket() throws Exception { 402 | ws.close(); 403 | verify(mockS).close(); 404 | } 405 | 406 | @Test 407 | public void testShutdownInputDelegatesToSocket() throws Exception { 408 | ws.shutdownInput(); 409 | verify(mockS).shutdownInput(); 410 | } 411 | 412 | @Test 413 | public void testShutdownOutputDelegatesToSocket() throws Exception { 414 | ws.shutdownOutput(); 415 | verify(mockS).shutdownOutput(); 416 | } 417 | 418 | @Test 419 | public void testToStringDelegatesToSocket() throws Exception { 420 | when(mockS.toString()).thenReturn("Exterminate!"); 421 | assertEquals("Exterminate!", ws.toString()); 422 | // toString() ... Mockito's Kryptonite 423 | // verify(mockS).toString(); 424 | } 425 | 426 | @Test 427 | public void testIsConnectedDelegatesToSocket() throws Exception { 428 | when(mockS.isConnected()).thenReturn(true); 429 | assertTrue(ws.isConnected()); 430 | verify(mockS).isConnected(); 431 | } 432 | 433 | @Test 434 | public void testIsBoundDelegatesToSocket() throws Exception { 435 | when(mockS.isBound()).thenReturn(true); 436 | assertTrue(ws.isBound()); 437 | verify(mockS).isBound(); 438 | } 439 | 440 | @Test 441 | public void testIsClosedDelegatesToSocket() throws Exception { 442 | when(mockS.isClosed()).thenReturn(true); 443 | assertTrue(ws.isClosed()); 444 | verify(mockS).isClosed(); 445 | } 446 | 447 | @Test 448 | public void testIsInputShutdownDelegatesToSocket() throws Exception { 449 | when(mockS.isInputShutdown()).thenReturn(true); 450 | assertTrue(ws.isInputShutdown()); 451 | verify(mockS).isInputShutdown(); 452 | } 453 | 454 | @Test 455 | public void testIsOutputShutdownDelegatesToSocket() throws Exception { 456 | when(mockS.isOutputShutdown()).thenReturn(true); 457 | assertTrue(ws.isOutputShutdown()); 458 | verify(mockS).isOutputShutdown(); 459 | } 460 | 461 | @Test 462 | public void testSetPerformancePreferencesDelegatesToSocket() throws Exception { 463 | ws.setPerformancePreferences(123, 456, 789); 464 | verify(mockS).setPerformancePreferences(123, 456, 789); 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /start-echo-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # start-echo-server 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU Affero General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU Affero General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Affero General Public License 15 | # along with this program. If not, see . 16 | #---------------------------------------------------------------------------- 17 | 18 | java -cp "target/classes:target/dependency/*" com.pmeade.websocket.example.EchoServer 19 | 20 | #---------------------------------------------------------------------------- 21 | # end of start-echo-server 22 | --------------------------------------------------------------------------------