diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 5d56b3594..000000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,41 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the firefly-5.0.0 branch - push: - branches: [ main ] - pull_request: - branches: [ main ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - strategy: - matrix: - java: [8, 11, 17, 21] - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Install dependencies Ubuntu - run: sudo apt-get update && sudo apt-get install openssl - - - name: Set up JDK - uses: actions/setup-java@v2 - with: - java-version: '${{ matrix.java }}' - distribution: 'temurin' - - - name: Build with Maven - run: mvn -B clean package diff --git a/.gitignore b/.gitignore index 3b4776dfa..5fe476b91 100644 --- a/.gitignore +++ b/.gitignore @@ -3,14 +3,5 @@ .settings *.class .DS_Store -*.iml -*.iws -*.ipr -.idea -out -target -firefly-common/logs/ -firefly/temp/ -logs/ -temp/ -/.java-version \ No newline at end of file +Gemfile.lock +_site/ diff --git a/.travis.yml b/.travis.yml index f907521b9..f3b65c348 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,25 @@ -arch: arm64 -os: linux -language: java -jdk: - - openjdk8 - - openjdk11 - - openjdk16 \ No newline at end of file +language: ruby +rvm: +- 2.3.3 + +before_install: + - bundle config disable_checksum_validation true + +before_script: + - chmod +x ./script/cibuild.sh # or do this locally and commit + +# Assume bundler is being used, therefore +# the `install` step will run `bundle install` by default. +script: ./script/cibuild.sh + +# branch whitelist, only for GitHub Pages +branches: + only: + - gh-pages # test the gh-pages branch + - /pages-(.*)/ # test every branch which starts with "pages-" + +env: + global: + - NOKOGIRI_USE_SYSTEM_LIBRARIES=true # speeds up installation of html-proofer + +sudo: false # route your build to the container-based infrastructure for a faster build diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..5e29322a3 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.fireflysource.com \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..57294a2e3 --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +source "https://rubygems.org" + +gem "jekyll" +gem "html-proofer" +gem 'github-pages', group: :jekyll_plugins + +gem "webrick", "~> 1.7" diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index d64569567..000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..d0016776d --- /dev/null +++ b/_config.yml @@ -0,0 +1,42 @@ +sass: + style: compressed + +plugins: + - jekyll-paginate + - jekyll-sitemap + +source: . +destination: ./_site +plugins_dir: ./_plugins +layouts_dir: ./_layouts +paginate: 20 +paginate_path: "blog/page:num/" + +defaults: + - + scope: + path: "_docs" + type: "docs" + values: + layout: "document" + - + scope: + path: "_posts" + type: "posts" + values: + layout: "post" + +collections: + posts: + output: true + docs: + output: true + +url: http://www.fireflysource.com + +exclude: + - .gitignore + +webrick: + headers: + Access-Control-Allow-Origin: "*" diff --git a/_data/global.yml b/_data/global.yml new file mode 100644 index 000000000..82213ffea --- /dev/null +++ b/_data/global.yml @@ -0,0 +1,5 @@ +downloadURL : https://github.com/hypercube1024/firefly +githubURL : https://github.com/hypercube1024/firefly +qqgroupURL : http://qgc.qq.com/4691508 +exampleURL: https://github.com/hypercube1024/firefly/tree/firefly-5.0.0/firefly-example/src/main/kotlin/com/fireflysource/example +releaseVersion : 5.0.2 \ No newline at end of file diff --git a/README.md b/_docs/getting-start.md similarity index 75% rename from README.md rename to _docs/getting-start.md index fe011397a..5c9f30f50 100644 --- a/README.md +++ b/_docs/getting-start.md @@ -1,16 +1,30 @@ +--- + +category : docs +title: Getting Started + +--- + +**Table of Contents** + +- [What is Firefly?](#what-is-firefly) +- [Event driven](#event-driven) +- [Quick start](#quick-start) +- [Contact information](#contact-information) + + # What is Firefly? -[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/hypercube1024/firefly/maven.yml)](https://github.com/hypercube1024/firefly) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/hypercube1024/firefly/CI)](https://github.com/hypercube1024/firefly) [![Maven Central](https://img.shields.io/maven-central/v/com.fireflysource/firefly-net)](https://search.maven.org/artifact/com.fireflysource/firefly-net/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) Firefly framework is an asynchronous Java web framework. It helps you create a web application ***Easy*** and ***Quickly***. -It provides asynchronous HTTP, Websocket, TCP Server/Client, and many other useful components for developing -web applications, protocol servers, etc. That means you can easy deploy your web without any other java web containers, -in short, it's containerless. Using Kotlin coroutines, Firefly is truly asynchronous and highly scalable. It taps into -the fullest potential of hardware. Use the power of non-blocking development without the callback nightmare. +It provides asynchronous HTTP, Websocket, TCP Server/Client, and many other useful components for developing web applications, protocol servers, etc. +That means you can easy deploy your web without any other java web containers, in short, it's containerless. +Using Kotlin coroutines, Firefly is truly asynchronous and highly scalable. +It taps into the fullest potential of hardware. Use the power of non-blocking development without the callback nightmare. Firefly core provides functionality for things like: - - HTTP server and client - WebSocket server and client - HTTP, Socks proxy @@ -18,6 +32,7 @@ Firefly core provides functionality for things like: - TCP server and client - UDP server and client + # Event driven The Firefly APIs are largely event-driven. It means that when things happen in Firefly that you are interested in, Firefly will call you by sending you events. @@ -45,6 +60,7 @@ Threads have overhead regarding the memory they require (e.g. for their stack) a For the levels of concurrency required in many modern applications, a blocking approach just doesn’t scale. + # Quick start Add maven dependency in your pom.xml. ```xml @@ -52,13 +68,13 @@ Add maven dependency in your pom.xml. com.fireflysource firefly-net - 5.0.2 + {{ site.data.global.releaseVersion }} com.fireflysource firefly-slf4j - 5.0.2 + {{ site.data.global.releaseVersion }} ``` @@ -151,7 +167,14 @@ private suspend fun writeLoop(data: String, connection: TcpConnection) = connect } ``` +More detailed information, please refer to the +* [HTTP server/client document]({{ site.url }}/docs/http-server-and-client.html) +* [WebSocket server and client]({{ site.url }}/docs/websocket-server-and-client.html) +* [TCP server/client document]({{ site.url }}/docs/tcp-server-and-client.html) +* [SSL/TLS configuration document]({{ site.url }}/docs/ssl-tls-configuration.html) +* [Log document]({{ site.url }}/docs/log.html) + + # Contact information -- E-mail: qptkk@163.com -- QQ Group: 126079579 -- Wechat: AlvinQiu +E-mail: qptkk@163.com +QQ Group: 126079579 diff --git a/_docs/http-server-and-client.md b/_docs/http-server-and-client.md new file mode 100644 index 000000000..124cf580f --- /dev/null +++ b/_docs/http-server-and-client.md @@ -0,0 +1,424 @@ +--- + +category : docs +title: HTTP server and client + +--- + + + +- [Basic concepts](#basic-concepts) + - [Class $](#class-) + - [Route order](#route-order) + - [Calling the next handler](#calling-the-next-handler) + - [Routing Context](#routing-context) +- [Capturing path parameters](#capturing-path-parameters) +- [Routing by wildcard](#routing-by-wildcard) +- [Routing by regular expressions](#routing-by-regular-expressions) +- [Routing by HTTP method](#routing-by-http-method) +- [Routing by content type](#routing-by-content-type) +- [Routing based on MIME types acceptable by the client](#routing-based-on-mime-types-acceptable-by-the-client) +- [Error handling](#error-handling) + - [Custom error handling](#custom-error-handling) +- [Serving static resources](#serving-static-resources) +- [Multipart file uploading](#multipart-file-uploading) +- [CORS handler](#cors-handler) + + + +# Basic concepts +The router is one of the core concepts of Firefly HTTP server. It maintains zero or more Routes. + +A router takes an HTTP request and finds the first matching route for that request, and passes the request to that route. + +The route can have a handler associated with it, which then receives the request. You then do something with the request, and then, either end it or pass it to the next matching handler. + +Here’s a simple router example: +```kotlin +fun main() { + `$`.httpServer() + .router().get("/").handler { ctx -> ctx.write("Hello world! ").next() } // (1) + .router().get("/").handler { ctx -> ctx.end("The router demo.") } // (2) + .listen("localhost", 8090) +} +``` +Two routers handle GET method and path '/'. When you visit the `http://localhost:8090`, the server response: +``` +Hello world! The router demo. +``` + +## Class $ +The class $ provides primary API of Firefly, such as +- The HTTP server and client +- The WebSocket server and client +- The TCP server and client +- The UDP server and client +- Other utilities + +It uses fluent style API to help you to build a complex application. Chaining calls like this allow you to write code that’s a little bit less verbose. + +## Route order +By default, routes are matched in the order which they are added to the router manager. Invokes the method `$.httpServer().router()` to create a router. + +When a request arrives, the router will step through each route and check if it matches then the handler for that route will be called. + +## Calling the next handler +If the handler subsequently calls `ctx.next()` method the handler for the next matching route (if any) will be called. And so on. + +In the first example, When the server receives the request, it invokes the first handler. +1. The first handler writes the "Hello world!" and calls `ctx.next()` method to invoke the next router. +2. The second handler writes the "The router demo." and ends the chain of responsibility. Then the server will flush the data to the client. + +## Routing Context +A new RoutingContext(ctx) instance is created for each HTTP request. + +You can visit the RoutingContext instance in the whole router chain. It provides HTTP request/response API and you can save data in the routing context. + +Here’s an example where one handler saves data in the routing context, and a subsequent handler reads it: +```kotlin +fun main() { + `$`.httpServer() + .router().get("/").handler { ctx -> + ctx.attributes["router1"] = "Some one visits the /. " // (1) + ctx.write("Hello world! ").next() + } + .router().get("/").handler { ctx -> + val data = ctx.attributes["router1"] // (2) + ctx.end("The router data: $data") + } + .listen("localhost", 8090) +} +``` +You visit the `http://localhost:8090` , the server response: +``` +Hello world! The router data: Some one visits the /. +``` +1. The first handler saves data using `ctx.attributes` method. +2. The second handler reads data and flushes it to the client. + + +# Capturing path parameters +It’s possible to match paths using placeholders for parameters. The placeholders consist of : followed by the parameter name. Parameter names consist of any alphabetic character, numeric character or underscore. + +```kotlin +fun main() { + `$`.httpServer() + .router().get("/product/:id").handler { ctx -> + when (val id = ctx.getPathParameter("id")) { + "1" -> ctx.end("Apple") + "2" -> ctx.end("Orange") + else -> ctx.setStatus(NOT_FOUND_404).end("The product $id not found.") + } + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().get("$url/product/1").submit() + .thenAccept { response -> println(response.stringBody) } // (1) + + `$`.httpClient().get("$url/product/2").submit() + .thenAccept { response -> println(response.stringBody) } // (2) + + `$`.httpClient().get("$url/product/3").submit() + .thenAccept { response -> println(response.stringBody) } // (3) +} +``` +In this case, we use the HTTP client to call the HTTP server. +1. The `$.httpClient().get` builds the GET request and then submits it to the server, the server uses `ctx.getPathParameter(name: String)` method to get product id in the path. The server finds the apple and response `Apple`. +2. The server gets the orange and respone `Orange` +3. The server can not find the product 3, so response `The product 3 not found.` + + +# Routing by wildcard +Often you want to route all requests that accord with a pattern. A simply way is to use wildcard `*`. For example: +```kotlin +fun main() { + `$`.httpServer() + .router().put("/product/*/*").handler { ctx -> + val type = ctx.getPathParameter(0) + val id = ctx.getPathParameter(1) + val product = ctx.stringBody + ctx.end("Put product success. id: $id, type: $type, product: $product") + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().put("$url/product/fruit/1").body("Apple").submit() + .thenAccept { response -> println(response.stringBody) } // (1) + + `$`.httpClient().put("$url/product/book/1").body("Tom and Jerry").submit() + .thenAccept { response -> println(response.stringBody) } // (2) + + `$`.httpClient().put("$url/product/book/2").body("The Three-Body Problem").submit() + .thenAccept { response -> println(response.stringBody) } // (3) +} +``` +The server uses `ctx.getPathParameter(index: Int)` method to get path parameter. The index starts from 0. +1. The `$.httpClient().put` builds the PUT request and then submits it to the server. The server response `Put product success. id: 1, type: fruit, product: Apple`. +2. The server response `Put product success. id: 1, type: book, product: Tom and Jerry`. +3. The server response `Put product success. id: 2, type: book, product: The Three-Body Problem`. + + +# Routing by regular expressions +Regular expressions can also be used to match URI paths in routes. For example: +```kotlin +fun main() { + `$`.httpServer() + .router().method(HttpMethod.PUT).pathRegex("/product/(.*)/(.*)").handler { ctx -> + val type = ctx.getPathParameterByRegexGroup(1) + val id = ctx.getPathParameterByRegexGroup(2) + val product = ctx.stringBody + ctx.end("Put product success. id: $id, type: $type, product: $product") + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().put("$url/product/fruit/1").body("Apple").submit() + .thenAccept { response -> println(response.stringBody) } + + `$`.httpClient().put("$url/product/book/1").body("Tom and Jerry").submit() + .thenAccept { response -> println(response.stringBody) } + + `$`.httpClient().put("$url/product/book/2").body("The Three-Body Problem").submit() + .thenAccept { response -> println(response.stringBody) } +} +``` + +We use the `getPathParameterByRegexGroup` method to get the path parameter. The regex group index starts from 1. + + +# Routing by HTTP method +By the default, a route will match all HTTP methods if you do not call `router.method`, `router.get`, `router.post`, `router.put` or `router.delete`. For example: +```kotlin +fun main() { + `$`.httpServer() + .router().get("/product/:id").handler { ctx -> + val id = ctx.getPathParameter("id") + ctx.end("Get the product $id") + } + .router().post("/product").handler { ctx -> + ctx.end("Create the product 1") + } + .router().put("/product/:id").handler { ctx -> + val id = ctx.getPathParameter("id") + ctx.end("Update the product $id") + } + .router().delete("/product/:id").handler { ctx -> + val id = ctx.getPathParameter("id") + ctx.end("Delete the product $id") + } + .listen("localhost", 8090) +} +``` +In the above example, we build the RESTful APIs. The URL `/product/:id` represents resources. The HTTP verbs (Such as, `GET`, `POST`, `PUT`, `DELETE` and so on) represent the operation of resources (Such as get, create, update and delete). + +If you want to let a lot of HTTP methods match a router, just use the `router.methods(list: List)` instead of `router.method(name: String)`. + +# Routing by content type +You can specify that a route will match against matching request MIME types using method `router.consumes`. + +In this case, the request will contain a content-type header specifying the MIME type of the request body. This will be matched against the value specified in consumes. + +Basically, The `router.consumes` method is describing which MIME types the handler can consume. For example: +```kotlin +@NoArg +data class Car(var name: String, var color: String) + +fun main() { + `$`.httpServer() + .router().put("/product/:id").consumes("*/json") + .handler { ctx -> + val id = ctx.getPathParameter("id") + val type = ctx.getPathParameter(0) + val car = json.read(ctx.stringBody) + + ctx.write("Update product. id: $id, type: $type. \r\n") + .end(car.toString()) + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient() + .put("$url/product/3") + .add(HttpField(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value)) + .body(json.write(Car("Benz", "Black"))) + .submit().thenAccept { response -> println(response.stringBody) } +} +``` +Run it. The console shows: +``` +Update product. id: 3, type: application. +Car(name=Benz, color=Black) +``` +In the above example, we use the wildcard `*` to match the content type of the HTTP request. We can also use the exact MIME type to match the request. + +# Routing based on MIME types acceptable by the client +The HTTP Accept header is used to signify which MIME types of the response are acceptable to the client. + +An accept header can have multiple MIME types separated by ‘,’. + +MIME types can also have a q value appended to them which signifies a weighting to apply if more than one response MIME type is available matching the HTTP Accept header. The q value is a number between 0 and 1.0. If omitted it defaults to 1.0. + +For example, the following accept header signifies the client will accept a MIME type of only text/plain: +``` +Accept: text/plain +``` +With the following, the client will accept text/plain or text/html with no preference. +``` +Accept: text/plain, text/html +``` +With the following the client will accept text/plain or text/html but prefers text/html as it has a higher q value (the default value is q=1.0) +``` +Accept: text/plain; q=0.9, text/html +``` +If the server can provide both text/plain and text/html it should provide the text/html in this case. + +By using `router.produces` you define which MIME type(s) the route produces, e.g. the following handler produces a response with MIME type application/json. + +```kotlin +fun main() { + `$`.httpServer() + .router().get("/product/:id").produces("text/plain") + .handler { ctx -> + ctx.end(Car("Benz", "Black").toString()) + } + .router().get("/product/:id").produces("application/json") + .handler { ctx -> + ctx.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) + .end(json.write(Car("Benz", "Black"))) + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().get("$url/product/3") + .put(HttpHeader.ACCEPT, "text/plain, application/json;q=0.9, */*;q=0.8") + .submit().thenAccept { response -> println("accept text; ${response.stringBody}") } + + `$`.httpClient().get("$url/product/3") + .put(HttpHeader.ACCEPT, "application/json, text/plain, */*;q=0.8") + .submit().thenAccept { response -> println("accept json; ${response.stringBody}") } +} +``` +Run it. The console shows: +``` +accept text; Car(name=Benz, color=Black) +accept json; {"name":"Benz","color":"Black"} +``` +In the above example, the first request, the `text/plain` weight(1.0) is higher than `application/json`(0.9), so this request matches the first router that responds the text format. + +The second request, the `application/json` weight equals the `text/plain`, but `application/json` is in front of `text/plain`, so the `application/json` priority is higher than `text/plain`. It matches the second router that responds the JSON format. + +# Error handling +When the handler throws exception, the server uses `DefaultContentProvider` response the error message. + +```kotlin +fun main() { + `$`.httpServer() + .router().post("/product").handler { + throw IllegalStateException("Create product exception") // (1) + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().post("$url/product/").submit() + .thenAccept { response -> println(response) } +} +``` +The client will receive the `Create product exception`. + +## Custom error handling +You can use `httpServer.onException` method to output custom error message. + +For example. +```kotlin +fun main() { + `$`.httpServer() + .onException { ctx, exception -> // (1) + ctx.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500) + .end("The server exception. ${exception.message}") + } + .router().post("/product").handler { + throw IllegalStateException("Create product exception") + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().post("$url/product/").submit() + .thenAccept { response -> println(response) } +} +``` +The client will receive the `The server exception. Create product exception`. + + +# Serving static resources +Firefly comes with an out of the box handler for serving static web resources so you can write static web servers very easily. + +To serve static resources such as .html, .css, .js or any other static resource, you use an instance of `FileHandler`. +```kotlin +fun main() { + `$`.httpServer() + .router().method(HttpMethod.GET) + .paths(listOf("/favicon.ico", "/poem.html", "/poem.txt")) // (1) + .handler(FileHandler.createFileHandlerByResourcePath("files")) // (2) + .listen("localhost", 8090) +} +``` +1. Use the `router.paths` method to bind some path to the file handler. +2. Use the factory method `FileHandler.createFileHandlerByResourcePath` to create a file handler instance. When you visit the `http://localhost:8090/poem.html`, the server will read the file `resources/files/poem.html` and flush the file to the client. + +# Multipart file uploading + +```kotlin +@NoArg +data class Product(var id: String, var brand: String, var description: String) + +fun main() { + `$`.httpServer() + .router().post("/product/file-upload").handler { ctx -> + val id = ctx.getPart("id") // (1) + val brand = ctx.getPart("brand") + val description = ctx.getPart("description") + ctx.end(Product(id.stringBody, brand.stringBody, description.stringBody).toString()) + } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().post("$url/product/file-upload") + .addPart("id", stringBody("x01"), null) // (2) + .addPart("brand", stringBody("Test"), null) + .addFilePart( + "description", "poem.txt", + resourceFileBody("files/poem.txt", StandardOpenOption.READ), // (3) + null + ) + .submit().thenAccept { response -> println(response) } +} +``` + +1. The server uses the `ctx.getPart` to get the content of multi-part format. +2. The client uses the `httpclient.addPart` to upload content. +3. The client uses the `httpclient.addFilePart` to upload file. The factory method `resourceFileBody` reads the resource file and encodes it to the multi-part format. + +# CORS handler +Cross Origin Resource Sharing is a safe mechanism for allowing resources to be requested from one domain and served from another. + +The example: +```kotlin +fun main() { + val corsConfig = CorsConfig("*.cors.test.com") + `$`.httpServer() + .router().path("*").handler(CorsHandler(corsConfig)) // (1) + .router().post("/cors-data-request/*") + .handler { it.end("success") } + .listen("localhost", 8090) + + val url = "http://localhost:8090" + `$`.httpClient().post("$url/cors-data-request/xxx") + .put(HttpHeader.ORIGIN, "hello.cors.test.com") // (2) + .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN_UTF_8.value) + .body("hello") + .submit().thenAccept { response -> println(response) } +} +``` +1. The server uses the `CorsHandler` to set some origin can visit the server resources. +2. The client set the origin header, if the server allows this origin, the client can visit the server resources. diff --git a/_docs/log.md b/_docs/log.md new file mode 100644 index 000000000..b7d9de156 --- /dev/null +++ b/_docs/log.md @@ -0,0 +1,223 @@ +--- + +category : docs +title: Log configuration + +--- + + + +- [Overview](#overview) +- [Configuration](#configuration) +- [Log formatter](#log-formatter) +- [Log filter](#log-filter) +- [Lazy logger](#lazy-logger) + + + +# Overview +Firefly log implements slf4j APIs. The features: +* Asynchronous writing +* Timeout or max buffer size flush disk strategy +* Lazy logger +* MDC & custom formatter + +# Configuration +Add maven dependency +```xml + + com.fireflysource + firefly-slf4j + {{ site.data.global.releaseVersion }} + +``` + +Add `firefly-log.xml` to classpath +```xml + + + + firefly-system + INFO + ${log.path} + com.firefly.example.common.ExampleLogFormatter + com.firefly.example.common.ErrorLogFilter + + + + firefly-monitor + INFO + ${log.path} + com.firefly.example.common.ExampleLogFormatter + + + + com.firefly.example.reactive + INFO + ${log.path} + com.firefly.example.common.ExampleLogFormatter + com.firefly.example.common.ErrorLogFilter + + + + com.firefly.example.kotlin + INFO + ${log.path} + com.firefly.example.common.ExampleLogFormatter + com.firefly.example.common.ErrorLogFilter + + + + firefly-example-error + ERROR + ${log.path} + com.firefly.example.common.ExampleLogFormatter + + + + test.max.size + INFO + ${log.path} + 300 + UTF-8 + + + + time-split-minute + INFO + ${log.path} + minute + + +``` +The `firefly-system` is the default logger. It records firefly framework and any other not specified log. The `firefly-monitor` records the firefly framework runtime performance metric. + +The `` node sets the logger name, it uses prefix to match logger instance. For example: +```java +private static final Logger logger = LoggerFactory.getLogger(Foo.class); //com.firefly.example.reactive.Foo + +public void print() { + logger.info("print foo") +} +``` + +The settings description: + +``: +The Foo class name is com.firefly.example.reactive.Foo, it matches the logger instance com.firefly.example.reactive. The logger will print records to ${log.path}/com.firefly.example.reactive.2017-10-04.txt. + + +``: +The log output level value contains TRACE, DEBUG, INFO, WARN, and the ERROR. + + +``: +The log file output path. + + +``: +You can use it to set the max log file size, and the unit is the byte. When the log file size exceeds the max size, Firefly logger creates a new file to save log records automatically. + + +``: +The log entry formatter class, it implements the LogFormatter interface. You can use it to format the log entry content. + + +``: +The log file name formatter class, it implements the LogNameFormatter interface. You can use it to format the log file name. + + +``: +The log filter class, it implements the LogFilter interface. It intercepts the specified log output. + + +``: +The logger splits the log files daily. You can also use this parameter to set the log file max split time, and the value is minute, hour or day. + + +``: +If this parameter is true, the logger prints record to console. + + +# Log formatter +The `` specifies a class that implements `LogFormatter`. For example: +```java +public class ExampleLogFormatter implements LogFormatter { + + @Override + public String format(LogItem logItem) { + String logStr = logItem.getLevel() + " " + SafeSimpleDateFormat.defaultDateFormat.format(logItem.getDate()); + + if (!CollectionUtils.isEmpty(logItem.getMdcData())) { + logStr += " " + logItem.getMdcData(); + } + + if (StringUtils.hasText(logItem.getClassName())) { + String[] arr = $.string.split(logItem.getClassName(), '.'); + logStr += " " + arr[arr.length - 1]; + } + + if (StringUtils.hasText(logItem.getThreadName())) { + logStr += " " + logItem.getThreadName(); + } + + if (logItem.getStackTraceElement() != null) { + logStr += " " + logItem.getStackTraceElement(); + } + + logStr += " -> " + logItem.renderContentTemplate(); + return logStr; + } +} +``` +When the logger writes log records to a file or console, it will call the `LogFormatter.format` method. You can convert LogItem to a String using custom format. + +# Log filter +Sometimes we want collect error log and print it in a single log file. The firefly provides the `` that specifies a class that implements `LogFilter`. For example: +```java +public class ErrorLogFilter implements LogFilter { + + private static Logger logger = LoggerFactory.getLogger("firefly-example-error"); + + @Override + public void filter(LogItem logItem) { + if (logItem.getLevel().equals("ERROR")) { + logger.error(logItem.getContent(), logItem.getThrowable()); + } + } +} +``` +In this case, we collect and print error information via `ErrorLogFilter`. + +# Lazy logger +When we want to print a large data object that helps us to debug programming in development stage, we need use the `logger.isDebugEnabled()` condition to avoid the resources are consumed excessively in the production environment. Just like: +```java +if (logger.isDebugEnabled()) { + logger.getLogger().debug("debug dump data: ", dumpLargeData()); +} +``` + +We set logger level is INFO in the production environment. The `dumpLargeData()` method will be not executed. But Firefly logger provides a simple API to do this. Just like: +```java +public class Slf4jImplDemo { + + private static final LazyLogger logger = LazyLogger.create(); + + public static void main(String[] args) { + // logger level is INFO + logger.debug(() -> "debug dump data: " + dumpLargeData()); + if (logger.isDebugEnabled()) { + logger.getLogger().debug("debug dump data: ", dumpLargeData()); + } + } + + private static String dumpLargeData() { + System.out.println("dump......"); + ThreadUtils.sleep(2000); + return "large data"; + } +} +``` +The lambda will be executed lazily. In this case, when the logger level is DEBUG, the lambda will be executed. diff --git a/_docs/ssl-tls-configuration.md b/_docs/ssl-tls-configuration.md new file mode 100644 index 000000000..24305e8a1 --- /dev/null +++ b/_docs/ssl-tls-configuration.md @@ -0,0 +1,54 @@ +--- + +category : docs +title: SSL/TLS configuration + +--- + + + +- [Enable HTTPs](#enable-https) +- [Set secure session factory](#set-secure-session-factory) + + + +# Enable HTTPs +```kotlin +fun main() { + `$`.httpServer() + .router().get("/").handler { ctx -> ctx.end("Hello https! ") } + .enableSecureConnection() // (1) + .listen("localhost", 8090) + + `$`.httpClient().get("https://localhost:8090/").submit() // (2) + .thenAccept { response -> println(response.stringBody) } +} +``` +1. Use `httpServer.enableSecureConnection` method to enable default https certificate. +2. Once the https enabled, the http2 protocol has the high priority in the ALPN handshake. + +# Set secure session factory +The https server uses Firefly insecure self-signed certificate. It is just can be used in test or development stage. If you want to use your certificate file instead of the self-signed certificate, you can use the `FileConscryptSSLContextFactory` that can read your jks(KeyStore) file. + +The first, copy your jks file (such as `fireflySecureKeys.jks`) to your classpath. + +Notes: the maven resource filter will modify your jks file. It can cause the jks verifying failure. If you use maven and copy the jks file to the `src/main/resources` folder, you must exclude the jks file in maven resource filter. For example: +```xml + + + src/main/resources + true + + **/*.xml + + + + src/main/resources + false + + **/*.xml + + + +``` +We only add xml files to the maven resource filter. diff --git a/_docs/tcp-server-and-client.md b/_docs/tcp-server-and-client.md new file mode 100644 index 000000000..087efc66b --- /dev/null +++ b/_docs/tcp-server-and-client.md @@ -0,0 +1,50 @@ +--- + +category : docs +title: TCP server and client + +--- +**Table of Contents** + + +- [TCP server and client](#tcp-server-and-client) + + + +# TCP server and client +```kotlin +fun main() { + `$`.tcpServer().onAcceptAsync { connection -> // (1) + launch { writeLoop("Server", connection) } + launch { readLoop(connection) } + }.listen("localhost", 8090) + + `$`.tcpClient().connectAsync("localhost", 8090) { connection -> // (2) + launch { writeLoop("Client", connection) } + launch { readLoop(connection) } + } +} + +private suspend fun readLoop(connection: TcpConnection) = connection.useAwait { // (3) + while (true) { + try { + val buffer = connection.read().await() + println(BufferUtils.toString(buffer)) + } catch (e: Exception) { + println("Connection closed.") + break + } + } +} + +private suspend fun writeLoop(data: String, connection: TcpConnection) = connection.useAwait { + (1..10).forEach { // (4) + connection.write(toBuffer("TCP ${data}. count: $it, time: ${Date()}")) + delay(1000) + } +} +``` +1. Use the `onAcceptAsync` method to accept tcp connection. +2. Use the `connectAsync` method to establish a tcp connection to the server. +3. Lauch a coroutine and read data in a loop. The function `connection.useAwait` closes connection automatically when the `readLoop` exits. +4. Write data in a loop. diff --git a/_docs/websocket-server-and-client.md b/_docs/websocket-server-and-client.md new file mode 100644 index 000000000..511d6797e --- /dev/null +++ b/_docs/websocket-server-and-client.md @@ -0,0 +1,60 @@ +--- + +category : docs +title: WebSocket server and client + +--- + + + +- [Basic concepts](#basic-concepts) + + + +# Basic concepts +Unlike HTTP, WebSocket provides full-duplex communication. Additionally, WebSocket enables streams of messages on top of TCP. TCP alone deals with streams of bytes with no inherent concept of a message. Before WebSocket, port 80 full-duplex communication was attainable using Comet channels; however, Comet implementation is non-trivial, and due to the TCP handshake and HTTP header overhead, it is inefficient for small messages. WebSocket protocol aims to solve these problems without compromising security assumptions of the web. + +The WebSocket example: +```kotlin +fun main() { + `$`.httpServer().websocket("/websocket/hello") + .onServerMessageAsync { frame, _ -> onMessage(frame) } + .onAcceptAsync { connection -> sendMessage("Server", connection) } + .listen("localhost", 8090) + + val url = "ws://localhost:8090" // (1) + `$`.httpClient().websocket("$url/websocket/hello") + .extensions(listOf("permessage-deflate")) // (2) + .onClientMessageAsync { frame, _ -> onMessage(frame) } + .connectAsync { connection -> sendMessage("Client", connection) } +} + +private suspend fun sendMessage(data: String, connection: WebSocketConnection) = connection.useAwait { + (1..10).forEach { // (3) + connection.sendText("WebSocket ${data}. count: $it, time: ${Date()}") + delay(1000) + } +} + +private fun onMessage(frame: Frame) { + if (frame is TextFrame) { + println(frame.payloadAsUTF8) + } +} +``` + +Run it. And the console shows: +``` +Server. message: 1, time: Mon Feb 15 15:35:24 CST 2021 +Client. message: 1, time: Mon Feb 15 15:35:24 CST 2021 +Server. message: 2, time: Mon Feb 15 15:35:25 CST 2021 +Client. message: 2, time: Mon Feb 15 15:35:25 CST 2021 +Server. message: 3, time: Mon Feb 15 15:35:26 CST 2021 +Client. message: 3, time: Mon Feb 15 15:35:26 CST 2021 + +...... +``` + +1. The WebSocket protocol specification defines `ws` and `wss` as two new uniform resource identifier (URI) schemes that are used for unencrypted and encrypted connections, respectively. Apart from the scheme name and fragment (# is not supported), the rest of the URI components are defined to use URI generic syntax. +2. The Firefly WebSocket server and client supports some extension protocol, such as `permessage-deflate`, `deflate-frame`, `x-webkit-deflate-frame`, `fragment`, and `identity`. +3. The function `connection.useAwait` closes connection automatically when the `readLoop` exits. \ No newline at end of file diff --git a/_includes/doc-menu.html b/_includes/doc-menu.html new file mode 100644 index 000000000..6e537aaef --- /dev/null +++ b/_includes/doc-menu.html @@ -0,0 +1,8 @@ +
  • Getting start
  • +
  • HTTP server and client
  • +
  • WebSocket server and client
  • +
  • TCP server and client
  • +
  • SSL/TLS configuration
  • +
  • Log
  • +
  • +
  • Example
  • diff --git a/_includes/foot.html b/_includes/foot.html new file mode 100644 index 000000000..68208396c --- /dev/null +++ b/_includes/foot.html @@ -0,0 +1,14 @@ +
    +
    + +
    + + + + + + + diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 000000000..2cabefcc6 --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,39 @@ + +{{ page.title }} + + + + + + + + + + + + + + + + + + diff --git a/_includes/navbar.html b/_includes/navbar.html new file mode 100644 index 000000000..1451146b5 --- /dev/null +++ b/_includes/navbar.html @@ -0,0 +1,31 @@ + diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 000000000..a12f67fb6 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,14 @@ + + +{% include head.html %} + + +{% include navbar.html %} + +
    +{{ content }} +
    + +{% include foot.html %} + + \ No newline at end of file diff --git a/_layouts/document.html b/_layouts/document.html new file mode 100644 index 000000000..08772adc3 --- /dev/null +++ b/_layouts/document.html @@ -0,0 +1,27 @@ + + +{% include head.html %} + + +{% include navbar.html %} + +
    +
    +
    +
    {{ content }}
    +
    + +
    +
    +
    +
    + +{% include foot.html %} + + diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 000000000..a5cb00de4 --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,21 @@ + + +{% include head.html %} + + +{% include navbar.html %} + +
    +
    +
    +
    +

    {{ page.title }}

    + {{ content }} +
    +
    +
    +
    + +{% include foot.html %} + + diff --git a/_posts/2014-11-15-firefly-3.0.3-release.md b/_posts/2014-11-15-firefly-3.0.3-release.md new file mode 100644 index 000000000..0d8322f45 --- /dev/null +++ b/_posts/2014-11-15-firefly-3.0.3-release.md @@ -0,0 +1,59 @@ +--- + +category : release +title: Firefly v3.0.3 is released +date: "2014-11-15 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v3.0.3 fixed some bugs and optimized the framework's performance. The last update was one year ago. During this time, I have been improving various aspects of Firefly framework. Now, I have deployed firefly to Apache Central Repository, so you can download it easy. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +The firefly v3.0.3 fixed some bugs and optimized the framework's performance. The last update was one year ago. During this time, I have been improving various aspects of Firefly framework and complete a more detailed guide document. Now, I have deployed firefly to Apache Central Repository, so you can download it easy. The pom is: + +{% highlight xml %} + + com.fireflysource + firefly-common + 3.0.3 + + + com.fireflysource + firefly-template + 3.0.3 + + + com.fireflysource + firefly-nettool + 3.0.3 + + + com.fireflysource + firefly + 3.0.3 + +{% endhighlight %} + + +Update log: + +1. Add a new collection class, consistent hash. +2. Use ConcurrentLinkedHashMap instead of ConcurrentLRUHashMap. +3. Fix the log tools make directory problem. +4. Fix the problem that some special characters don't escape in json tool +5. The template adds string filter function. +6. Optimize nettool's thread model and code structure. +7. Optimize template expression language. +8. The object navigation expression adds the map's key that is integer type. +9. The template language adds two default function that are "remove" and "modOut" +10. Fix the array index out bound occurs in json tool outputs string. +11. Fix client can visit the any directories of host through http server. +12. Fix the CutStringFunction isn't boundary check lead to StringIndexOutOfBoundsException. +13. Add a business logic thread queued mechanism. +14. Enable thread pool parameter instead of pipeline parameter in HTTP server. +15. Add the property reflect wrapped method with cache. +16. Fix the risk of SimpleTcpClient connection leaking. +17. Add compiler utils and maven module configuration. +18. Add constructor injection using annotation or XML configuration. +19. Improve performance of the date roll file log. +20. Add copy method of ReflectUtils and improves performance. diff --git a/_posts/2014-12-15-firefly-3.0.4-release.md b/_posts/2014-12-15-firefly-3.0.4-release.md new file mode 100644 index 000000000..dfe9909e2 --- /dev/null +++ b/_posts/2014-12-15-firefly-3.0.4-release.md @@ -0,0 +1,77 @@ +--- + +category : release +title: Firefly v3.0.4 is released +date: "2014-12-15 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v3.0.4 optimized the asynchronous log performance and added some new JSON APIs, AIO network frameworks, and ALPN (Application Layer Protocol Negotiation) support. Now, firefly changed the JDK dependency to JDK7. Firefly v3.0.4 takes advantage of JDK7 provides brand new asynchronous I/O and j.u.c APIs to enhance the performance. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v3.0.4 optimized the asynchronous log performance and added some new JSON APIs, AIO network frameworks, and ALPN (Application Layer Protocol Negotiation) support. Now, firefly changed the JDK dependency to JDK7. Firefly v3.0.4 takes advantage of JDK7 provides brand new asynchronous I/O and j.u.c APIs to enhance the performance. + +Firefly v3.0.4 provides the unified abstract layer for NIO and AIO. You don't need modify your program when you change NIO to AIO. Below picture is the architecture diagram of firefly network framework: + +![network framework](/images/network-framework.png "network framework") + +In addition, the network framework adds ALPN (Application Layer Protocol Negotiation) support of SSL. The examples of server and client which enable SSL with ALPN are in test.net.ssl.SSLClientDemo and test.net.ssl.SSLServerDemo. Before you run these examples, you must enable ALPN support, start the JVM as follows: + +{% highlight text %} +java -Xbootclasspath/p: ... +{% endhighlight %} + +where path_to_alpn_boot_jar is the path on the file system for the ALPN Boot Jar file, for example, one at the Maven coordinates org.mortbay.jetty.alpn:alpn-boot. + +The ALPN implementation, relying on modifications of OpenJDK classes, updates every time there are updates to the modified OpenJDK classes. + + + + + + + + + + + + + + + + + + + + + + +
    OpenJDK versionALPN version
    1.7.0u407.1.0.v20141016
    1.7.0u457.1.0.v20141016
    1.7.0u517.1.0.v20141016
    1.7.0u557.1.0.v20141016
    1.7.0u607.1.0.v20141016
    1.7.0u657.1.0.v20141016
    1.7.0u677.1.0.v20141016
    1.7.0u717.1.2.v20141202
    1.7.0u727.1.2.v20141202
    1.8.08.1.0.v20141016
    1.8.0u058.1.0.v20141016
    1.8.0u118.1.0.v20141016
    1.8.0u208.1.0.v20141016
    1.8.0u208.1.0.v20141016
    1.8.0u258.1.2.v20141202
    + +More ALPN details, please see the the eclipse ALPN document. + +Another important update of Firefly v3.0.4 is some new JSON parsing APIs that can parse a JSON string and return a `JsonObject` or `JsonArray` instance without object binding. Such as: + +{% highlight java %} + +String json = "[333,444,{\"key\" : \"hello\", \"keyObject\" : [\"object0\",\"object1\" ]},666]"; +JsonArray array = Json.toJsonArray(json); + +Assert.assertThat(array.getJsonObject(2).getJsonArray("keyObject").getString(0), is("object0")); +Assert.assertThat(array.getJsonObject(2).getJsonArray("keyObject").getString(1), is("object1")); + +json = "{\"key1\":333, \"arrayKey\":[444, \"array\"], \"key2\" : {\"key3\" : \"hello\", \"key4\":\"world\" }, \"booleanKey\" : true } "; +JsonObject jsonObject = Json.toJsonObject(json); +Assert.assertThat(jsonObject.getJsonArray("arrayKey").getString(1), is("array")); + +{% endhighlight %} + +These methods are more convenient than the `toObject(String json, Class clazz)` method, although the object binding API has the higher performance. + +Update log: + +1. Improve the asynchronous log performance. +2. Add AIO network framework. +3. Add ALPN support of SSL. +4. Add some new JSON APIs. +5. use the new j.u.c APIs of JDK7 to enhance the performance of Firefly. diff --git a/_posts/2017-02-26-firefly-4.0.21-release.md b/_posts/2017-02-26-firefly-4.0.21-release.md new file mode 100644 index 000000000..a3d01973e --- /dev/null +++ b/_posts/2017-02-26-firefly-4.0.21-release.md @@ -0,0 +1,45 @@ +--- + +category : release +title: Firefly v4.0.21 is released +date: "2017-02-26 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly adds a brand new functional style fluent API to help you to build a complex network application and clean up a log of deprecated codes. Firefly 4.0.21 is now JDK8 only. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly adds a brand new functional style fluent API to help you to build a complex network application and clean up a log of deprecated codes. Firefly 4.0.21 is now JDK8 only. + +Here is an example to show how to build HTTP server and client using fluent API. +```java +public class HelloHTTPServerAndClient { + public static void main(String[] args) { + Phaser phaser = new Phaser(2); + + HTTP2ServerBuilder httpServer = $.httpServer(); + httpServer.router().get("/").handler(ctx -> ctx.write("hello world! ").next()) + .router().get("/").handler(ctx -> ctx.end("end message")) + .listen("localhost", 8080); + + $.httpClient().get("http://localhost:8080/").submit() + .thenAccept(res -> System.out.println(res.getStringBody())) + .thenAccept(res -> phaser.arrive()); + + phaser.arriveAndAwaitAdvance(); + httpServer.stop(); + $.httpClient().stop(); + } +} +``` + +More examples, please refer to the [full document]({{ site.url }}/docs/http-server-and-client.html). + +Firefly 4.0.21 adds many new features, like this: +- HTTP tunnel. +- Complete redesign of functional style fluent API for HTTP server and client, TCP server and client. +- A powerful HTTP router. +- Using Boring SSL engine instead of JDK SSL engine. +- Mustache template adapter. +- Add the max log file size limit. +- Fix some bugs. diff --git a/_posts/2017-04-02-firefly-4.0.22-release.md b/_posts/2017-04-02-firefly-4.0.22-release.md new file mode 100644 index 000000000..2b369de03 --- /dev/null +++ b/_posts/2017-04-02-firefly-4.0.22-release.md @@ -0,0 +1,17 @@ +--- + +category : release +title: Firefly v4.0.22 is released +date: "2017-04-02 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.0.22 adds Metrics monitor for HTTP server/client, TCP server/client, JDBCHelper. The default reporter is Slf4jReporter, and the log name is firefly-monitor. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.0.22 adds [Metrics](http://metrics.dropwizard.io/) monitor for HTTP server/client, TCP server/client, JDBCHelper. The default reporter is Slf4jReporter, and the log name is firefly-monitor. You can also set the custom reporter in configuration. + +Update log: + +1. Add Metrics monitor. +2. Fix the "not an SSL/TLS record" exception of HTTP client. diff --git a/_posts/2017-10-28-firefly-4.5.0-release.md b/_posts/2017-10-28-firefly-4.5.0-release.md new file mode 100644 index 000000000..5aa98a7ba --- /dev/null +++ b/_posts/2017-10-28-firefly-4.5.0-release.md @@ -0,0 +1,132 @@ +--- + +category : release +title: Firefly v4.5.0 is released +date: "2017-10-28 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.5.0 adds brand new Kotlin DSL API, asynchronous SQL client, and reactive stream adapter that helps us easier to building the asynchronous application. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.5.0 adds brand new Kotlin DSL API, asynchronous SQL client, and reactive stream adapter that helps us easier to building the asynchronous application. + +## Kotlin DSL +Kotlin DSL APIs allow for constructing the application in a semi-declarative way. + +For example: +```kotlin +private val log = Log.getLogger { } + +fun main(args: Array) { + val server = HttpServer(requestLocal) { + router { + httpMethod = HttpMethod.GET + path = "/" + + asyncHandler { + end("hello world!") + } + } + + router { + httpMethods = listOf(GET, POST) + path = "/product/:type/:id" + + asyncHandler { + statusLine { + status = OK.code + reason = OK.message + } + + header { + "My-Header" to "Ohh nice" + SERVER to "Firefly kotlin DSL server" + +HttpField("Add-My-Header", "test add") + } + + trailer { + "You-are-trailer" to "Crane ....." + } + + val type = getRouterParameter("type") + val id = getRouterParameter("id") + log.info { "req type: $type, id: $id" } + + writeJson(Response("ok", 200, Product(id, type))).end() + } + } + } + server.listen("localhost", 8080) +} +``` +We set HTTP method, path, response status line, headers, trailers using Kotlin DSL. It expresses HTTP protocol structure is clear. And we can add our business logic in the handler. + +The HTTP server (Kotlin version) executes handler in coroutines. That means we can replace asynchronous callback style codes to the block style easily. The coroutine block codes make the programs are shorter and far simpler to understand and enjoy the scalability and performance benefits of asynchronous codes. + +For example: +```kotlin +router { + httpMethod = HttpMethod.GET + path = "/project/:id" + + asyncHandler { + val response = projectService.getProject(Request("test", getPathParameter("id").toLong())) + writeJson(response).end() + } +} +``` + +All the DB requests are asynchronous. The DB client (Kotlin version) uses the coroutine to replacing callback-ridden codes. It keeps the scalability and performance without complex, hard-to-maintain codes. + +The Java has not native coroutine. We consider to adding the [Quasar](http://www.paralleluniverse.co/quasar/) (the Fiber library for JVM) adapter in future. + +## Reactor adapter +In this version, we add the [Reactor](http://projectreactor.io/) adapter for asynchronous API. The Reactor helps us to compose asynchronous codes fluently. + +For example: +```java +@Override +public Mono buy(ProductBuyRequest request) { + if (request == null) { + return Mono.error(new IllegalArgumentException("The product request is required")); + } + + if (request.getUserId() == null || request.getUserId().equals(0L)) { + return Mono.error(new IllegalArgumentException("The user id is required")); + } + + if (CollectionUtils.isEmpty(request.getProducts())) { + return Mono.error(new IllegalArgumentException("The products must bu not empty")); + } + + return db.newTransaction(c -> buy(request, c)); +} + +private Mono buy(ProductBuyRequest request, ReactiveSQLConnection c) { + return inventoryDAO.updateBatch(request.getProducts(), InventoryOperator.SUB, c) + .doOnSuccess(this::verifyInventory) + .flatMap(arr -> productDAO.list(toProductIdList(request), c)) + .map(products -> toOrders(request, products)) + .flatMap(orders -> orderDAO.insertBatch(orders, c)) + .map(orderIdList -> true); +} +``` + +More examples, please refer to the: +* [Example (Java version)]({{ site.data.global.githubURL }}/tree/master/firefly-example/src/main/java/com/firefly/example) +* [Example (Kotlin version)]({{ site.data.global.githubURL }}/tree/master/firefly-example/src/main/kotlin/com/firefly/example/kotlin) + +Update log: + +1. Add Kotlin DSL API. +2. Add asynchronous SQL client. +3. Add reactive stream adapter. +4. Add Kotlin coroutine dispatcher for HTTP server. +5. Add the asynchronous handler for HTTP server. +6. Add gzip content decoding for HTTP client. +7. Add the default path is "/" when the HTTP client path is empty. +8. Add generic type bind for JSON parser. +9. Add sl4fj MDC implementation. +10. Add log message formatter. +11. Add AffinityThreadPool. diff --git a/_posts/2017-11-20-firefly-4.6.0-release.md b/_posts/2017-11-20-firefly-4.6.0-release.md new file mode 100644 index 000000000..bf762bbde --- /dev/null +++ b/_posts/2017-11-20-firefly-4.6.0-release.md @@ -0,0 +1,116 @@ +--- + +category : release +title: Firefly v4.6.0 is released +date: "2017-11-20 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.6.0 adds OpenSSL engine support for network tools, named parameter SQL APIs, connection leak detector for HTTP client and fixes some bugs. We have completed the Kotlin API documents and SSL/TLS configuration documents. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.6.0 adds OpenSSL engine support for network tools, named parameter SQL APIs, connection leak detector for HTTP client and fixes some bugs. We have completed the [HTTP server/client document (Kotlin version)]({{ site.url }}/docs/http-server-and-client-kotlin-ext.html), [Database access document (Kotlin version)]({{ site.url }}/docs/database-access-kotlin.html) and [SSL/TLS configuration document]({{ site.url }}/docs/ssl-tls-configuration.html). + +## Use the OpenSSL engine +Firefly provides JDK SSL engine and OpenSSL engine. The JDK SSL engine is the default. +The Java8 SSL engine does not support ALPN (Application Layer Protocol Negotiation). + +The HTTP/2 need ALPN within a TLS handshake. If you use the Java8 SSL engine, you need to add a VM option to set up Jetty ALPN boot jar. + +If you can not modify the VM options in some situations, you can use the OpenSSL engine that supports ALPN, and it need not add any VM options. + +```java +public class OpensslHTTPsServer { + public static void main(String[] args) { + $.httpsServer(new DefaultOpenSSLSecureSessionFactory()) + .router().get("/").handler(ctx -> ctx.end("hello world!")) + .listen("localhost", 8081); + } +} +``` +Run it and visit the `https://localhost:8081`. The browser will render +``` +hello world! +``` + +You can also use yourself openssl certificate file. For example: +```java +public class OpensslFileCertHTTPsServer { + public static void main(String[] args) throws IOException { + ClassPathResource certificate = new ClassPathResource("/myCA.cer"); + ClassPathResource privateKey = new ClassPathResource("/myCAPriv8.key"); + SecureSessionFactory factory = new FileCertificateOpenSSLSecureSessionFactory( + certificate.getFile().getAbsolutePath(), + privateKey.getFile().getAbsolutePath()); + + $.httpsServer(factory) + .router().get("/").handler(ctx -> ctx.end("hello world!")) + .listen("localhost", 8081); + } +} +``` + +Notes: the OpenSSL private key must be `PKCS8` format. You can use the tool `openssl pkcs8` to convert the format. Such as: +``` +openssl genrsa -out myCA.key 2048 +openssl req -new -x509 -key myCA.key -out myCA.cer -days 36500 +openssl pkcs8 -topk8 -inform PEM -outform PEM -in myCA.key -out myCAPriv8.key -nocrypt +``` + +More details, please refer to the [SSL/TLS configuration]({{ site.url }}/docs/ssl-tls-configuration.html) + +## Named parameter SQL +The named SQL improves readability. When we use the named SQL, we can use javabean or map to replace the placeholders. The javabean uses the property name to match the parameter. The map uses the key to match the parameter. For example: +```java +@Test +public void testInsert() { + String sql = "insert into `test`.`user`(pt_name, pt_password) values(?,?)"; + Mono newUserId = exec(c -> c.insert(sql, "hello user", "hello user pwd")); + StepVerifier.create(newUserId).expectNext(size + 1L).verifyComplete(); + + String namedSql = "insert into `test`.`user`(pt_name, pt_password) values(:name, :password)"; + Map paramMap = new HashMap<>(); + paramMap.put("name", "hello user1"); + paramMap.put("password", "hello user pwd1"); + newUserId = exec(c -> c.namedInsert(namedSql, paramMap)); + StepVerifier.create(newUserId).expectNext(size + 2L).verifyComplete(); + + User user = new User(); + user.setName("hello user2"); + user.setPassword("hello user pwd2"); + newUserId = exec(c -> c.namedInsert(namedSql, user)); + StepVerifier.create(newUserId).expectNext(size + 3L).verifyComplete(); +} +``` + +The Kotlin version example: +```kotlin +@Test +fun testInsert() = runBlocking { + val newUserId = exec { + it.asyncInsert("insert into `test`.`user`(pt_name, pt_password) values(?,?)", + "hello user", "hello user pwd") + } + assertEquals(size + 1L, newUserId) + + val namedSQL = "insert into `test`.`user`(pt_name, pt_password) values(:name, :password)" + val newUserId2 = exec { + it.asyncNamedInsert(namedSQL, mapOf("name" to "hello user", "password" to "hello user pwd")) + } + assertEquals(size + 2L, newUserId2) + + val newUserId3 = exec { + it.asyncNamedInsert(namedSQL, User(null, "hello user", "hello user pwd", null)) + } + assertEquals(size + 3L, newUserId3) +} +``` + +Update log: +1. Add OpenSSL engine support for network tools. +2. Add named parameter SQL APIs. +3. Complete Kotlin API documents. +4. Add SSL/TLS configuration documents. +5. Fix the priority mistake when the router is matched by the accept MIME type. +6. Add default bad message listener for the HTTP server. +7. Add connection leak detector for the HTTP client. diff --git a/_posts/2017-12-04-firefly-4.6.1-release.md b/_posts/2017-12-04-firefly-4.6.1-release.md new file mode 100644 index 000000000..93ff5d884 --- /dev/null +++ b/_posts/2017-12-04-firefly-4.6.1-release.md @@ -0,0 +1,34 @@ +--- + +category : release +title: Firefly v4.6.1 is released +date: "2017-12-04 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.6.1 improves performance of network tools, set the OpenSSL is default SSL engine provider and fixes some bugs. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.6.1 improves the performance of network tools. When you send data on a slow network, the data will save in a buffer queue, and then the Firefly will merge the data that in the queue automatically. It reduces registered write event number and the cost of the thread context switching. + +In this version, we set the OpenSSL is default SSL engine provider. And we add a new SSL engine provider [Google Conscrypt](https://github.com/google/conscrypt) as an experimental function. Such as: +```java +public class HTTPsServerDemo { + public static void main(String[] args) throws Exception { + Path path = Paths.get(HTTPsServerDemo.class.getResource("/").toURI()); + + $.httpsServer(new ConscryptSecureSessionFactory()) + .router().get("/").handler(ctx -> ctx.end("hello world!")) + .router().get("/static/*") + .handler(new StaticFileHandler(path.toAbsolutePath().toString())) + .listen("localhost", 8081); + } +} +``` +Conscrypt is a Java Security Provider (JSP) that implements parts of the Java Cryptography Extension (JCE) and Java Secure Socket Extension (JSSE). It uses BoringSSL to provide cryptographical primitives and Transport Layer Security (TLS) for Java applications. The newest version is `1.0.0.RC13`. It still has some compatibility problems in our tests. + +Update log: +1. Improves performance of network tools. +2. Set the OpenSSL is default SSL engine provider. +3. Fix the SSL handshake state logic mistake. +4. Fix the close TCP connection exception when the connection is timeout. diff --git a/_posts/2017-12-05-firefly-4.6.3-release.md b/_posts/2017-12-05-firefly-4.6.3-release.md new file mode 100644 index 000000000..4e75269d4 --- /dev/null +++ b/_posts/2017-12-05-firefly-4.6.3-release.md @@ -0,0 +1,58 @@ +--- + +category : release +title: Firefly v4.6.3 is released +date: "2017-12-05 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.6.3 is a patch release. It fixes the Conscrypt compatibility problems. The Conscrypt 'sslEngine.unwrap(src, dst)' method can not support the destination buffer remaining is 0. In that case, the Conscrypt SSL engine will throw an EOFException. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.6.3 is a patch release. It fixes the Conscrypt(1.0.0.RC13) compatibility problems. When I call the Conscrypt(1.0.0.RC13) `sslEngine.unwrap(src, dst)` method and the destination buffer remaining is 0, I encounter an EORException. +```java +// if the dst.remaining() return 0, sslEngine will throw the EORException. +SSLEngineResult result = sslEngine.unwrap(src, receivedAppBuf); +``` + +In this case, the jdk default SSL engine provider will return the status is `BUFFER_OVERFLOW`. I only need to resize the destination buffer capacity. Such as: +```java +while(true) { + SSLEngineResult result = sslEngine.unwrap(src, receivedAppBuf); + switch (result.getStatus()) { + case BUFFER_OVERFLOW: { + resizeAppBuffer(); + // retry the operation. + } + break; + } +} +``` + +The `resizeAppBuffer` method: +```java +protected void resizeAppBuffer() { + int applicationBufferSize = sslEngine.getSession().getApplicationBufferSize(); + ByteBuffer b = newBuffer(receivedAppBuf.position() + applicationBufferSize); + receivedAppBuf.flip(); + b.put(receivedAppBuf); + receivedAppBuf = b; +} +``` + +If we want to adapt the Conscrypt(1.0.0.RC13), we need call `resizeAppBuffer` method before the `unwrap` method. Such as: +```java +while(true) { + if (!receivedAppBuf.hasRemaining()) { // for Conscrypt compatibility + resizeAppBuffer(); + } + SSLEngineResult result = sslEngine.unwrap(src, receivedAppBuf); + switch (result.getStatus()) { + case BUFFER_OVERFLOW: { + resizeAppBuffer(); + // retry the operation. + } + break; + } +} +``` diff --git a/_posts/2017-12-11-firefly-4.6.4-release.md b/_posts/2017-12-11-firefly-4.6.4-release.md new file mode 100644 index 000000000..2c5a83844 --- /dev/null +++ b/_posts/2017-12-11-firefly-4.6.4-release.md @@ -0,0 +1,21 @@ +--- + +category : release +title: Firefly v4.6.4 is released +date: "2017-12-11 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.6.4 is a patch release. It fixes the HTTP server receives body data error when it uses HTTP2 protocol. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.6.4 is a patch release. It fixes the HTTP server receives body data error when it uses HTTP2 protocol. The HTTP2 uses DATA frames to carry message payloads. The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in the HTTP2. + +If the client can not know the data size and sends stream data that does not contain `Content-Length` header, the HTTPBodyHandler cannot process this case correctly. We add a compatibility method to fix this problem. Such as: +```java +public boolean isChunked(SimpleRequest request) { + String transferEncoding = request.getFields().get(HttpHeader.TRANSFER_ENCODING); + return HttpHeaderValue.CHUNKED.asString().equals(transferEncoding) + || (request.getHttpVersion() == HttpVersion.HTTP_2 && request.getContentLength() < 0); +} +``` diff --git a/_posts/2017-12-16-firefly-4.6.5-release.md b/_posts/2017-12-16-firefly-4.6.5-release.md new file mode 100644 index 000000000..7fe28fb5e --- /dev/null +++ b/_posts/2017-12-16-firefly-4.6.5-release.md @@ -0,0 +1,16 @@ +--- + +category : release +title: Firefly v4.6.5 is released +date: "2017-12-16 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.6.5 is a patch release. It fixes some bugs of the HTTP2 codec. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.6.5 is a patch release. It fixes some bugs of the HTTP2 codec. + +Update log: +1. Fix the FlowControlStrategy keeps around reset streams. +2. Fix the HTTP2 stream reset leaves stream frames in the flusher. diff --git a/_posts/2018-01-12-firefly-4.7.0-release.md b/_posts/2018-01-12-firefly-4.7.0-release.md new file mode 100644 index 000000000..d68c0c665 --- /dev/null +++ b/_posts/2018-01-12-firefly-4.7.0-release.md @@ -0,0 +1,52 @@ +--- + +category : release +title: Firefly v4.7.0 is released +date: "2018-01-12 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.7.0 adds new WebSocket DSL APIs, optimizes the HTTP2 codec's performance, and fixes some bugs. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.7.0 adds new WebSocket DSL APIs, improves the HTTP2 codec's performance, and fixes some bugs. The new WebSocket DSL APIs like this: +```java +public static void main(String[] args) { + SimpleWebSocketServer server = $.createWebSocketServer(); + server.webSocket("/helloWebSocket") + .onConnect(conn -> conn.sendText("OK.")) + .onText((text, conn) -> System.out.println("The server received: " + text)) + .listen("localhost", 8080); + + SimpleWebSocketClient client = $.createWebSocketClient(); + client.webSocket("ws://localhost:8080/helloWebSocket") + .onText((text, conn) -> System.out.println("The client received: " + text)) + .connect() + .thenAccept(conn -> conn.sendText("Hello server.")); +} +``` + +The Kotlin version: +```kotlin +fun main(args: Array) { + val server = firefly.createWebSocketServer() + server.webSocket("/helloWebSocket") + .onConnect { conn -> conn.sendText("OK.") } + .onText { text, conn -> println("The server received: " + text) } + .listen("localhost", 8080) + + val client = firefly.createWebSocketClient() + client.webSocket("ws://localhost:8080/helloWebSocket") + .onText { text, conn -> println("The client received: " + text) } + .connect() + .thenAccept { conn -> conn.sendText("Hello server.") } +} +``` + +More detailed information, please refer to the [WebSocket server and client]({{ site.url }}/docs/websocket-server-and-client.html) document. + +Update log: +1. Add new WebSocket DSL APIs (include Java and Kotlin). +2. Optimize the HTTP2 codec's performance. +3. Fix the HTTP1 upgrades to HTTP2 or WebSocket failure. +4. Fix the session can't close correctly when the many threads call the close and write method concurrently. diff --git a/_posts/2018-03-31-firefly-4.8.0-release.md b/_posts/2018-03-31-firefly-4.8.0-release.md new file mode 100644 index 000000000..f77cf7d6a --- /dev/null +++ b/_posts/2018-03-31-firefly-4.8.0-release.md @@ -0,0 +1,117 @@ +--- + +category : release +title: Firefly v4.8.0 is released +date: "2018-03-31 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.8.0 adds RedisSessionHandler, AsyncStaticFileHandler, and fixes the AsyncTransactionalManager JDBC connection early closed exception. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.8.0 adds RedisSessionHandler, AsyncStaticFileHandler, and fixes the AsyncTransactionalManager JDBC connection early closed exception. + +Usually, we develop the HTTP service that is stateless, so we store the user session state in the external DB system. The Redis fits well with that situation. In this version, we provide the RedisSessionHandler that stores the session state in the Redis. + +The first we set the Redis configuration when the RedisSessionHandler is initialized. +```kotlin +@Component("redisSessionHandler") +class RedisSessionHandler : Handler { + + private val handler: RedisHTTPSessionHandler + + init { + val store = RedisSessionStore() + val client = Redisson.createReactive() + store.client = client + store.keyPrefix = "com:fireflysource" + store.sessionKey = "test_session" + handler = RedisHTTPSessionHandler(store) + } + + override fun handle(ctx: RoutingContext?) = handler.handle(ctx) +} +``` + +And then, set the handler to the router. In this way, we can store user state in the Redis session using the session APIs in the RoutingContext. +```kotlin +@Inject +private lateinit var redisHTTPSessionHandler: RedisSessionHandler + + +// session handler +router { + path = "*" + asyncHandler { redisHTTPSessionHandler.handle(this) } +} + +// the user login +private suspend fun verifyPasswordRequest(ctx: RoutingContext) { + val username = ctx.getParameter("username") + val password = ctx.getParameter("password") + + Assert.hasText(username, "The username is required") + Assert.hasText(password, "The password is required") + + try { + val user = userService.getByName(username) + Assert.isTrue(user.password == password, "The password is incorrect") + + val session = ctx.session.await() + val userInfo = UserInfo(user.id ?: 0, user.name) + session.attributes[config.loginUserKey] = userInfo + session.maxInactiveInterval = config.sessionMaxInactiveInterval + ctx.updateSession(session).await() + ctx.setAttribute(config.loginUserKey, userInfo) + ctx.redirect(getBackURL(ctx)) + log.info("user $userInfo login success!") + } catch (e: RecordNotFound) { + throw IllegalArgumentException("The username is incorrect") + } +} +``` + +The Firefly v4.8.0 adds the AsyncStaticFileHandler that reads the static file using the AsynchronousFileChannel. It is not blocking the Firefly network thread when the user gets the static file from the Firefly HTTP server. We can use the AsyncStaticFileHandler in this way: +```kotlin +@Component("staticResourceHandler") +class StaticResourceHandler : AsyncHandler { + + val staticResources = listOf("/favicon.ico", "/static/*") + + companion object { + private val log = KtLogger.getLogger { } + private val staticFileHandler = createStaticFileHandler() + + private fun createStaticFileHandler(): AsyncStaticFileHandler { + try { + val path = Paths.get(SystemRouter::class.java.getResource("/").toURI()) + return AsyncStaticFileHandler(path.toAbsolutePath().toString(), 16 * 1024, true) + } catch (e: Exception) { + throw CommonRuntimeException(e) + } + } + } + + override suspend fun handle(ctx: RoutingContext) { + log.info("static file request ${ctx.uri}") + ctx.put(HttpHeader.CACHE_CONTROL, "max-age=86400") + staticFileHandler.handle(ctx) + } + +} +``` + +Set the router: +```kotlin +// static file handler +router { + httpMethod = HttpMethod.GET + paths = staticResourceHandler.staticResources + asyncHandler(staticResourceHandler) +} +``` + +Update log: +1. Add RedisSessionHandler. +2. Add AsyncStaticFileHandler. +3. Fix the AsyncTransactionalManager JDBC connection early closed exception. diff --git a/_posts/2018-06-13-firefly-4.8.1-release.md b/_posts/2018-06-13-firefly-4.8.1-release.md new file mode 100644 index 000000000..fbdc315f2 --- /dev/null +++ b/_posts/2018-06-13-firefly-4.8.1-release.md @@ -0,0 +1,60 @@ +--- + +category : release +title: Firefly v4.8.1 is released +date: "2018-06-13 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.8.1 adds Coroutine MDC adapter and fixes the AsynchronousTcpSession can not write big data on windows system. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.8.1 adds Coroutine MDC adapter and fixes the AsynchronousTcpSession cannot write big data on Windows system. + +The Firefly HTTP server is asynchronous. An HTTP request passes many threads. The default MDC saves data in the ThreadLocal. It means the default MDC can not track the user request. We add a new CoroutineMappedDiagnosticContext that can save data through the whole HTTP request. The CoroutineMappedDiagnosticContext uses the coroutine interceptor mechanism. You can only use it in Firefly HTTP server Kotlin version. + +For example, the first, we need to create a new Java ServiceLoader configuration to replace the default MDC implementation. Create a new file in the classpath: +``` +classpath:/META-INF/services/com.firefly.utils.log.MappedDiagnosticContext +``` + +Add a new MDC class name in this file: +``` +com.firefly.kotlin.ext.log.CoroutineMappedDiagnosticContext +``` + +Initialize the CoroutineMappedDiagnosticContext: +```kotlin +@Inject +private lateinit var requestCtx: CoroutineLocal + +@InitialMethod +fun init() { + val mdc = MappedDiagnosticContextFactory.getInstance() + .mappedDiagnosticContext as CoroutineMappedDiagnosticContext + mdc.setRequestCtx(requestCtx) +} +``` + +Then we can use the MDC APIs in the Firefly HTTP server Kotlin version. Such as add a tracing id: +```kotlin +val mdc = MDC.putCloseable("tracingId", UUID.randomUUID().toString().replace("-", "")) +``` + +If you start a coroutine in a new context, you need to combine the new context and the current request context. We provide the method `asyncTraceable`. +``` +fun asyncTraceable(context: ContinuationInterceptor = Unconfined, block: suspend CoroutineScope.() -> T): Deferred = asyncTraceable(getRequestCtx(), context, block) +``` + +Combine the new coroutine context to the current request context. Such as: +```kotlin +val data = asyncTraceable(ioBlocking) { + fileInputStream.use { + `$`.io.readBytes(it) + } +}.await() +``` + +Update log: +1. Add CoroutineMappedDiagnosticContext. +2. Fix the AsynchronousTcpSession can not write big data on windows system. diff --git a/_posts/2018-10-16-firefly-4.9.1-release.md b/_posts/2018-10-16-firefly-4.9.1-release.md new file mode 100644 index 000000000..913c4e596 --- /dev/null +++ b/_posts/2018-10-16-firefly-4.9.1-release.md @@ -0,0 +1,61 @@ +--- + +category : release +title: Firefly v4.9.1 is released +date: "2018-10-16 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.9.1 adds OAuth2 APIs for HTTP server and client, CLI code generator, CORSHandler, AsyncWebJarHandler and fixes some bugs. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.9.1 adds OAuth2 APIs for HTTP server and client, CLI code generator, CORSHandler, AsyncWebJarHandler and fixes some bugs. + +### CLI code generator +The CLI tool can help us to create a Kotlin web project easily. Run command: +```bash +fireflyCli -g com.test.abc -a abc-haha -p com.test.abc -d www.abc.com -j www.abc.com +``` + +This command creates a firefly Kotlin web project in the current path. Import abc-haha project in your IDE. The CLI detail please see [CLI generator document]({{ site.url }}/docs/cli-generator.html) + +### OAuths APIs + The firefly framework provides APIs to support the four grant types, such as Authorization Code Grant, Implicit Grant, Resource Owner Password Credentials Grant, and Client Credentials Grant. The OAuth2 API details please see the [OAuth2 server/client document]({{ site.url }}/docs/oauth2-server-and-client.html) + +### CORS handler +The CORSHandler can allow resources to be requested from one domain and served from another. Here is an example: +```java +CORSConfiguration config = new CORSConfiguration(); +config.setAllowOrigins(new HashSet<>(Arrays.asList("http://foo.com", "http://bar.com"))); +config.setExposeHeaders(Arrays.asList("a1", "a2")); +config.setAllowHeaders(new HashSet<>(Arrays.asList("a1", "a2", "a3", "a4"))); +CORSHandler corsHandler = new CORSHandler(); +corsHandler.setConfiguration(config); + +HTTP2ServerBuilder s = $.httpServer(); + +s.router().path("/cors/*").handler(corsHandler) + .router().path("/cors/foo").handler(ctx -> ctx.end("foo")) + .router().path("/cors/bar").handler(ctx -> { + JsonObject jsonObject = ctx.getJsonObjectBody(); + Map map = new HashMap<>(jsonObject); + map.put("bar", "x1"); + ctx.writeJson(map).end(); +}) + .listen(host, port); +``` + + + +Update log: +1. Add CLI code generator. +2. Add OAuth2 APIs for HTTP server and client. +3. Add AsyncWebJarHandler. +4. Add JsonProperty Annotation that maps JSON key to the object field. +5. Add timeout parameter for HTTP, WebSocket, and DB Kotlin asynchronous APIs. +6. Add CORSHandler. +7. Add createSession method for RoutingContext. +8. Add asyncNext suspend method for RoutingContext. +9. Add image processing components. +10. Fix the data disorder when the net framework writes the big data. +11. Fix the JsonStringReader.readLong() method reads the blank string exception. diff --git a/_posts/2018-11-05-firefly-4.9.3-release.md b/_posts/2018-11-05-firefly-4.9.3-release.md new file mode 100644 index 000000000..9f13d586a --- /dev/null +++ b/_posts/2018-11-05-firefly-4.9.3-release.md @@ -0,0 +1,19 @@ +--- + +category : release +title: Firefly v4.9.3 is released +date: "2018-11-05 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.9.3 migrates Kotlin version to 1.3.0, fixes the JDK11 compatibility and some bugs. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.9.3 updates Kotlin version to 1.3.0, fixes the JDK11 compatibility and some bugs. + +The Kotlin 1.3.0 coroutine removes the experimental package name, and we can use the IntelliJ IDEA Kotlin migration tools to rename the experimental package name when you update Firefly to 4.9.3. + +Update log: +1. Migrates Kotlin version to 1.3.0. +2. Fix the JDK11 compatibility. +3. Fix the flex stream header data error. diff --git a/_posts/2018-11-13-firefly-4.9.4-release.md b/_posts/2018-11-13-firefly-4.9.4-release.md new file mode 100644 index 000000000..357507f0a --- /dev/null +++ b/_posts/2018-11-13-firefly-4.9.4-release.md @@ -0,0 +1,18 @@ +--- + +category : release +title: Firefly v4.9.4 is released +date: "2018-11-13 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.9.4 extracts firefly version configuration to the properties file, updates the coroutine version to 1.0.1, the Redisson version to 4.9.0, and the Conscrypt version to 1.4.1. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.9.4 extracts firefly version configuration to the properties file, updates the coroutine version to 1.0.1, the Redisson version to 4.9.0, and the Conscrypt version to 1.4.1. + +Update log: +1. Update kotlin coroutine version to 1.0.1. +2. Update redisson version to 3.9.0. +3. Update Conscrypt version to 1.4.1. +4. Extract firefly version configuration to the properties file. diff --git a/_posts/2018-11-26-firefly-4.9.5-release.md b/_posts/2018-11-26-firefly-4.9.5-release.md new file mode 100644 index 000000000..5abf93115 --- /dev/null +++ b/_posts/2018-11-26-firefly-4.9.5-release.md @@ -0,0 +1,29 @@ +--- + +category : release +title: Firefly v4.9.5 is released +date: "2018-11-26 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v4.9.5 adds time-based log file split configuration, fixes the JDBC connection thread safe problem, remove the ALPN boot dependency, and updates the Kotlin version to 1.3.10. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v4.9.5 adds time-based log file split configuration, fixes the JDBC connection thread safe problem, remove the ALPN boot dependency, and updates the Kotlin version to 1.3.10. + +We add a new log split parameter `max-split-time`, the value is minute, hour or day. For example: +```xml + + time-split-minute + INFO + ${log.path} + minute + +``` +In this case, the log file is split by one minute. The default value is day. + +Update log: +1. Add time-based log file split configuration. +2. Fix the JDBC connection thread safe problem. +3. Remove the ALPN boot dependency. +4. Update the Kotlin version to 1.3.10. diff --git a/_posts/2021-02-18-firefly-5.0.0-alpha12-release.md b/_posts/2021-02-18-firefly-5.0.0-alpha12-release.md new file mode 100644 index 000000000..61e4f7cdd --- /dev/null +++ b/_posts/2021-02-18-firefly-5.0.0-alpha12-release.md @@ -0,0 +1,17 @@ +--- + +category : release +title: Firefly v5.0.0-alpha12 is released +date: "2021-04-25 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v5.0.0-alpha12 updates the Kotlin version to 1.4.32, conscrypt version to 2.5.2 and adds some suspended APIs. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v5.0.0-alpha12 updates the Kotlin version to 1.4.32, Conscrypt version to 2.5.2 and adds some suspended APIs. + +Update log: +1. Adds some suspended APIs. +2. Updates Conscrypt version to 2.5.2. +3. Updates Kotlin version to 1.4.32. \ No newline at end of file diff --git a/_posts/2021-02-18-firefly-5.0.0-alpha9-release.md b/_posts/2021-02-18-firefly-5.0.0-alpha9-release.md new file mode 100644 index 000000000..2c9f064c1 --- /dev/null +++ b/_posts/2021-02-18-firefly-5.0.0-alpha9-release.md @@ -0,0 +1,20 @@ +--- + +category : release +title: Firefly v5.0.0-alpha9 is released +date: "2021-02-18 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v5.0.0-alpha9 refactors the net framework based on Kotlin coroutine, optimizes performance over 50%. Supports the newest JDK, e.g. JDK15 and updates the Kotlin version to 1.4.30. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v5.0.0-alpha9 refactors the net framework based on Kotlin coroutine, optimizes performance over 50%. Supports the newest JDK, e.g. JDK15 and updates the Kotlin version to 1.4.30. + +Firefly 5.0.0 APIs are not backward-compatible with 4.x.x. The API update details reference document and example. + +Update log: +1. Refactors the net framework based on Kotlin coroutine. +2. Optimizes performance over 50%. +3. Supports the newest JDK, e.g. JDK15. +4. Updates the Kotlin version to 1.4.30. \ No newline at end of file diff --git a/_posts/2021-10-12-firefly-5.0.0-alpha14-release.md b/_posts/2021-10-12-firefly-5.0.0-alpha14-release.md new file mode 100644 index 000000000..bef534875 --- /dev/null +++ b/_posts/2021-10-12-firefly-5.0.0-alpha14-release.md @@ -0,0 +1,18 @@ +--- + +category : release +title: Firefly v5.0.0-alpha14 is released +date: "2021-10-12 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v5.0.0-alpha14 updates the Kotlin version to 1.5.31, adds JNI helper module, HTTP client proxy configs and adapts JDK 17. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v5.0.0-alpha14 updates the Kotlin version to 1.5.31, adds JNI helper module, HTTP client proxy configs and adapts JDK 17. + +Update log: +1. Adds JNI helper module. +2. Adds HTTP client proxy configs. +3. Adapts JDK 17. +4. Updates Kotlin version to 1.5.31. \ No newline at end of file diff --git a/_posts/2021-10-12-firefly-5.0.0-release.md b/_posts/2021-10-12-firefly-5.0.0-release.md new file mode 100644 index 000000000..61b8b53a3 --- /dev/null +++ b/_posts/2021-10-12-firefly-5.0.0-release.md @@ -0,0 +1,16 @@ +--- + +category : release +title: Firefly v5.0.0 is released +date: "2021-12-23 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v5.0.0 updates the Kotlin version to 1.6.10, Kotlin coroutine version to 1.6.0. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v5.0.0 updates the Kotlin version to 1.6.10, Kotlin coroutine version to 1.6.0. + +Update log: +1. Updates Kotlin version to 1.6.10. +2. Updates Kotlin coroutine version to 1.6.0. \ No newline at end of file diff --git a/_posts/2021-10-12-firefly-5.0.1-release.md b/_posts/2021-10-12-firefly-5.0.1-release.md new file mode 100644 index 000000000..0017816c7 --- /dev/null +++ b/_posts/2021-10-12-firefly-5.0.1-release.md @@ -0,0 +1,17 @@ +--- + +category : release +title: Firefly v5.0.1 is released +date: "2022-04-16 00:00:00 +0800" +author: Alvin Qiu +excerpt: Firefly v5.0.1 updates the Kotlin version to 1.6.20, Kotlin coroutine version to 1.6.1. Fix the jackson security vulnerabilities. Please click view all to see the details. + +--- +

    {{ page.date | date_to_string }}, {{ page.author }}

    + +Firefly v5.0.1 updates the Kotlin version to 1.6.20, Kotlin coroutine version to 1.6.1. Fix the jackson security vulnerabilities. + +Update log: +1. Updates Kotlin version to 1.6.20. +2. Updates Kotlin coroutine version to 1.6.1. +3. Updates jackson data bind version to 2.13.2.2. \ No newline at end of file diff --git a/beans.xsd b/beans.xsd new file mode 100644 index 000000000..5356fbd98 --- /dev/null +++ b/beans.xsd @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 000000000..d460e7131 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,30 @@ +--- +layout: post +--- + +{% for post in paginator.posts %} +
    +

    {{ post.title }}

    +

    {{ post.date | date_to_string }}, {{ post.author }}

    +

    {{ post.excerpt }}

    + +
    +{% endfor %} + + + +{% if paginator.total_pages > 1 %} +
      + {% if paginator.previous_page %} + + {% else %} + + {% endif %} + + {% if paginator.next_page %} + + {% else %} + + {% endif %} +
    +{% endif %} diff --git a/css/bootstrap-theme.min.css b/css/bootstrap-theme.min.css new file mode 100644 index 000000000..cad36b4e6 --- /dev/null +++ b/css/bootstrap-theme.min.css @@ -0,0 +1 @@ +.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0%,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0)}.btn-default:active,.btn-default.active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.btn-primary:active,.btn-primary.active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.btn-success:active,.btn-success.active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.btn-warning:active,.btn-warning.active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.btn-danger:active,.btn-danger.active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.btn-info:active,.btn-info.active{background-color:#31b0d5;border-color:#2aabd2}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0%,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0%,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0%,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0%,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0%,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0%,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} \ No newline at end of file diff --git a/css/bootstrap.min.css b/css/bootstrap.min.css new file mode 100644 index 000000000..295aa8e15 --- /dev/null +++ b/css/bootstrap.min.css @@ -0,0 +1,12 @@ +@import url("font.css"); +/*! + * bootswatch v3.3.7 + * Homepage: http://bootswatch.com + * Copyright 2012-2016 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Georgia,"Times New Roman",Times,serif;font-size:15px;line-height:1.42857143;color:#777777;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#eb6864;text-decoration:none}a:hover,a:focus{color:#e22620;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:21px;margin-bottom:21px;border:0;border-top:1px solid #eeeeee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"News Cycle","Arial Narrow Bold",sans-serif;font-weight:700;line-height:1.1;color:#000000}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:21px;margin-bottom:10.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10.5px;margin-bottom:10.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:39px}h2,.h2{font-size:32px}h3,.h3{font-size:26px}h4,.h4{font-size:19px}h5,.h5{font-size:15px}h6,.h6{font-size:13px}p{margin:0 0 10.5px}.lead{margin-bottom:21px;font-size:17px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:22.5px}}small,.small{font-size:86%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#eb6864}a.text-primary:hover,a.text-primary:focus{color:#e53c37}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-danger{color:#b94a48}a.text-danger:hover,a.text-danger:focus{color:#953b39}.bg-primary{color:#fff;background-color:#eb6864}a.bg-primary:hover,a.bg-primary:focus{background-color:#e53c37}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid #eeeeee}ul,ol{margin-top:0;margin-bottom:10.5px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:21px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10.5px 21px;margin:0 0 21px;font-size:18.75px;border-left:5px solid #eeeeee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:21px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:10px;margin:0 0 10.5px;font-size:14px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333333;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:21px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #dddddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #dddddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #dddddd}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #dddddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #dddddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15.75px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #dddddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:21px;font-size:22.5px;line-height:inherit;color:#777777;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:9px;font-size:15px;line-height:1.42857143;color:#777777}.form-control{display:block;width:100%;height:39px;padding:8px 12px;font-size:15px;line-height:1.42857143;color:#777777;background-color:#ffffff;background-image:none;border:1px solid #cccccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:39px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:31px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:56px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:21px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:9px;padding-bottom:9px;margin-bottom:0;min-height:36px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:31px;padding:5px 10px;font-size:13px;line-height:1.5;border-radius:3px}select.input-sm{height:31px;line-height:31px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:31px;padding:5px 10px;font-size:13px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:31px;line-height:31px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:31px;min-height:34px;padding:6px 10px;font-size:13px;line-height:1.5}.input-lg{height:56px;padding:14px 16px;font-size:19px;line-height:1.3333333;border-radius:6px}select.input-lg{height:56px;line-height:56px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:56px;padding:14px 16px;font-size:19px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:56px;line-height:56px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:56px;min-height:40px;padding:15px 16px;font-size:19px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:48.75px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:39px;height:39px;line-height:39px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:56px;height:56px;line-height:56px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:31px;height:31px;line-height:31px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;border-color:#468847;background-color:#dff0d8}.has-success .form-control-feedback{color:#468847}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;border-color:#c09853;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#c09853}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;border-color:#b94a48;background-color:#f2dede}.has-error .form-control-feedback{color:#b94a48}.has-feedback label~.form-control-feedback{top:26px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#b7b7b7}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:9px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:30px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:9px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:15px;font-size:19px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:13px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:8px 12px;font-size:15px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#ffffff;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#ffffff;background-color:#999999;border-color:#999999}.btn-default:focus,.btn-default.focus{color:#ffffff;background-color:#808080;border-color:#595959}.btn-default:hover{color:#ffffff;background-color:#808080;border-color:#7a7a7a}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#ffffff;background-color:#808080;border-color:#7a7a7a}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#ffffff;background-color:#6e6e6e;border-color:#595959}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#999999;border-color:#999999}.btn-default .badge{color:#999999;background-color:#ffffff}.btn-primary{color:#ffffff;background-color:#eb6864;border-color:#eb6864}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#e53c37;border-color:#b81c18}.btn-primary:hover{color:#ffffff;background-color:#e53c37;border-color:#e4332e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#e53c37;border-color:#e4332e}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#dc221c;border-color:#b81c18}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#eb6864;border-color:#eb6864}.btn-primary .badge{color:#eb6864;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#22b24c;border-color:#22b24c}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#1a873a;border-color:#0e471e}.btn-success:hover{color:#ffffff;background-color:#1a873a;border-color:#187f36}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#1a873a;border-color:#187f36}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#14692d;border-color:#0e471e}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#22b24c;border-color:#22b24c}.btn-success .badge{color:#22b24c;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#336699;border-color:#336699}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#264c73;border-color:#132639}.btn-info:hover{color:#ffffff;background-color:#264c73;border-color:#24476b}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#264c73;border-color:#24476b}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#1d3b58;border-color:#132639}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#336699;border-color:#336699}.btn-info .badge{color:#336699;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#f5e625;border-color:#f5e625}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#ddce0a;border-color:#948a07}.btn-warning:hover{color:#ffffff;background-color:#ddce0a;border-color:#d3c50a}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#ddce0a;border-color:#d3c50a}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#bbae09;border-color:#948a07}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f5e625;border-color:#f5e625}.btn-warning .badge{color:#f5e625;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#f57a00;border-color:#f57a00}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#c26100;border-color:#763b00}.btn-danger:hover{color:#ffffff;background-color:#c26100;border-color:#b85c00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#c26100;border-color:#b85c00}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#9e4f00;border-color:#763b00}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#f57a00;border-color:#f57a00}.btn-danger .badge{color:#f57a00;background-color:#ffffff}.btn-link{color:#eb6864;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#e22620;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:14px 16px;font-size:19px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:13px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:13px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;text-align:left;background-color:#ffffff;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#333333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#ffffff;background-color:#eb6864}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#eb6864}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:13px;line-height:1.42857143;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:56px;padding:14px 16px;font-size:19px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:56px;line-height:56px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:31px;padding:5px 10px;font-size:13px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:31px;line-height:31px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:8px 12px;font-size:15px;font-weight:normal;line-height:1;color:#777777;text-align:center;background-color:#eeeeee;border:1px solid #cccccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:13px;border-radius:3px}.input-group-addon.input-lg{padding:14px 16px;font-size:19px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eeeeee;border-color:#eb6864}.nav .nav-divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #dddddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#777777;background-color:#ffffff;border:1px solid #dddddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #dddddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #dddddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#eb6864}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #dddddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #dddddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:60px;margin-bottom:21px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:19.5px 15px;font-size:19px;line-height:21px;height:60px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:13px;margin-bottom:13px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:9.75px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:21px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:21px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:19.5px;padding-bottom:19.5px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:10.5px;margin-bottom:10.5px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:10.5px;margin-bottom:10.5px}.navbar-btn.btn-sm{margin-top:14.5px;margin-bottom:14.5px}.navbar-btn.btn-xs{margin-top:19px;margin-bottom:19px}.navbar-text{margin-top:19.5px;margin-bottom:19.5px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#ffffff;border-color:#eeeeee}.navbar-default .navbar-brand{color:#000000}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#000000;background-color:#eeeeee}.navbar-default .navbar-text{color:#000000}.navbar-default .navbar-nav>li>a{color:#000000}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#000000;background-color:#eeeeee}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#000000;background-color:#eeeeee}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#dddddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#dddddd}.navbar-default .navbar-toggle .icon-bar{background-color:#cccccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#eeeeee}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#eeeeee;color:#000000}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#000000}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#000000;background-color:#eeeeee}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#000000;background-color:#eeeeee}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#000000}.navbar-default .navbar-link:hover{color:#000000}.navbar-default .btn-link{color:#000000}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#000000}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#cccccc}.navbar-inverse{background-color:#eb6864;border-color:#e53c37}.navbar-inverse .navbar-brand{color:#ffffff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:#e74b47}.navbar-inverse .navbar-text{color:#ffffff}.navbar-inverse .navbar-nav>li>a{color:#ffffff}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:#e74b47}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#e74b47}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#e53c37}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#e53c37}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#e74944}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#e74b47;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#e53c37}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#e53c37}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#e74b47}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#e74b47}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444444;background-color:transparent}}.navbar-inverse .navbar-link{color:#ffffff}.navbar-inverse .navbar-link:hover{color:#ffffff}.navbar-inverse .btn-link{color:#ffffff}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#ffffff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444444}.breadcrumb{padding:8px 15px;margin-bottom:21px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#cccccc}.breadcrumb>.active{color:#999999}.pagination{display:inline-block;padding-left:0;margin:21px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:8px 12px;line-height:1.42857143;text-decoration:none;color:#eb6864;background-color:#ffffff;border:1px solid #dddddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#e22620;background-color:#eeeeee;border-color:#dddddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#999999;background-color:#f5f5f5;border-color:#dddddd;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;background-color:#ffffff;border-color:#dddddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:14px 16px;font-size:19px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:13px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:21px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#ffffff;border:1px solid #dddddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:#ffffff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#eb6864}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#e53c37}.label-success{background-color:#22b24c}.label-success[href]:hover,.label-success[href]:focus{background-color:#1a873a}.label-info{background-color:#336699}.label-info[href]:hover,.label-info[href]:focus{background-color:#264c73}.label-warning{background-color:#f5e625}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ddce0a}.label-danger{background-color:#f57a00}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c26100}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:13px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#eb6864;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#eb6864;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eeeeee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:23px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:68px}}.thumbnail{display:block;padding:4px;margin-bottom:21px;line-height:1.42857143;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#eb6864}.thumbnail .caption{padding:9px;color:#777777}.alert{padding:15px;margin-bottom:21px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{background-color:#fcf8e3;border-color:#fbeed5;color:#c09853}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{background-color:#f2dede;border-color:#eed3d7;color:#b94a48}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:21px;margin-bottom:21px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:13px;line-height:21px;color:#ffffff;text-align:center;background-color:#eb6864;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#22b24c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#336699}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f5e625}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#f57a00}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #dddddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#555555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eeeeee;color:#999999;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#eb6864;border-color:#eb6864}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#ffffff}.list-group-item-success{color:#468847;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#468847}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#468847;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#468847;border-color:#468847}.list-group-item-info{color:#3a87ad;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#3a87ad}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#3a87ad;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#3a87ad;border-color:#3a87ad}.list-group-item-warning{color:#c09853;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#c09853}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#c09853;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#c09853;border-color:#c09853}.list-group-item-danger{color:#b94a48;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#b94a48}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#b94a48;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#b94a48;border-color:#b94a48}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:21px;background-color:#ffffff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:17px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #dddddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #dddddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:21px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #dddddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #dddddd}.panel-default{border-color:#dddddd}.panel-default>.panel-heading{color:#777777;background-color:#f5f5f5;border-color:#dddddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#dddddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#777777}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#dddddd}.panel-primary{border-color:#eb6864}.panel-primary>.panel-heading{color:#ffffff;background-color:#eb6864;border-color:#eb6864}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#eb6864}.panel-primary>.panel-heading .badge{color:#eb6864;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#eb6864}.panel-success{border-color:#22b24c}.panel-success>.panel-heading{color:#468847;background-color:#22b24c;border-color:#22b24c}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#22b24c}.panel-success>.panel-heading .badge{color:#22b24c;background-color:#468847}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#22b24c}.panel-info{border-color:#336699}.panel-info>.panel-heading{color:#3a87ad;background-color:#336699;border-color:#336699}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#336699}.panel-info>.panel-heading .badge{color:#336699;background-color:#3a87ad}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#336699}.panel-warning{border-color:#f5e625}.panel-warning>.panel-heading{color:#c09853;background-color:#f5e625;border-color:#f5e625}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f5e625}.panel-warning>.panel-heading .badge{color:#f5e625;background-color:#c09853}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f5e625}.panel-danger{border-color:#f57a00}.panel-danger>.panel-heading{color:#b94a48;background-color:#f57a00;border-color:#f57a00}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f57a00}.panel-danger>.panel-heading .badge{color:#f57a00;background-color:#b94a48}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f57a00}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:22.5px;font-weight:bold;line-height:1;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:Georgia,"Times New Roman",Times,serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:13px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:Georgia,"Times New Roman",Times,serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:15px;background-color:#ffffff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:15px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{font-size:18px;font-family:"News Cycle","Arial Narrow Bold",sans-serif;font-weight:700}.navbar-default .badge{background-color:#000;color:#fff}.navbar-inverse .badge{background-color:#fff;color:#eb6864}.navbar-brand{font-size:inherit;font-weight:700;text-transform:uppercase}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#f57a00}.has-warning .form-control,.has-warning .form-control:focus{border-color:#f57a00}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#eb6864}.has-error .form-control,.has-error .form-control:focus{border-color:#eb6864}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#22b24c}.has-success .form-control,.has-success .form-control:focus{border-color:#22b24c}.badge{padding-bottom:4px;vertical-align:3px;font-size:10px}.jumbotron h1,.jumbotron h2,.jumbotron h3,.jumbotron h4,.jumbotron h5,.jumbotron h6{font-family:"News Cycle","Arial Narrow Bold",sans-serif;font-weight:700;color:#000}.panel-primary .panel-title,.panel-success .panel-title,.panel-warning .panel-title,.panel-danger .panel-title,.panel-info .panel-title{color:#fff} diff --git a/css/font.css b/css/font.css new file mode 100644 index 000000000..6e63e20f6 --- /dev/null +++ b/css/font.css @@ -0,0 +1,32 @@ +/* latin-ext */ +@font-face { + font-family: 'News Cycle'; + font-style: normal; + font-weight: 400; + src: url(../fonts/d03oiboZGiaNuMDvH253CgsYbbCjybiHxArTLjt7FRU.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'News Cycle'; + font-style: normal; + font-weight: 400; + src: url(../fonts/9Xe8dq6pQDsPyVH2D3tMQgzyDMXhdD8sAj6OAJTFsBI.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} +/* latin-ext */ +@font-face { + font-family: 'News Cycle'; + font-style: normal; + font-weight: 700; + src: url(../fonts/G28Ny31cr5orMqEQy6ljtzrEaqfC9P2pvLXik1Kbr9s.woff2) format('woff2'); + unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'News Cycle'; + font-style: normal; + font-weight: 700; + src: url(../fonts/G28Ny31cr5orMqEQy6ljt2aVI6zN22yiurzcBKxPjFE.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; +} diff --git a/css/github.css b/css/github.css new file mode 100644 index 000000000..dc60655fa --- /dev/null +++ b/css/github.css @@ -0,0 +1,61 @@ +.hll { background-color: #ffffcc } +.c { color: #999988; font-style: italic } /* Comment */ +.err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.k { color: #000000; font-weight: bold } /* Keyword */ +.o { color: #000000; font-weight: bold } /* Operator */ +.cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ +.c1 { color: #999988; font-style: italic } /* Comment.Single */ +.cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.ge { color: #000000; font-style: italic } /* Generic.Emph */ +.gr { color: #aa0000 } /* Generic.Error */ +.gh { color: #999999 } /* Generic.Heading */ +.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.go { color: #888888 } /* Generic.Output */ +.gp { color: #555555 } /* Generic.Prompt */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #aaaaaa } /* Generic.Subheading */ +.gt { color: #aa0000 } /* Generic.Traceback */ +.kc { color: #000000; font-weight: bold } /* Keyword.Constant */ +.kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ +.kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ +.kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ +.kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ +.kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.m { color: #009999 } /* Literal.Number */ +.s { color: #d01040 } /* Literal.String */ +.na { color: #008080 } /* Name.Attribute */ +.nb { color: #0086B3 } /* Name.Builtin */ +.nc { color: #445588; font-weight: bold } /* Name.Class */ +.no { color: #008080 } /* Name.Constant */ +.nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ +.ni { color: #800080 } /* Name.Entity */ +.ne { color: #990000; font-weight: bold } /* Name.Exception */ +.nf { color: #990000; font-weight: bold } /* Name.Function */ +.nl { color: #990000; font-weight: bold } /* Name.Label */ +.nn { color: #555555 } /* Name.Namespace */ +.nt { color: #000080 } /* Name.Tag */ +.nv { color: #008080 } /* Name.Variable */ +.ow { color: #000000; font-weight: bold } /* Operator.Word */ +.w { color: #bbbbbb } /* Text.Whitespace */ +.mf { color: #009999 } /* Literal.Number.Float */ +.mh { color: #009999 } /* Literal.Number.Hex */ +.mi { color: #009999 } /* Literal.Number.Integer */ +.mo { color: #009999 } /* Literal.Number.Oct */ +.sb { color: #d01040 } /* Literal.String.Backtick */ +.sc { color: #d01040 } /* Literal.String.Char */ +.sd { color: #d01040 } /* Literal.String.Doc */ +.s2 { color: #d01040 } /* Literal.String.Double */ +.se { color: #d01040 } /* Literal.String.Escape */ +.sh { color: #d01040 } /* Literal.String.Heredoc */ +.si { color: #d01040 } /* Literal.String.Interpol */ +.sx { color: #d01040 } /* Literal.String.Other */ +.sr { color: #009926 } /* Literal.String.Regex */ +.s1 { color: #d01040 } /* Literal.String.Single */ +.ss { color: #990073 } /* Literal.String.Symbol */ +.bp { color: #999999 } /* Name.Builtin.Pseudo */ +.vc { color: #008080 } /* Name.Variable.Class */ +.vg { color: #008080 } /* Name.Variable.Global */ +.vi { color: #008080 } /* Name.Variable.Instance */ +.il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/css/my-site.css b/css/my-site.css new file mode 100644 index 000000000..f7d04925e --- /dev/null +++ b/css/my-site.css @@ -0,0 +1,10 @@ +.bs-docs-sidebar { + position: fixed; +} + +.bs-docs-sidebar .divider { + height: 1px; + margin: 9.5px 0; + overflow: hidden; + background-color: #e5e5e5; +} diff --git a/deploy.ps1 b/deploy.ps1 deleted file mode 100644 index efda59f25..000000000 --- a/deploy.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -$env:HTTP_PROXY="http://127.0.0.1:7890" -$env:HTTPS_PROXY="http://127.0.0.1:7890" -$response = Invoke-RestMethod 'https://www.google.com' -Method 'GET' -if ($response.Length -gt 0) { - Write-Output "set proxy success" -} -mvn clean deploy -Prelease \ No newline at end of file diff --git a/firefly-example/src/main/resources/files/favicon.ico b/favicon.ico similarity index 100% rename from firefly-example/src/main/resources/files/favicon.ico rename to favicon.ico diff --git a/firefly-common/.gitignore b/firefly-common/.gitignore deleted file mode 100644 index ea8c4bf7f..000000000 --- a/firefly-common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/firefly-common/pom.xml b/firefly-common/pom.xml deleted file mode 100644 index 2f6ccd092..000000000 --- a/firefly-common/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-common - jar - - firefly-common - http://www.fireflysource.com - - - - org.slf4j - slf4j-api - - - org.javassist - javassist - - - org.jctools - jctools-core - - - - - firefly-common - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.xml - **/*.properties - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/test/resources - false - - **/*.xml - **/*.properties - - - - - diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/AbstractActor.java b/firefly-common/src/main/java/com/fireflysource/common/actor/AbstractActor.java deleted file mode 100644 index b698d042a..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/AbstractActor.java +++ /dev/null @@ -1,272 +0,0 @@ -package com.fireflysource.common.actor; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; - -import java.util.Objects; -import java.util.Queue; -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -abstract public class AbstractActor implements Runnable, Actor, ActorInternalApi { - - private static final LazyLogger log = SystemLogger.create(AbstractActor.class); - - private final String address; - private final Dispatcher dispatcher; - private final Mailbox mailbox; - private final AtomicReference taskState = new AtomicReference<>(TaskState.IDLE); - private ActorState actorState = ActorState.RUNNING; - - public AbstractActor() { - this(UUID.randomUUID().toString(), DispatcherFactory.createDispatcher(), MailboxFactory.createMailbox()); - } - - public AbstractActor(String address, Dispatcher dispatcher, Mailbox mailbox) { - this.address = address; - this.dispatcher = dispatcher; - this.mailbox = mailbox; - } - - @Override - public String getAddress() { - return address; - } - - @Override - public boolean offer(T message) { - if (mailbox.offerUserMessage(message)) { - dispatch(); - return true; - } else { - return false; - } - } - - @Override - public void pause() { - sendSystemMessage(SystemMessage.PAUSE); - } - - @Override - public void resume() { - sendSystemMessage(SystemMessage.RESUME); - } - - @Override - public void shutdown() { - sendSystemMessage(SystemMessage.SHUTDOWN); - } - - @Override - public void restart() { - sendSystemMessage(SystemMessage.RESTART); - } - - @Override - public ActorState getActorState() { - return actorState; - } - - @Override - public void run() { - while (true) { - boolean systemMailboxEmpty = handleSystemMessages(); - - if (actorState == ActorState.PAUSE) { - break; - } - - boolean userMailboxEmpty = handleUserMessages(); - - if (systemMailboxEmpty && userMailboxEmpty) { - break; - } - } - - dispatchNext(); - } - - private void dispatchNext() { - taskState.set(TaskState.IDLE); - switch (actorState) { - case SHUTDOWN: - case RUNNING: - if (mailbox.hasSystemMessage() || mailbox.hasUserMessage()) { - dispatch(); - } - break; - case PAUSE: - if (mailbox.hasSystemMessage()) { - dispatch(); - } - break; - } - } - - private boolean handleUserMessages() { - boolean empty; - T message = mailbox.pollUserMessage(); - if (message != null) { - switch (actorState) { - case RUNNING: - handleMessage(message); - break; - case SHUTDOWN: - handleDiscardMessage(message); - break; - } - empty = false; - } else { - empty = true; - } - return empty; - } - - protected void pauseInMessageProcessThread() { - if (actorState == ActorState.RUNNING) { - actorState = ActorState.PAUSE; - } - } - - private boolean handleSystemMessages() { - boolean empty; - SystemMessage systemMessage = mailbox.pollSystemMessage(); - if (systemMessage != null) { - switch (systemMessage) { - case PAUSE: - if (actorState == ActorState.RUNNING) { - actorState = ActorState.PAUSE; - } - break; - case RESUME: - if (actorState == ActorState.PAUSE) { - actorState = ActorState.RUNNING; - } - break; - case SHUTDOWN: - actorState = ActorState.SHUTDOWN; - break; - case RESTART: - if (actorState == ActorState.SHUTDOWN) { - actorState = ActorState.RUNNING; - } - break; - } - empty = false; - } else { - empty = true; - } - return empty; - } - - private void sendSystemMessage(SystemMessage message) { - if (mailbox.offerSystemMessage(message)) { - dispatch(); - } - } - - private void dispatch() { - if (taskState.compareAndSet(TaskState.IDLE, TaskState.BUSY)) { - dispatcher.dispatch(this); - } - } - - private void handleMessage(T message) { - try { - onReceive(message); - } catch (Exception e) { - log.error("on receive exception. address: " + getAddress(), e); - } - } - - private void handleDiscardMessage(T message) { - try { - onDiscard(message); - } catch (Exception e) { - log.error("on discard exception. address: " + getAddress(), e); - } - } - - abstract public void onReceive(T message); - - public void onDiscard(T message) { - - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractActor that = (AbstractActor) o; - return address.equals(that.address); - } - - @Override - public int hashCode() { - return Objects.hash(address); - } - - enum TaskState { - IDLE, BUSY - } - - public enum SystemMessage { - PAUSE, RESUME, SHUTDOWN, RESTART - } - - public static class DispatcherImpl implements Dispatcher { - private final Executor executor; - - public DispatcherImpl(Executor executor) { - this.executor = executor; - } - - @Override - public void dispatch(Runnable runnable) { - executor.execute(runnable); - } - } - - public static class MailboxImpl implements Mailbox { - private final Queue userMessageQueue; - private final Queue systemMessageQueue; - - public MailboxImpl(Queue userMessageQueue, Queue systemMessageQueue) { - this.userMessageQueue = userMessageQueue; - this.systemMessageQueue = systemMessageQueue; - } - - @Override - public AbstractActor.SystemMessage pollSystemMessage() { - return systemMessageQueue.poll(); - } - - @Override - public boolean offerSystemMessage(AbstractActor.SystemMessage systemMessage) { - return systemMessageQueue.offer(systemMessage); - } - - @Override - public boolean hasSystemMessage() { - return systemMessageQueue.peek() != null; - } - - @Override - public T pollUserMessage() { - return userMessageQueue.poll(); - } - - @Override - public boolean offerUserMessage(T userMessage) { - return userMessageQueue.offer(userMessage); - } - - @Override - public boolean hasUserMessage() { - return userMessageQueue.peek() != null; - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/AbstractAsyncActor.java b/firefly-common/src/main/java/com/fireflysource/common/actor/AbstractAsyncActor.java deleted file mode 100644 index cd170c17b..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/AbstractAsyncActor.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.common.actor; - -import com.fireflysource.common.sys.Result; - -import java.util.concurrent.CompletableFuture; - -abstract public class AbstractAsyncActor extends AbstractActor { - - public AbstractAsyncActor() { - super(); - } - - public AbstractAsyncActor(String address, Dispatcher dispatcher, Mailbox mailbox) { - super(address, dispatcher, mailbox); - } - - @Override - public void onReceive(T message) { - pauseInMessageProcessThread(); - onReceiveAsync(message).handle((result, throwable) -> { - resume(); - return Result.DONE; - }); - } - - abstract public CompletableFuture onReceiveAsync(T message); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/Actor.java b/firefly-common/src/main/java/com/fireflysource/common/actor/Actor.java deleted file mode 100644 index 131fb9613..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/Actor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.common.actor; - -/** - * The actor interface. - * - * @param The actor message type. - */ -public interface Actor { - - /** - * Get actor id. - * - * @return The actor id. - */ - String getAddress(); - - /** - * Offer message to this actor's mailbox. - * - * @param message The message. - * @return If true, offer message success. - */ - boolean offer(T message); - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/ActorInternalApi.java b/firefly-common/src/main/java/com/fireflysource/common/actor/ActorInternalApi.java deleted file mode 100644 index 985cd2308..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/ActorInternalApi.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.fireflysource.common.actor; - -/** - * Only call these methods in the receiving message thread. - */ -public interface ActorInternalApi { - - /** - * Pause to receive messages. - */ - void pause(); - - /** - * Resume to receive messages. - */ - void resume(); - - /** - * Shutdown the actor. - */ - void shutdown(); - - /** - * Restart the actor. - */ - void restart(); - - /** - * Get the actor internal state. - * - * @return The actor internal state. - */ - ActorState getActorState(); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/ActorState.java b/firefly-common/src/main/java/com/fireflysource/common/actor/ActorState.java deleted file mode 100644 index a3ad8392d..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/ActorState.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fireflysource.common.actor; - -public enum ActorState { - PAUSE, RUNNING, SHUTDOWN -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/BlockingTask.java b/firefly-common/src/main/java/com/fireflysource/common/actor/BlockingTask.java deleted file mode 100644 index da27f9976..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/BlockingTask.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.fireflysource.common.actor; - -import com.fireflysource.common.func.Callback; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.Result; -import com.fireflysource.common.sys.SystemLogger; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.locks.Lock; - -import static com.fireflysource.common.sys.Result.runCaching; - -public class BlockingTask implements ForkJoinPool.ManagedBlocker { - - private static final LazyLogger log = SystemLogger.create(BlockingTask.class); - - private final Callable callable; - private final Callable> tryCallable; - private T result; - private boolean done = false; - - public BlockingTask(Callable callable, Callable> tryCallable) { - this.callable = callable; - this.tryCallable = tryCallable; - } - - @Override - public boolean block() throws InterruptedException { - try { - result = callable.call(); - } catch (Exception e) { - log.error("run blocking task exception.", e); - } - done = true; - return true; - } - - @Override - public boolean isReleasable() { - try { - Result r = tryCallable.call(); - done = r.isSuccess(); - if (done) { - result = r.getValue(); - } - } catch (Exception e) { - done = false; - } - return done; - } - - public static void runBlockingTask(Callback callback) { - runBlockingTask(() -> { - callback.call(); - return null; - }); - } - - public static Result runBlockingTask(Callable callable) { - return runBlockingTask(callable, null); - } - - public static Result runBlockingTask(Callable callable, Callable> tryCallable) { - return runCaching(() -> { - BlockingTask task = new BlockingTask<>(callable, tryCallable); - ForkJoinPool.managedBlock(task); - return task.result; - }); - } - - public static void sleep(long millisecond) { - runBlockingTask(() -> Thread.sleep(millisecond)); - } - - public static T blockingTake(final BlockingQueue queue) { - Result result = runBlockingTask(queue::take, () -> { - T t = queue.poll(); - return new Result<>(t != null, t, null); - }); - return result.getValue(); - } - - public static T blockingLock(final Lock lock, Callable callable) { - Result result = runBlockingTask(() -> { - try { - lock.lock(); - return callable.call(); - } finally { - lock.unlock(); - } - }, () -> { - boolean success = lock.tryLock(); - if (success) { - try { - return new Result<>(true, callable.call(), null); - } finally { - lock.unlock(); - } - } else { - return new Result<>(false, null, null); - } - }); - return result.getValue(); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/Dispatcher.java b/firefly-common/src/main/java/com/fireflysource/common/actor/Dispatcher.java deleted file mode 100644 index ebd328aad..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/Dispatcher.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.common.actor; - -/** - * The actor dispatcher. - */ -public interface Dispatcher { - - /** - * Dispatch the message process task. - * - * @param runnable The message process task. - */ - void dispatch(Runnable runnable); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/DispatcherFactory.java b/firefly-common/src/main/java/com/fireflysource/common/actor/DispatcherFactory.java deleted file mode 100644 index 253edbe76..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/DispatcherFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.fireflysource.common.actor; - -import com.fireflysource.common.coroutine.CoroutineDispatchers; - -import java.util.concurrent.Executor; - -public class DispatcherFactory { - - public static Dispatcher createDispatcher() { - return createDispatcher(CoroutineDispatchers.INSTANCE.getComputationThreadPool()); - } - - public static Dispatcher createDispatcher(Executor executor) { - return new AbstractActor.DispatcherImpl(executor); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/Mailbox.java b/firefly-common/src/main/java/com/fireflysource/common/actor/Mailbox.java deleted file mode 100644 index 6ddd4bf84..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/Mailbox.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fireflysource.common.actor; - -/** - * The actor mailbox. - */ -public interface Mailbox { - - S pollSystemMessage(); - - boolean offerSystemMessage(S systemMessage); - - boolean hasSystemMessage(); - - U pollUserMessage(); - - boolean offerUserMessage(U userMessage); - - boolean hasUserMessage(); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/actor/MailboxFactory.java b/firefly-common/src/main/java/com/fireflysource/common/actor/MailboxFactory.java deleted file mode 100644 index 63479c60e..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/actor/MailboxFactory.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.common.actor; - -import org.jctools.queues.MpscLinkedQueue; -import org.jctools.queues.SpscLinkedQueue; - -import java.util.Queue; - -abstract public class MailboxFactory { - - public static Mailbox createMailbox() { - return new AbstractActor.MailboxImpl<>(new MpscLinkedQueue<>(), new SpscLinkedQueue<>()); - } - - public static Mailbox createMailbox(Queue userMessageQueue, Queue systemMessageQueue) { - return new AbstractActor.MailboxImpl<>(userMessageQueue, systemMessageQueue); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/AbstractProxyFactory.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/AbstractProxyFactory.java deleted file mode 100644 index 0b8e74405..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/AbstractProxyFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fireflysource.common.bytecode; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public abstract class AbstractProxyFactory implements ProxyFactory { - - static final IdentityHashMap, String> primitiveWrapMap = new IdentityHashMap<>(); - public static ClassLoader classLoader; - - static { - primitiveWrapMap.put(short.class, Short.class.getCanonicalName()); - primitiveWrapMap.put(byte.class, Byte.class.getCanonicalName()); - primitiveWrapMap.put(int.class, Integer.class.getCanonicalName()); - primitiveWrapMap.put(char.class, Character.class.getCanonicalName()); - primitiveWrapMap.put(float.class, Float.class.getCanonicalName()); - primitiveWrapMap.put(double.class, Double.class.getCanonicalName()); - primitiveWrapMap.put(long.class, Long.class.getCanonicalName()); - primitiveWrapMap.put(boolean.class, Boolean.class.getCanonicalName()); - - classLoader = Thread.currentThread().getContextClassLoader(); - } - - protected final Map methodCache = new ConcurrentHashMap<>(); - protected final Map fieldCache = new ConcurrentHashMap<>(); - protected final Map, ArrayProxy> arrayCache = new ConcurrentHashMap<>(); - - @Override - public MethodProxy getMethodProxy(Method method) { - return methodCache.computeIfAbsent(method, this::createMethodProxy); - } - - abstract protected MethodProxy createMethodProxy(Method method); - - @Override - public ArrayProxy getArrayProxy(Class clazz) { - return arrayCache.computeIfAbsent(clazz, this::createArrayProxy); - } - - abstract protected ArrayProxy createArrayProxy(Class clazz); - - @Override - public FieldProxy getFieldProxy(Field field) { - return fieldCache.computeIfAbsent(field, this::createFieldProxy); - } - - abstract protected FieldProxy createFieldProxy(Field field); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ArrayProxy.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/ArrayProxy.java deleted file mode 100644 index d2ef3cfc8..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ArrayProxy.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fireflysource.common.bytecode; - -/** - * @author Pengtao Qiu - */ -public interface ArrayProxy { - int size(Object array); - - Object get(Object array, int index); - - void set(Object array, int index, Object value); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ClassProxy.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/ClassProxy.java deleted file mode 100644 index 06a0f8022..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ClassProxy.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.fireflysource.common.bytecode; - -@FunctionalInterface -public interface ClassProxy { - Object intercept(MethodProxy handler, Object originalInstance, Object[] args); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ClassProxyFactory.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/ClassProxyFactory.java deleted file mode 100644 index 7e4a1e76e..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ClassProxyFactory.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.fireflysource.common.bytecode; - -public interface ClassProxyFactory { - T createProxy(T instance, ClassProxy proxy, MethodFilter methodFilter) throws Throwable; -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/CompilerUtils.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/CompilerUtils.java deleted file mode 100644 index 5a03280bc..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/CompilerUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.fireflysource.common.bytecode; - -import javax.tools.*; -import javax.tools.JavaCompiler.CompilationTask; -import javax.tools.JavaFileObject.Kind; -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class CompilerUtils { - - public static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - private static final Map outputJavaFile = new ConcurrentHashMap<>(); - private static final Map> classCache = new ConcurrentHashMap<>(); - private static ClassLoader classLoader = new CompilerClassLoader(CompilerUtils.class.getClassLoader()); - - public static Class compileSource(String completeClassName, String source) throws IOException { - boolean result; - try (JavaFileManager fileManager = getStringSourceJavaFileManager(compiler, - null, null, StandardCharsets.UTF_8)) { - CompilationTask task = compiler.getTask(null, fileManager, - null, null, null, - Collections.singletonList(new JavaSourceFromString(completeClassName, source))); - result = task.call(); - } - - if (!result) - return null; - - return getClassByName(completeClassName); - } - - public static Class getClassByName(String name) { - return classCache.computeIfAbsent(name, CompilerUtils::getClass); - } - - private static Class getClass(String name) { - try { - return Class.forName(name, false, classLoader); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } - } - - public static JavaFileManager getStringSourceJavaFileManager(JavaCompiler compiler, DiagnosticListener diagnosticListener, Locale locale, Charset charset) { - - return new ForwardingJavaFileManager(compiler.getStandardFileManager(diagnosticListener, locale, charset)) { - @Override - public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { - JavaFileObject jfo = new ByteJavaObject(className, kind); - outputJavaFile.put(className, jfo); - return jfo; - } - }; - } - - private static URI toURI(String name) { - try { - return new URI(name); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - - public static class JavaSourceFromString extends SimpleJavaFileObject { - /** - * The source code of this "file". - */ - final String code; - - /** - * Constructs a new JavaSourceFromString. - * - * @param name the name of the compilation unit represented by this file - * object - * @param code the source code for the compilation unit represented by - * this file object - */ - public JavaSourceFromString(String name, String code) { - super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); - this.code = code; - } - - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) { - return code; - } - } - - private static class ByteJavaObject extends SimpleJavaFileObject { - - private ByteArrayOutputStream byteArrayOutputStream; - - public ByteJavaObject(String name, Kind kind) { - super(toURI(name), kind); - } - - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IllegalStateException, UnsupportedOperationException { - throw new UnsupportedOperationException(); - } - - @Override - public InputStream openInputStream() throws IllegalStateException, UnsupportedOperationException { - return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); - } - - @Override - public OutputStream openOutputStream() throws IllegalStateException, UnsupportedOperationException { - return byteArrayOutputStream = new ByteArrayOutputStream(); - } - } - - public static class CompilerClassLoader extends ClassLoader { - - public CompilerClassLoader(ClassLoader classLoader) { - super(classLoader); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - JavaFileObject jfo = outputJavaFile.get(name); - if (jfo != null) { - byte[] bytes = ((ByteJavaObject) jfo).byteArrayOutputStream.toByteArray(); - outputJavaFile.remove(name); - return defineClass(name, bytes, 0, bytes.length); - } - return super.findClass(name); - } - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/FieldProxy.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/FieldProxy.java deleted file mode 100644 index e21e5e5f9..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/FieldProxy.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.common.bytecode; - -import java.lang.reflect.Field; - -/** - * @author Pengtao Qiu - */ -public interface FieldProxy { - Field field(); - - Object get(Object obj); - - void set(Object obj, Object value); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavaReflectionProxyFactory.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavaReflectionProxyFactory.java deleted file mode 100644 index 362cc26d0..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavaReflectionProxyFactory.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.fireflysource.common.bytecode; - -import com.fireflysource.common.string.StringUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.UUID; - -/** - * @author Pengtao Qiu - */ -public class JavaReflectionProxyFactory extends AbstractProxyFactory { - - public static final JavaReflectionProxyFactory INSTANCE = new JavaReflectionProxyFactory(); - - private JavaReflectionProxyFactory() { - - } - - protected FieldProxy createFieldProxy(Field field) { - try { - String packageName = "com.firefly.utils"; - String className = "FieldReflectionProxy" + UUID.randomUUID().toString().replace("-", ""); - String completeClassName = packageName + "." + className; - - String value = ""; - Class fieldClazz = field.getType(); - if (fieldClazz.isPrimitive()) { - value += StringUtils.replace("(({})value).{}Value()", primitiveWrapMap.get(fieldClazz), fieldClazz.getCanonicalName()); - } else { - value += StringUtils.replace("({})value", fieldClazz.getCanonicalName()); - } - String source = "package " + packageName + ";\n" - + "import " + Field.class.getCanonicalName() + ";\n" - + "public class " + className + " implements " + FieldProxy.class.getCanonicalName() + " {\n" - + "private Field field;\n" - + "public " + className + "(Field field){\n" - + "\tthis.field = field;\n" - + "}\n\n" - + "public Field field(){return field;}\n" - + "public Object get(Object obj){\n" - + "\treturn " + StringUtils.replace("(({})obj).{};\n", field.getDeclaringClass().getCanonicalName(), field.getName()) - + "}\n\n" - - + "public void set(Object obj, Object value){\n" - + StringUtils.replace("\t(({})obj).{} = ", field.getDeclaringClass().getCanonicalName(), field.getName()) - + value + ";\n" - + "}\n" - + "}"; - - Class fieldProxyClass = CompilerUtils.compileSource(completeClassName, source); - if (fieldProxyClass == null) { - return null; - } else { - return (FieldProxy) fieldProxyClass.getConstructor(Field.class).newInstance(field); - } - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } - - protected MethodProxy createMethodProxy(Method method) { - try { - String packageName = "com.firefly.utils"; - String className = "MethodReflectionProxy" + UUID.randomUUID().toString().replace("-", ""); - String completeClassName = packageName + "." + className; - - Class[] paramClazz = method.getParameterTypes(); - String returnCode = ""; - if (!method.getReturnType().equals(Void.TYPE)) { - returnCode += "return "; - } - - returnCode += "((" + method.getDeclaringClass().getCanonicalName() + ")obj)." + method.getName() + "("; - if (paramClazz.length > 0) { - int max = paramClazz.length - 1; - for (int i = 0; ; i++) { - Class param = paramClazz[i]; - if (param.isPrimitive()) { - returnCode += StringUtils.replace("(({})args[{}]).{}Value()", primitiveWrapMap.get(param), i, param.getCanonicalName()); - } else { - returnCode += "(" + param.getCanonicalName() + ")args[" + i + "]"; - } - - if (i == max) - break; - - returnCode += ","; - } - } - returnCode += ");"; - - if (method.getReturnType().equals(Void.TYPE)) { - returnCode += "\n\treturn null;"; - } - - String source = "package " + packageName + ";\n" - + "import " + Method.class.getCanonicalName() + ";\n" - + "public class " + className + " implements " + MethodProxy.class.getCanonicalName() + " {\n" - + "private Method method;\n" - + "public " + className + "(Method method){\n" - + "\tthis.method = method;\n" - + "}\n\n" - + "public Method method(){return method;}\n\n" - + "public Object invoke(Object obj, Object[] args){\n" - + "\tif(args == null || args.length != " + paramClazz.length + ")\n" - + "\t\tthrow new IllegalArgumentException(\"arguments error\");\n\n" - + "\t" + returnCode + "\n" - + "}\n" - + "}"; - - Class methodProxyClass = CompilerUtils.compileSource(completeClassName, source); - if (methodProxyClass == null) - return null; - - return (MethodProxy) methodProxyClass.getConstructor(Method.class).newInstance(method); - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } - - protected ArrayProxy createArrayProxy(Class clazz) { - try { - String packageName = "com.firefly.utils"; - String className = "ArrayReflectionProxy" + UUID.randomUUID().toString().replace("-", ""); - String completeClassName = packageName + "." + className; - - Class componentType = clazz.getComponentType(); - String v; - if (componentType.isPrimitive()) { - v = StringUtils.replace("(({})value).{}Value()", primitiveWrapMap.get(componentType), componentType.getCanonicalName()); - } else { - v = "(" + clazz.getComponentType().getCanonicalName() + ")value;\n"; - } - - String source = "package " + packageName + ";\n" - + "public class " + className + " implements " + ArrayProxy.class.getCanonicalName() + " {\n" - + "@Override\n" - + "public int size(Object array){\n" - + "\treturn ((" + clazz.getCanonicalName() + ")array).length;\n" - + "}\n\n" - - + "@Override\n" - + "public Object get(Object array, int index){\n" - + "\treturn ((" + clazz.getCanonicalName() + ")array)[index];\n" - + "}\n\n" - - + "@Override\n" - + "public void set(Object array, int index, Object value){\n" - + "\t((" + clazz.getCanonicalName() + ")array)[index] = " + v + ";" - + "}\n\n" - + "}"; - - Class arrayProxyClazz = CompilerUtils.compileSource(completeClassName, source); - if (arrayProxyClazz == null) - return null; - - return (ArrayProxy) arrayProxyClazz.getConstructor().newInstance(); - } catch (Throwable e) { - throw new IllegalStateException(e); - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistClassProxyFactory.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistClassProxyFactory.java deleted file mode 100644 index a92e74a3a..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistClassProxyFactory.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.fireflysource.common.bytecode; - -import javassist.*; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static com.fireflysource.common.string.StringUtils.replace; - -public class JavassistClassProxyFactory implements ClassProxyFactory { - - public static final JavassistClassProxyFactory INSTANCE = new JavassistClassProxyFactory(); - - private JavassistClassProxyFactory() { - } - - @SuppressWarnings("unchecked") - @Override - public T createProxy(T instance, ClassProxy proxy, MethodFilter methodFilter) throws Throwable { - Class clazz = instance.getClass(); - Method[] methods = Arrays.stream(clazz.getMethods()).filter(m -> filterMethods(methodFilter, m)).toArray(Method[]::new); - if (methods.length == 0) { - return instance; - } - - ClassPool classPool = ClassPool.getDefault(); - classPool.insertClassPath(new ClassClassPath(ClassProxyFactory.class)); - CtClass cc = buildClass(classPool, clazz); - buildPrivateFields(clazz, cc); - buildConstructor(classPool, clazz, cc); - List methodCodes = buildMethodCodes(methods); - for (String str : methodCodes) { - cc.addMethod(CtMethod.make(str, cc)); - } - - MethodProxy[] methodProxies = getMethodProxies(methods); - return (T) JavassistUtils - .getClass(cc) - .getConstructor(ClassProxy.class, clazz, MethodProxy[].class) - .newInstance(proxy, instance, methodProxies); - } - - - private List buildMethodCodes(Method[] methods) { - return IntStream - .range(0, methods.length) - .boxed() - .map(index -> buildMethodCode(methods[index], index)) - .collect(Collectors.toList()); - } - - private String buildMethodCode(Method m, Integer index) { - Class[] parameters = m.getParameterTypes(); - return buildMethodSignatureLine(m, parameters) + "{\n" + - convertParametersToObjectArray(parameters) + - buildInvokeInterceptMethodAndReturnLine(m, index) + - "}"; - } - - private MethodProxy[] getMethodProxies(Method[] methods) { - return Arrays.stream(methods) - .map(JavassistReflectionProxyFactory.INSTANCE::getMethodProxy) - .toArray(MethodProxy[]::new); - } - - private String buildMethodSignatureLine(Method m, Class[] parameters) { - String t = "public {} {} ({})"; - return replace(t, m.getReturnType().getCanonicalName(), m.getName(), buildParameters(parameters)); - } - - private String convertParametersToObjectArray(Class[] parameters) { - if (parameters == null || parameters.length == 0) { - return "Object[] args = new Object[0];\n"; - } else { - return "Object[] args = new Object[]{" + - buildParameterObjectArray(parameters) + - "};\n"; - } - } - - private String buildInvokeInterceptMethodAndReturnLine(Method m, Integer index) { - if (!m.getReturnType().equals(void.class)) { - if (m.getReturnType().isPrimitive()) { - String t = "\t{} ret = (({})classProxy.intercept(methodProxies[{}], originalInstance, args)).{}Value();\n" + - "\treturn ret;\n"; - return replace(t, - m.getReturnType().getCanonicalName(), - AbstractProxyFactory.primitiveWrapMap.get(m.getReturnType()), - index, - m.getReturnType().getCanonicalName()); - } else { - String t = "\t{} ret = ({})classProxy.intercept(methodProxies[{}], originalInstance, args);\n" + - "\treturn ret;\n"; - return replace(t, - m.getReturnType().getCanonicalName(), - m.getReturnType().getCanonicalName(), - index); - } - } else { - String t = "\tclassProxy.intercept(methodProxies[{}], originalInstance, args);\n"; - return replace(t, index); - } - } - - - private String buildParameterObjectArray(Class[] parameters) { - return IntStream.range(0, parameters.length) - .boxed() - .map(index -> convertTypeToObject(parameters, index)) - .collect(Collectors.joining(",")); - } - - private String convertTypeToObject(Class[] parameters, Integer index) { - final Class parameter = parameters[index]; - String objectParam; - if (parameter.isPrimitive()) { - String t = "(Object){}.valueOf(arg{})"; - objectParam = replace(t, AbstractProxyFactory.primitiveWrapMap.get(parameters[index]), index); - } else { - objectParam = "(Object)arg" + index; - } - return objectParam; - } - - private String buildParameters(Class[] parameters) { - if (parameters == null || parameters.length == 0) { - return ""; - } - return IntStream - .range(0, parameters.length) - .boxed() - .map(index -> parameters[index].getCanonicalName() + " arg" + index) - .collect(Collectors.joining(",")); - } - - private boolean filterMethods(MethodFilter filter, Method m) { - return !m.getDeclaringClass().equals(Object.class) - && !Modifier.isFinal(m.getModifiers()) - && !Modifier.isStatic(m.getModifiers()) - && !Modifier.isNative(m.getModifiers()) - && Optional.ofNullable(filter).map(f -> f.accept(m)).orElse(true); - } - - private CtClass buildClass(ClassPool classPool, Class clazz) throws NotFoundException, CannotCompileException { - String className = "com.fireflysource.common.bytecode.ClassProxy" + UUID.randomUUID().toString().replace("-", ""); - CtClass cc = classPool.makeClass(className); - cc.setSuperclass(classPool.get(clazz.getName())); - return cc; - } - - private void buildPrivateFields(Class clazz, CtClass cc) throws CannotCompileException { - cc.addField(CtField.make("private " + ClassProxy.class.getCanonicalName() + " classProxy;", cc)); - cc.addField(CtField.make("private " + clazz.getCanonicalName() + " originalInstance;", cc)); - cc.addField(CtField.make("private " + MethodProxy[].class.getCanonicalName() + " methodProxies;", cc)); - } - - private void buildConstructor(ClassPool classPool, Class clazz, CtClass cc) throws CannotCompileException, NotFoundException { - CtConstructor empty = new CtConstructor(null, cc); - empty.setBody("{}"); - cc.addConstructor(empty); - - CtConstructor constructor = new CtConstructor(new CtClass[]{ - classPool.get(ClassProxy.class.getName()), - classPool.get(clazz.getName()), - classPool.get(MethodProxy[].class.getName()) - }, cc); - String bodyTemplate = "{" - + "this.classProxy = ({})$1;" - + "this.originalInstance = ({})$2;" - + "this.methodProxies = ({})$3;" - + "}"; - String body = replace(bodyTemplate, - ClassProxy.class.getCanonicalName(), clazz.getCanonicalName(), MethodProxy[].class.getCanonicalName()); - constructor.setBody(body); - cc.addConstructor(constructor); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistReflectionProxyFactory.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistReflectionProxyFactory.java deleted file mode 100644 index 3e8c9c4b2..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistReflectionProxyFactory.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.fireflysource.common.bytecode; - -import com.fireflysource.common.string.StringUtils; -import javassist.*; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.UUID; - -/** - * @author Pengtao Qiu - */ -public class JavassistReflectionProxyFactory extends AbstractProxyFactory { - - public static final JavassistReflectionProxyFactory INSTANCE = new JavassistReflectionProxyFactory(); - - private JavassistReflectionProxyFactory() { - - } - - protected ArrayProxy createArrayProxy(Class clazz) { - try { - ClassPool classPool = ClassPool.getDefault(); - classPool.insertClassPath(new ClassClassPath(ArrayProxy.class)); - - CtClass cc = classPool.makeClass("com.fireflysource.common.bytecode.ArrayField" + UUID.randomUUID().toString().replace("-", "")); - cc.addInterface(classPool.get(ArrayProxy.class.getName())); - - cc.addMethod(CtMethod.make(createArraySizeCode(clazz), cc)); - cc.addMethod(CtMethod.make(createArrayGetCode(clazz), cc)); - cc.addMethod(CtMethod.make(createArraySetCode(clazz), cc)); - - return (ArrayProxy) JavassistUtils.getClass(cc).getConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - private String createArraySetCode(Class clazz) { - StringBuilder code = new StringBuilder("public void set(Object array, int index, Object value){\n"); - - Class componentType = clazz.getComponentType(); - if (componentType.isPrimitive()) { - String t = "\t(({})array)[index] = (({})value).{}Value();\n"; - code.append(StringUtils.replace(t, - clazz.getCanonicalName(), - primitiveWrapMap.get(componentType), - componentType.getCanonicalName())); - } else { - String t = "\t(({})array)[index] = ({})value;\n"; - code.append(StringUtils.replace(t, clazz.getCanonicalName(), componentType.getCanonicalName())); - } - - code.append("}"); - return code.toString(); - } - - private String createArrayGetCode(Class clazz) { - StringBuilder code = new StringBuilder("public Object get(Object array, int index){\n"); - - Class componentType = clazz.getComponentType(); - if (componentType.isPrimitive()) { - String t = "\treturn (Object){}.valueOf((({})array)[index]);\n"; - code.append(StringUtils.replace(t, primitiveWrapMap.get(componentType), clazz.getCanonicalName())); - } else { - String t = "\treturn (({})array)[index];\n"; - code.append(StringUtils.replace(t, clazz.getCanonicalName())); - } - - code.append("}"); - return code.toString(); - } - - private String createArraySizeCode(Class clazz) { - String t = "public int size(Object array){\n" + - "\treturn (({})array).length;\n" + - "}"; - return StringUtils.replace(t, clazz.getCanonicalName()); - } - - protected FieldProxy createFieldProxy(Field field) { - try { - ClassPool classPool = ClassPool.getDefault(); - classPool.insertClassPath(new ClassClassPath(FieldProxy.class)); - - CtClass cc = classPool.makeClass("com.fireflysource.common.bytecode.ProxyField" + UUID.randomUUID().toString().replace("-", "")); - cc.addInterface(classPool.get(FieldProxy.class.getName())); - cc.addField(CtField.make("private java.lang.reflect.Field field;", cc)); - - CtConstructor constructor = new CtConstructor(new CtClass[]{classPool.get(Field.class.getName())}, cc); - constructor.setBody("{this.field = (java.lang.reflect.Field)$1;}"); - cc.addConstructor(constructor); - - cc.addMethod(CtMethod.make("public java.lang.reflect.Field field(){return field;}", cc)); - cc.addMethod(CtMethod.make(createFieldGetterMethodCode(field), cc)); - cc.addMethod(CtMethod.make(createFieldSetterMethodCode(field), cc)); - - return (FieldProxy) JavassistUtils.getClass(cc).getConstructor(Field.class).newInstance(field); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - private String createFieldGetterMethodCode(Field field) { - Class fieldClazz = field.getType(); - StringBuilder code = new StringBuilder("public Object get(Object obj){\n"); - - if (fieldClazz.isPrimitive()) { - String t = "\treturn (Object){}.valueOf( (({})obj).{} );\n"; - code.append(StringUtils.replace(t, - primitiveWrapMap.get(fieldClazz), - field.getDeclaringClass().getCanonicalName(), - field.getName())); - } else { - String t = "\treturn (({})obj).{};\n"; - code.append(StringUtils.replace(t, - field.getDeclaringClass().getCanonicalName(), - field.getName())); - } - - code.append("}"); - return code.toString(); - } - - private String createFieldSetterMethodCode(Field field) { - Class fieldClazz = field.getType(); - StringBuilder code = new StringBuilder("public void set(Object obj, Object value){\n"); - - if (fieldClazz.isPrimitive()) { - String t = "\t(({})obj).{} = (({})value).{}Value();\n"; - code.append(StringUtils.replace(t, - field.getDeclaringClass().getCanonicalName(), field.getName(), - primitiveWrapMap.get(fieldClazz), fieldClazz.getCanonicalName())); - } else { - String t = "\t(({})obj).{} = ({})value;\n"; - code.append(StringUtils.replace(t, - field.getDeclaringClass().getCanonicalName(), field.getName(), - fieldClazz.getCanonicalName())); - } - - code.append("}"); - return code.toString(); - } - - protected MethodProxy createMethodProxy(Method method) { - try { - ClassPool classPool = ClassPool.getDefault(); - classPool.insertClassPath(new ClassClassPath(MethodProxy.class)); - - CtClass cc = classPool.makeClass("com.fireflysource.common.bytecode.ProxyMethod" + UUID.randomUUID().toString().replace("-", "")); - - cc.addInterface(classPool.get(MethodProxy.class.getName())); - cc.addField(CtField.make("private java.lang.reflect.Method method;", cc)); - - CtConstructor constructor = new CtConstructor(new CtClass[]{classPool.get(Method.class.getName())}, cc); - constructor.setBody("{this.method = (java.lang.reflect.Method)$1;}"); - cc.addConstructor(constructor); - - cc.addMethod(CtMethod.make("public java.lang.reflect.Method method(){return method;}", cc)); - cc.addMethod(CtMethod.make(createInvokeMethodCode(method), cc)); - - return (MethodProxy) JavassistUtils.getClass(cc).getConstructor(Method.class).newInstance(method); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - private String createInvokeMethodCode(Method method) { - Class[] paramClazz = method.getParameterTypes(); - StringBuilder code = new StringBuilder(); - - code.append("public Object invoke(Object obj, Object[] args){\n "); - if (paramClazz.length > 0) { - String t = "\tif(args == null || args.length != {})\n" + - "\t\tthrow new IllegalArgumentException(\"arguments error\");\n\n"; - code.append(StringUtils.replace(t, paramClazz.length)); - } - if (method.getReturnType().equals(Void.TYPE)) { - code.append('\t').append(createMethodCall(method)).append(";\n") - .append("\treturn null;\n"); - } else { - code.append("\treturn "); - if (method.getReturnType().isPrimitive()) { - code.append(StringUtils.replace("(Object){}.valueOf(", primitiveWrapMap.get(method.getReturnType()))) - .append(createMethodCall(method)) - .append(");\n"); - } else { - code.append(createMethodCall(method)).append(";\n"); - } - } - - code.append('}'); - return code.toString(); - } - - private String createMethodCall(Method method) { - Class[] paramClazz = method.getParameterTypes(); - StringBuilder code = new StringBuilder(); - - if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) { - code.append(method.getDeclaringClass().getCanonicalName()); - } else { - code.append(StringUtils.replace("(({})obj)", method.getDeclaringClass().getCanonicalName())); - } - - code.append('.').append(method.getName()).append('('); - if (paramClazz.length > 0) { - int max = paramClazz.length - 1; - for (int i = 0; ; i++) { - Class param = paramClazz[i]; - if (param.isPrimitive()) { - code.append(StringUtils.replace("(({})args[{}]).{}Value()", primitiveWrapMap.get(param), i, param.getCanonicalName())); - } else { - code.append(StringUtils.replace("({})args[{}]", param.getCanonicalName(), i)); - } - - if (i == max) { - break; - } - code.append(", "); - } - } - code.append(')'); - return code.toString(); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistUtils.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistUtils.java deleted file mode 100644 index 94ea86cc7..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/JavassistUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.common.bytecode; - -import com.fireflysource.common.sys.JavaVersion; -import javassist.CtClass; - -public class JavassistUtils { - - public static Class getClass(CtClass cc) throws Exception { - Class clazz; - if (JavaVersion.VERSION.getPlatform() < 9) { - clazz = cc.toClass(Thread.currentThread().getContextClassLoader(), null); - } else { - clazz = cc.toClass(JavassistUtils.class); - } - return clazz; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/MethodFilter.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/MethodFilter.java deleted file mode 100644 index df368a622..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/MethodFilter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.common.bytecode; - -import java.lang.reflect.Method; - -@FunctionalInterface -public interface MethodFilter { - boolean accept(Method method); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/MethodProxy.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/MethodProxy.java deleted file mode 100644 index 9139f0abd..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/MethodProxy.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fireflysource.common.bytecode; - -import java.lang.reflect.Method; - -/** - * @author Pengtao Qiu - */ -public interface MethodProxy { - Method method(); - - /** - * Executes this method - * - * @param obj The instance of object that contains this method - * @param args The parameters of this method - * @return Return value of this method - */ - Object invoke(Object obj, Object... args); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ProxyFactory.java b/firefly-common/src/main/java/com/fireflysource/common/bytecode/ProxyFactory.java deleted file mode 100644 index e4d2e335a..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/bytecode/ProxyFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.common.bytecode; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * @author Pengtao Qiu - */ -public interface ProxyFactory { - ArrayProxy getArrayProxy(Class clazz); - - FieldProxy getFieldProxy(Field field); - - MethodProxy getMethodProxy(Method method); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Base64.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Base64.java deleted file mode 100644 index b26edf02b..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Base64.java +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; - -/** - * Provides Base64 encoding and decoding as defined by RFC 2045. - * - *

    - * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. - *

    - *

    - * The class can be parameterized in the following manner with various constructors: - *

    - *
      - *
    • URL-safe mode: Default off.
    • - *
    • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of - * 4 in the encoded data. - *
    • Line separator: Default is CRLF ("\r\n")
    • - *
    - *

    - * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes. - *

    - *

    - * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only - * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, - * UTF-8, etc). - *

    - *

    - * This class is thread-safe. - *

    - * - * @version $Id: Base64.java 1635986 2014-11-01 16:27:52Z tn $ - * @see RFC 2045 - * @since 1.0 - */ -public class Base64 extends BaseNCodec { - - /** - * BASE32 characters are 6 bits in length. - * They are formed by taking a block of 3 octets to form a 24-bit string, - * which is converted into 4 BASE64 characters. - */ - private static final int BITS_PER_ENCODED_BYTE = 6; - private static final int BYTES_PER_UNENCODED_BLOCK = 3; - private static final int BYTES_PER_ENCODED_BLOCK = 4; - - /** - * Chunk separator per RFC 2045 section 2.1. - * - *

    - * N.B. The next major release may break compatibility and make this field private. - *

    - * - * @see RFC 2045 section 2.1 - */ - static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; - - /** - * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" - * equivalents as specified in Table 1 of RFC 2045. - *

    - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] STANDARD_ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / - * changed to - and _ to make the encoded Base64 results more URL-SAFE. - * This table is only used when the Base64's mode is set to URL-SAFE. - */ - private static final byte[] URL_SAFE_ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' - }; - - /** - * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified - * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 - * alphabet but fall within the bounds of the array are translated to -1. - *

    - * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both - * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). - *

    - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - */ - private static final byte[] DECODE_TABLE = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - }; - - /** - * Base64 uses 6-bit fields. - */ - /** - * Mask used to extract 6 bits, used when encoding - */ - private static final int MASK_6BITS = 0x3f; - - // The static final fields above are used for the original static byte[] methods on Base64. - // The private member fields below are used with the new streaming approach, which requires - // some state be preserved between calls of encode() and decode(). - - /** - * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able - * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch - * between the two modes. - */ - private final byte[] encodeTable; - - // Only one decode table currently; keep for consistency with Base32 code - private final byte[] decodeTable = DECODE_TABLE; - - /** - * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. - */ - private final byte[] lineSeparator; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * decodeSize = 3 + lineSeparator.length; - */ - private final int decodeSize; - - /** - * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. - * encodeSize = 4 + lineSeparator.length; - */ - private final int encodeSize; - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

    - * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. - *

    - * - *

    - * When decoding all variants are supported. - *

    - */ - public Base64() { - this(0); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. - *

    - * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. - *

    - * - *

    - * When decoding all variants are supported. - *

    - * - * @param urlSafe if true, URL-safe encoding is used. In most cases this should be set to - * false. - * @since 1.4 - */ - public Base64(final boolean urlSafe) { - this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

    - * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

    - *

    - * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

    - *

    - * When decoding all variants are supported. - *

    - * - * @param lineLength Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @since 1.4 - */ - public Base64(final int lineLength) { - this(lineLength, CHUNK_SEPARATOR); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

    - * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

    - *

    - * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

    - *

    - * When decoding all variants are supported. - *

    - * - * @param lineLength Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator Each line of encoded data will end with this sequence of bytes. - * @throws IllegalArgumentException Thrown when the provided lineSeparator included some base64 characters. - * @since 1.4 - */ - public Base64(final int lineLength, final byte[] lineSeparator) { - this(lineLength, lineSeparator, false); - } - - /** - * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. - *

    - * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. - *

    - *

    - * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. - *

    - *

    - * When decoding all variants are supported. - *

    - * - * @param lineLength Each line of encoded data will be at most of the given length (rounded down to nearest multiple of - * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when - * decoding. - * @param lineSeparator Each line of encoded data will end with this sequence of bytes. - * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode - * operations. Decoding seamlessly handles both modes. - * Note: no padding is added when using the URL-safe alphabet. - * @throws IllegalArgumentException The provided lineSeparator included some base64 characters. That's not going to work! - * @since 1.4 - */ - public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { - super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, - lineLength, - lineSeparator == null ? 0 : lineSeparator.length); - // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 - // @see test case Base64Test.testConstructors() - if (lineSeparator != null) { - if (containsAlphabetOrPad(lineSeparator)) { - final String sep = new String(lineSeparator, StandardCharsets.UTF_8); - throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); - } - if (lineLength > 0) { // null line-sep forces no chunking rather than throwing IAE - this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; - this.lineSeparator = new byte[lineSeparator.length]; - System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - } else { - this.encodeSize = BYTES_PER_ENCODED_BLOCK; - this.lineSeparator = null; - } - this.decodeSize = this.encodeSize - 1; - this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; - } - - /** - * Returns our current encode mode. True if we're URL-SAFE, false otherwise. - * - * @return true if we're in URL-SAFE mode, false otherwise. - * @since 1.4 - */ - public boolean isUrlSafe() { - return this.encodeTable == URL_SAFE_ENCODE_TABLE; - } - - /** - *

    - * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with - * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last - * remaining bytes (if not multiple of 3). - *

    - *

    Note: no padding is added when encoding using the URL-safe alphabet.

    - *

    - * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - *

    - * - * @param in byte[] array of binary data to base64 encode. - * @param inPos Position to start reading data from. - * @param inAvail Amount of bytes available from input for encoding. - * @param context the context to be used - */ - @Override - void encode(final byte[] in, int inPos, final int inAvail, final Context context) { - if (context.eof) { - return; - } - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - context.eof = true; - if (0 == context.modulus && lineLength == 0) { - return; // no leftovers to process and not using chunking - } - final byte[] buffer = ensureBufferSize(encodeSize, context); - final int savedPos = context.pos; - switch (context.modulus) { // 0-2 - case 0: // nothing to do here - break; - case 1: // 8 bits = 6 + 2 - // top 6 bits: - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; - // remaining 2: - buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[context.pos++] = pad; - buffer[context.pos++] = pad; - } - break; - - case 2: // 16 bits = 6 + 6 + 4 - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[context.pos++] = pad; - } - break; - default: - throw new IllegalStateException("Impossible modulus " + context.modulus); - } - context.currentLinePos += context.pos - savedPos; // keep track of current line position - // if currentPos == 0 we are at the start of a line, so don't add CRLF - if (lineLength > 0 && context.currentLinePos > 0) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(encodeSize, context); - context.modulus = (context.modulus + 1) % BYTES_PER_UNENCODED_BLOCK; - int b = in[inPos++]; - if (b < 0) { - b += 256; - } - context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE - if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS]; - buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; - context.currentLinePos += BYTES_PER_ENCODED_BLOCK; - if (lineLength > 0 && lineLength <= context.currentLinePos) { - System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); - context.pos += lineSeparator.length; - context.currentLinePos = 0; - } - } - } - } - } - - /** - *

    - * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once - * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" - * call is not necessary when decoding, but it doesn't hurt, either. - *

    - *

    - * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are - * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, - * garbage-out philosophy: it will not check the provided data for validity. - *

    - *

    - * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - *

    - * - * @param in byte[] array of ascii data to base64 decode. - * @param inPos Position to start reading data from. - * @param inAvail Amount of bytes available from input for encoding. - * @param context the context to be used - */ - @Override - void decode(final byte[] in, int inPos, final int inAvail, final Context context) { - if (context.eof) { - return; - } - if (inAvail < 0) { - context.eof = true; - } - for (int i = 0; i < inAvail; i++) { - final byte[] buffer = ensureBufferSize(decodeSize, context); - final byte b = in[inPos++]; - if (b == pad) { - // We're done. - context.eof = true; - break; - } else { - if (b >= 0 && b < DECODE_TABLE.length) { - final int result = DECODE_TABLE[b]; - if (result >= 0) { - context.modulus = (context.modulus + 1) % BYTES_PER_ENCODED_BLOCK; - context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; - if (context.modulus == 0) { - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); - } - } - } - } - } - - // Two forms of EOF as far as base64 decoder is concerned: actual - // EOF (-1) and first time '=' character is encountered in stream. - // This approach makes the '=' padding characters completely optional. - if (context.eof && context.modulus != 0) { - final byte[] buffer = ensureBufferSize(decodeSize, context); - - // We have some spare bits remaining - // Output all whole multiples of 8 bits and ignore the rest - switch (context.modulus) { -// case 0 : // impossible, as excluded above - case 1: // 6 bits - ignore entirely - // TODO not currently tested; perhaps it is impossible? - break; - case 2: // 12 bits = 8 + 4 - context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits - buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); - break; - case 3: // 18 bits = 8 + 8 + 2 - context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits - buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); - buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); - break; - default: - throw new IllegalStateException("Impossible modulus " + context.modulus); - } - } - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet byte array to test - * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0. - */ - @Deprecated - public static boolean isArrayByteBase64(final byte[] arrayOctet) { - return isBase64(arrayOctet); - } - - /** - * Returns whether or not the octet is in the base 64 alphabet. - * - * @param octet The value to test - * @return true if the value is defined in the the base 64 alphabet, false otherwise. - * @since 1.4 - */ - public static boolean isBase64(final byte octet) { - return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); - } - - /** - * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param base64 String to test - * @return true if all characters in the String are valid characters in the Base64 alphabet or if - * the String is empty; false, otherwise - * @since 1.5 - */ - public static boolean isBase64(final String base64) { - return isBase64(base64.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet byte array to test - * @return true if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - * @since 1.5 - */ - public static boolean isBase64(final byte[] arrayOctet) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { - return false; - } - } - return true; - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * @param binaryData binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - */ - public static byte[] encodeBase64(final byte[] binaryData) { - return encodeBase64(binaryData, false); - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - *

    - * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to - * single-line non-chunking (commons-codec-1.5). - * - * @param binaryData binary data to encode - * @return String containing Base64 characters. - * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). - */ - public static String encodeBase64String(final byte[] binaryData) { - return new String(encodeBase64(binaryData, false), StandardCharsets.UTF_8); - } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * Note: no padding is added. - * - * @param binaryData binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - * @since 1.4 - */ - public static byte[] encodeBase64URLSafe(final byte[] binaryData) { - return encodeBase64(binaryData, false, true); - } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * Note: no padding is added. - * - * @param binaryData binary data to encode - * @return String containing Base64 characters - * @since 1.4 - */ - public static String encodeBase64URLSafeString(final byte[] binaryData) { - return new String(encodeBase64(binaryData, false, true), StandardCharsets.UTF_8); - } - - /** - * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks - * - * @param binaryData binary data to encode - * @return Base64 characters chunked in 76 character blocks - */ - public static byte[] encodeBase64Chunked(final byte[] binaryData) { - return encodeBase64(binaryData, true); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData Array containing binary data to encode. - * @param isChunked if true this encoder will chunk the base64 output into 76 character blocks - * @return Base64-encoded data. - * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { - return encodeBase64(binaryData, isChunked, false); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData Array containing binary data to encode. - * @param isChunked if true this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe if true this encoder will emit - and _ instead of the usual + and / characters. - * Note: no padding is added when encoding using the URL-safe alphabet. - * @return Base64-encoded data. - * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - * @since 1.4 - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { - return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData Array containing binary data to encode. - * @param isChunked if true this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe if true this encoder will emit - and _ instead of the usual + and / characters. - * Note: no padding is added when encoding using the URL-safe alphabet. - * @param maxResultSize The maximum result size to accept. - * @return Base64-encoded data. - * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than maxResultSize - * @since 1.4 - */ - public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, - final boolean urlSafe, final int maxResultSize) { - if (binaryData == null || binaryData.length == 0) { - return binaryData; - } - - // Create this so can use the super-class method - // Also ensures that the same roundings are performed by the ctor and the code - final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); - final long len = b64.getEncodedLength(binaryData); - if (len > maxResultSize) { - throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + - len + - ") than the specified maximum size of " + - maxResultSize); - } - - return b64.encode(binaryData); - } - - /** - * Decodes a Base64 String into octets. - *

    - * Note: this method seamlessly handles data encoded in URL-safe or normal mode. - *

    - * - * @param base64String String containing Base64 data - * @return Array containing decoded data. - * @since 1.4 - */ - public static byte[] decodeBase64(final String base64String) { - return new Base64().decode(base64String); - } - - /** - * Decodes Base64 data into octets. - *

    - * Note: this method seamlessly handles data encoded in URL-safe or normal mode. - *

    - * - * @param base64Data Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(final byte[] base64Data) { - return new Base64().decode(base64Data); - } - - // Implementation of the Encoder Interface - - // Implementation of integer encoding used for crypto - - /** - * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. - * - * @param pArray a byte array containing base64 character data - * @return A BigInteger - * @since 1.4 - */ - public static BigInteger decodeInteger(final byte[] pArray) { - return new BigInteger(1, decodeBase64(pArray)); - } - - /** - * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. - * - * @param bigInt a BigInteger - * @return A byte array containing base64 character data - * @throws NullPointerException if null is passed in - * @since 1.4 - */ - public static byte[] encodeInteger(final BigInteger bigInt) { - if (bigInt == null) { - throw new NullPointerException("encodeInteger called with null parameter"); - } - return encodeBase64(toIntegerBytes(bigInt), false); - } - - /** - * Returns a byte-array representation of a BigInteger without sign bit. - * - * @param bigInt BigInteger to be converted - * @return a byte array representation of the BigInteger parameter - */ - static byte[] toIntegerBytes(final BigInteger bigInt) { - int bitlen = bigInt.bitLength(); - // round bitlen - bitlen = ((bitlen + 7) >> 3) << 3; - final byte[] bigBytes = bigInt.toByteArray(); - - if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { - return bigBytes; - } - // set up params for copying everything but sign bit - int startSrc = 0; - int len = bigBytes.length; - - // if bigInt is exactly byte-aligned, just skip signbit in copy - if ((bigInt.bitLength() % 8) == 0) { - startSrc = 1; - len--; - } - final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec - final byte[] resizedBytes = new byte[bitlen / 8]; - System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); - return resizedBytes; - } - - /** - * Returns whether or not the octet is in the Base64 alphabet. - * - * @param octet The value to test - * @return true if the value is defined in the the Base64 alphabet false otherwise. - */ - @Override - protected boolean isInAlphabet(final byte octet) { - return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Base64Utils.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Base64Utils.java deleted file mode 100644 index 72f2edefd..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Base64Utils.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.fireflysource.common.codec.base64; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -public abstract class Base64Utils { - - private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final Base64Delegate delegate = new CommonsCodecBase64Delegate(); - - /** - * Base64-encode the given byte array. - * - * @param src the original byte array (may be {@code null}) - * @return the encoded byte array (or {@code null} if the input was - * {@code null}) - */ - public static byte[] encode(byte[] src) { - return delegate.encode(src); - } - - /** - * Base64-decode the given byte array. - * - * @param src the encoded byte array (may be {@code null}) - * @return the original byte array (or {@code null} if the input was - * {@code null}) - */ - public static byte[] decode(byte[] src) { - return delegate.decode(src); - } - - /** - * Base64-encode the given byte array using the RFC 4868 - * "URL and Filename Safe Alphabet". - * - * @param src the original byte array (may be {@code null}) - * @return the encoded byte array (or {@code null} if the input was - * {@code null}) - */ - public static byte[] encodeUrlSafe(byte[] src) { - return delegate.encodeUrlSafe(src); - } - - /** - * Base64-decode the given byte array using the RFC 4868 - * "URL and Filename Safe Alphabet". - * - * @param src the encoded byte array (may be {@code null}) - * @return the original byte array (or {@code null} if the input was - * {@code null}) - */ - public static byte[] decodeUrlSafe(byte[] src) { - return delegate.decodeUrlSafe(src); - } - - /** - * Base64-encode the given byte array to a String. - * - * @param src the original byte array (may be {@code null}) - * @return the encoded byte array as a UTF-8 String (or {@code null} if the - * input was {@code null}) - */ - public static String encodeToString(byte[] src) { - if (src == null) { - return null; - } - if (src.length == 0) { - return ""; - } - - return new String(delegate.encode(src), DEFAULT_CHARSET); - } - - /** - * Base64-decode the given byte array from an UTF-8 String. - * - * @param src the encoded UTF-8 String (may be {@code null}) - * @return the original byte array (or {@code null} if the input was - * {@code null}) - */ - public static byte[] decodeFromString(String src) { - if (src == null) { - return null; - } - if (src.length() == 0) { - return new byte[0]; - } - - return delegate.decode(src.getBytes(DEFAULT_CHARSET)); - } - - /** - * Base64-encode the given byte array to a String using the RFC 4868 - * "URL and Filename Safe Alphabet". - * - * @param src the original byte array (may be {@code null}) - * @return the encoded byte array as a UTF-8 String (or {@code null} if the - * input was {@code null}) - */ - public static String encodeToUrlSafeString(byte[] src) { - return new String(delegate.encodeUrlSafe(src), DEFAULT_CHARSET); - } - - /** - * Base64-decode the given byte array from an UTF-8 String using the RFC - * 4868 "URL and Filename Safe Alphabet". - * - * @param src the encoded UTF-8 String (may be {@code null}) - * @return the original byte array (or {@code null} if the input was - * {@code null}) - */ - public static byte[] decodeFromUrlSafeString(String src) { - return delegate.decodeUrlSafe(src.getBytes(DEFAULT_CHARSET)); - } - - interface Base64Delegate { - - byte[] encode(byte[] src); - - byte[] decode(byte[] src); - - byte[] encodeUrlSafe(byte[] src); - - byte[] decodeUrlSafe(byte[] src); - } - - static class CommonsCodecBase64Delegate implements Base64Delegate { - - private final Base64 base64 = new Base64(); - - private final Base64 base64UrlSafe = new Base64(0, null, true); - - @Override - public byte[] encode(byte[] src) { - return this.base64.encode(src); - } - - @Override - public byte[] decode(byte[] src) { - return this.base64.decode(src); - } - - @Override - public byte[] encodeUrlSafe(byte[] src) { - return this.base64UrlSafe.encode(src); - } - - @Override - public byte[] decodeUrlSafe(byte[] src) { - return this.base64UrlSafe.decode(src); - } - - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BaseNCodec.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BaseNCodec.java deleted file mode 100644 index d08828ed5..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BaseNCodec.java +++ /dev/null @@ -1,514 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -/** - * Abstract superclass for Base-N encoders and decoders. - * - *

    - * This class is thread-safe. - *

    - * - * @version $Id: BaseNCodec.java 1634404 2014-10-26 23:06:10Z ggregory $ - */ -public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { - - /** - * Holds thread context so classes can be thread-safe. - *

    - * This class is not itself thread-safe; each thread must allocate its own copy. - * - * @since 1.7 - */ - static class Context { - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - int ibitWorkArea; - - /** - * Place holder for the bytes we're dealing with for our based logic. - * Bitwise operations store and extract the encoding or decoding from this variable. - */ - long lbitWorkArea; - - /** - * Buffer for streaming. - */ - byte[] buffer; - - /** - * Position where next character should be written in the buffer. - */ - int pos; - - /** - * Position where next character should be read from the buffer. - */ - int readPos; - - /** - * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, - * and must be thrown away. - */ - boolean eof; - - /** - * Variable tracks how many characters have been written to the current line. Only used when encoding. We use - * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). - */ - int currentLinePos; - - /** - * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This - * variable helps track that. - */ - int modulus; - - Context() { - } - - /** - * Returns a String useful for debugging (especially within a debugger.) - * - * @return a String useful for debugging. - */ - @SuppressWarnings("boxing") // OK to ignore boxing here - @Override - public String toString() { - return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " + - "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), Arrays.toString(buffer), - currentLinePos, eof, ibitWorkArea, lbitWorkArea, modulus, pos, readPos); - } - } - - /** - * EOF - * - * @since 1.7 - */ - static final int EOF = -1; - - /** - * MIME chunk size per RFC 2045 section 6.8. - * - *

    - * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - *

    - * - * @see RFC 2045 section 6.8 - */ - public static final int MIME_CHUNK_SIZE = 76; - - /** - * PEM chunk size per RFC 1421 section 4.3.2.4. - * - *

    - * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. - *

    - * - * @see RFC 1421 section 4.3.2.4 - */ - public static final int PEM_CHUNK_SIZE = 64; - - private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; - - /** - * Defines the default buffer size - currently {@value} - * - must be large enough for at least one encoded block+separator - */ - private static final int DEFAULT_BUFFER_SIZE = 8192; - - /** - * Mask used to extract 8 bits, used in decoding bytes - */ - protected static final int MASK_8BITS = 0xff; - - /** - * Byte used to pad output. - */ - protected static final byte PAD_DEFAULT = '='; // Allow static access to default - - /** - * @deprecated Use {@link #pad}. Will be removed in 2.0. - */ - @Deprecated - protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later - - protected final byte pad; // instance variable just in case it needs to vary later - - /** - * Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 - */ - private final int unencodedBlockSize; - - /** - * Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 - */ - private final int encodedBlockSize; - - /** - * Chunksize for encoding. Not used when decoding. - * A value of zero or less implies no chunking of the encoded data. - * Rounded down to nearest multiple of encodedBlockSize. - */ - protected final int lineLength; - - /** - * Size of chunk separator. Not used unless {@link #lineLength} > 0. - */ - private final int chunkSeparatorLength; - - /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} - * If chunkSeparatorLength is zero, then chunking is disabled. - * - * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) - * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) - * @param lineLength if > 0, use chunking with a length lineLength - * @param chunkSeparatorLength the chunk separator length, if relevant - */ - protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, - final int lineLength, final int chunkSeparatorLength) { - this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); - } - - /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} - * If chunkSeparatorLength is zero, then chunking is disabled. - * - * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) - * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) - * @param lineLength if > 0, use chunking with a length lineLength - * @param chunkSeparatorLength the chunk separator length, if relevant - * @param pad byte used as padding byte. - */ - protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, - final int lineLength, final int chunkSeparatorLength, final byte pad) { - this.unencodedBlockSize = unencodedBlockSize; - this.encodedBlockSize = encodedBlockSize; - final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; - this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; - this.chunkSeparatorLength = chunkSeparatorLength; - - this.pad = pad; - } - - /** - * Returns true if this object has buffered data for reading. - * - * @param context the context to be used - * @return true if there is data still available for reading. - */ - boolean hasData(final Context context) { // package protected for access from I/O streams - return context.buffer != null; - } - - /** - * Returns the amount of buffered data available for reading. - * - * @param context the context to be used - * @return The amount of buffered data available for reading. - */ - int available(final Context context) { // package protected for access from I/O streams - return context.buffer != null ? context.pos - context.readPos : 0; - } - - /** - * Get the default buffer size. Can be overridden. - * - * @return {@link #DEFAULT_BUFFER_SIZE} - */ - protected int getDefaultBufferSize() { - return DEFAULT_BUFFER_SIZE; - } - - /** - * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. - * - * @param context the context to be used - */ - private byte[] resizeBuffer(final Context context) { - if (context.buffer == null) { - context.buffer = new byte[getDefaultBufferSize()]; - context.pos = 0; - context.readPos = 0; - } else { - final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; - System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); - context.buffer = b; - } - return context.buffer; - } - - /** - * Ensure that the buffer has room for size bytes - * - * @param size minimum spare space required - * @param context the context to be used - * @return the buffer - */ - protected byte[] ensureBufferSize(final int size, final Context context) { - if ((context.buffer == null) || (context.buffer.length < context.pos + size)) { - return resizeBuffer(context); - } - return context.buffer; - } - - /** - * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail - * bytes. Returns how many bytes were actually extracted. - *

    - * Package protected for access from I/O streams. - * - * @param b byte[] array to extract the buffered data into. - * @param bPos position in byte[] array to start extraction at. - * @param bAvail amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). - * @param context the context to be used - * @return The number of bytes successfully extracted into the provided byte[] array. - */ - int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { - if (context.buffer != null) { - final int len = Math.min(available(context), bAvail); - System.arraycopy(context.buffer, context.readPos, b, bPos, len); - context.readPos += len; - if (context.readPos >= context.pos) { - context.buffer = null; // so hasData() will return false, and this method can return -1 - } - return len; - } - return context.eof ? EOF : 0; - } - - /** - * Checks if a byte value is whitespace or not. - * Whitespace is taken to mean: space, tab, CR, LF - * - * @param byteToCheck the byte to check - * @return true if byte is whitespace, false otherwise - */ - protected static boolean isWhiteSpace(final byte byteToCheck) { - switch (byteToCheck) { - case ' ': - case '\n': - case '\r': - case '\t': - return true; - default: - return false; - } - } - - /** - * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of - * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[]. - * - * @param obj Object to encode - * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied. - * @throws EncoderException if the parameter supplied is not of type byte[] - */ - @Override - public Object encode(final Object obj) throws EncoderException { - if (!(obj instanceof byte[])) { - throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]"); - } - return encode((byte[]) obj); - } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. - * Uses UTF8 encoding. - * - * @param pArray a byte array containing binary data - * @return A String containing only Base-N character data - */ - public String encodeToString(final byte[] pArray) { - return new String(encode(pArray), StandardCharsets.UTF_8); - } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. - * Uses UTF8 encoding. - * - * @param pArray a byte array containing binary data - * @return String containing only character data in the appropriate alphabet. - */ - public String encodeAsString(final byte[] pArray) { - return new String(encode(pArray), StandardCharsets.UTF_8); - } - - /** - * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of - * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String. - * - * @param obj Object to decode - * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String - * supplied. - * @throws DecoderException if the parameter supplied is not of type byte[] - */ - @Override - public Object decode(final Object obj) throws DecoderException { - if (obj instanceof byte[]) { - return decode((byte[]) obj); - } else if (obj instanceof String) { - return decode((String) obj); - } else { - throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String"); - } - } - - /** - * Decodes a String containing characters in the Base-N alphabet. - * - * @param pArray A String containing Base-N character data - * @return a byte array containing binary data - */ - public byte[] decode(final String pArray) { - return decode(pArray.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Decodes a byte[] containing characters in the Base-N alphabet. - * - * @param pArray A byte array containing Base-N character data - * @return a byte array containing binary data - */ - @Override - public byte[] decode(final byte[] pArray) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - final Context context = new Context(); - decode(pArray, 0, pArray.length, context); - decode(pArray, 0, EOF, context); // Notify decoder of EOF. - final byte[] result = new byte[context.pos]; - readResults(result, 0, result.length, context); - return result; - } - - /** - * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. - * - * @param pArray a byte array containing binary data - * @return A byte array containing only the basen alphabetic character data - */ - @Override - public byte[] encode(final byte[] pArray) { - if (pArray == null || pArray.length == 0) { - return pArray; - } - final Context context = new Context(); - encode(pArray, 0, pArray.length, context); - encode(pArray, 0, EOF, context); // Notify encoder of EOF. - final byte[] buf = new byte[context.pos - context.readPos]; - readResults(buf, 0, buf.length, context); - return buf; - } - - // package protected for access from I/O streams - abstract void encode(byte[] pArray, int i, int length, Context context); - - // package protected for access from I/O streams - abstract void decode(byte[] pArray, int i, int length, Context context); - - /** - * Returns whether or not the octet is in the current alphabet. - * Does not allow whitespace or pad. - * - * @param value The value to test - * @return true if the value is defined in the current alphabet, false otherwise. - */ - protected abstract boolean isInAlphabet(byte value); - - /** - * Tests a given byte array to see if it contains only valid characters within the alphabet. - * The method optionally treats whitespace and pad as valid. - * - * @param arrayOctet byte array to test - * @param allowWSPad if true, then whitespace and PAD are also allowed - * @return true if all bytes are valid characters in the alphabet or if the byte array is empty; - * false, otherwise - */ - public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isInAlphabet(arrayOctet[i]) && - (!allowWSPad || (arrayOctet[i] != pad) && !isWhiteSpace(arrayOctet[i]))) { - return false; - } - } - return true; - } - - /** - * Tests a given String to see if it contains only valid characters within the alphabet. - * The method treats whitespace and PAD as valid. - * - * @param basen String to test - * @return true if all characters in the String are valid characters in the alphabet or if - * the String is empty; false, otherwise - * @see #isInAlphabet(byte[], boolean) - */ - public boolean isInAlphabet(final String basen) { - return isInAlphabet(basen.getBytes(StandardCharsets.UTF_8), true); - } - - /** - * Tests a given byte array to see if it contains any characters within the alphabet or PAD. - *

    - * Intended for use in checking line-ending arrays - * - * @param arrayOctet byte array to test - * @return true if any byte is a valid character in the alphabet or PAD; false otherwise - */ - protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { - if (arrayOctet == null) { - return false; - } - for (final byte element : arrayOctet) { - if (pad == element || isInAlphabet(element)) { - return true; - } - } - return false; - } - - /** - * Calculates the amount of space needed to encode the supplied array. - * - * @param pArray byte[] array which will later be encoded - * @return amount of space needed to encoded the supplied array. - * Returns a long since a max-len array will require > Integer.MAX_VALUE - */ - public long getEncodedLength(final byte[] pArray) { - // Calculate non-chunked size - rounded up to allow for padding - // cast to long is needed to avoid possibility of overflow - long len = ((pArray.length + unencodedBlockSize - 1) / unencodedBlockSize) * (long) encodedBlockSize; - if (lineLength > 0) { // We're using chunking - // Round up to nearest multiple - len += ((len + lineLength - 1) / lineLength) * chunkSeparatorLength; - } - return len; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BinaryDecoder.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BinaryDecoder.java deleted file mode 100644 index b6ab96125..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BinaryDecoder.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -/** - * Defines common decoding methods for byte array decoders. - * - * @version $Id: BinaryDecoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface BinaryDecoder extends Decoder { - - /** - * Decodes a byte array and returns the results as a byte array. - * - * @param source A byte array which has been encoded with the appropriate encoder - * @return a byte array that contains decoded content - * @throws DecoderException A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. - */ - byte[] decode(byte[] source) throws DecoderException; -} - diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BinaryEncoder.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BinaryEncoder.java deleted file mode 100644 index 54c9f1ed9..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/BinaryEncoder.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -/** - * Defines common encoding methods for byte array encoders. - * - * @version $Id: BinaryEncoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface BinaryEncoder extends Encoder { - - /** - * Encodes a byte array and return the encoded data as a byte array. - * - * @param source Data to be encoded - * @return A byte array containing the encoded data - * @throws EncoderException thrown if the Encoder encounters a failure condition during the encoding process. - */ - byte[] encode(byte[] source) throws EncoderException; -} - diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Decoder.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Decoder.java deleted file mode 100644 index 96125e2f4..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Decoder.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -/** - * Provides the highest level of abstraction for Decoders. - *

    - * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. - * Allows a user to pass a generic Object to any Decoder implementation in the codec package. - *

    - * One of the two interfaces at the center of the codec package. - * - * @version $Id: Decoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface Decoder { - - /** - * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will - * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a - * {@link ClassCastException} occurs this decode method will throw a DecoderException. - * - * @param source the object to decode - * @return a 'decoded" object - * @throws DecoderException a decoder exception can be thrown for any number of reasons. Some good candidates are that the - * parameter passed to this method is null, a param cannot be cast to the appropriate type for a - * specific encoder. - */ - Object decode(Object source) throws DecoderException; -} - diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/DecoderException.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/DecoderException.java deleted file mode 100644 index 48d3408f9..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/DecoderException.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -/** - * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} - * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. - * - * @version $Id: DecoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ - */ -public class DecoderException extends Exception { - - /** - * Declares the Serial Version Uid. - * - * @see Always Declare Serial Version Uid - */ - private static final long serialVersionUID = 1L; - - /** - * Constructs a new exception with null as its detail message. The cause is not initialized, and may - * subsequently be initialized by a call to {@link #initCause}. - * - * @since 1.4 - */ - public DecoderException() { - super(); - } - - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently - * be initialized by a call to {@link #initCause}. - * - * @param message The detail message which is saved for later retrieval by the {@link #getMessage()} method. - */ - public DecoderException(final String message) { - super(message); - } - - /** - * Constructs a new exception with the specified detail message and cause. - *

    - * Note that the detail message associated with cause is not automatically incorporated into this - * exception's detail message. - * - * @param message The detail message which is saved for later retrieval by the {@link #getMessage()} method. - * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public DecoderException(final String message, final Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new exception with the specified cause and a detail message of (cause==null ? - * null : cause.toString()) (which typically contains the class and detail message of cause). - * This constructor is useful for exceptions that are little more than wrappers for other throwables. - * - * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public DecoderException(final Throwable cause) { - super(cause); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Encoder.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Encoder.java deleted file mode 100644 index 53630da04..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/Encoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -/** - * Provides the highest level of abstraction for Encoders. - *

    - * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this - * common generic interface which allows a user to pass a generic Object to any Encoder implementation - * in the codec package. - * - * @version $Id: Encoder.java 1379145 2012-08-30 21:02:52Z tn $ - */ -public interface Encoder { - - /** - * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be - * byte[] or Strings depending on the implementation used. - * - * @param source An object to encode - * @return An "encoded" Object - * @throws EncoderException An encoder exception is thrown if the encoder experiences a failure condition during the encoding - * process. - */ - Object encode(Object source) throws EncoderException; -} - diff --git a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/EncoderException.java b/firefly-common/src/main/java/com/fireflysource/common/codec/base64/EncoderException.java deleted file mode 100644 index c534fd6ba..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/codec/base64/EncoderException.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fireflysource.common.codec.base64; - -/** - * Thrown when there is a failure condition during the encoding process. This exception is thrown when an - * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, - * characters outside of the expected range. - * - * @version $Id: EncoderException.java 1619948 2014-08-22 22:53:55Z ggregory $ - */ -public class EncoderException extends Exception { - - /** - * Declares the Serial Version Uid. - * - * @see Always Declare Serial Version Uid - */ - private static final long serialVersionUID = 1L; - - /** - * Constructs a new exception with null as its detail message. The cause is not initialized, and may - * subsequently be initialized by a call to {@link #initCause}. - * - * @since 1.4 - */ - public EncoderException() { - super(); - } - - /** - * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently - * be initialized by a call to {@link #initCause}. - * - * @param message a useful message relating to the encoder specific error. - */ - public EncoderException(final String message) { - super(message); - } - - /** - * Constructs a new exception with the specified detail message and cause. - * - *

    - * Note that the detail message associated with cause is not automatically incorporated into this - * exception's detail message. - *

    - * - * @param message The detail message which is saved for later retrieval by the {@link #getMessage()} method. - * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public EncoderException(final String message, final Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new exception with the specified cause and a detail message of (cause==null ? - * null : cause.toString()) (which typically contains the class and detail message of cause). - * This constructor is useful for exceptions that are little more than wrappers for other throwables. - * - * @param cause The cause which is saved for later retrieval by the {@link #getCause()} method. A null - * value is permitted, and indicates that the cause is nonexistent or unknown. - * @since 1.4 - */ - public EncoderException(final Throwable cause) { - super(cause); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/CollectionUtils.java b/firefly-common/src/main/java/com/fireflysource/common/collection/CollectionUtils.java deleted file mode 100644 index ca585f1d2..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/CollectionUtils.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.fireflysource.common.collection; - -import java.util.*; -import java.util.stream.Collectors; - -public class CollectionUtils { - - public static boolean isEmpty(Object[] array) { - return array == null || array.length == 0; - } - - public static boolean isEmpty(Map map) { - return (map == null || map.isEmpty()); - } - - public static boolean isEmpty(Collection collection) { - return (collection == null || collection.isEmpty()); - } - - public static Set intersect(Set a, Set b) { - if (isEmpty(a) || isEmpty(b)) { - return new HashSet<>(); - } else { - Set set = new HashSet<>(a); - set.retainAll(b); - return set; - } - } - - public static Set union(Set a, Set b) { - Set set = new HashSet<>(a); - set.addAll(b); - return set; - } - - public static boolean hasIntersection(Set a, Set b) { - if (isEmpty(a) || isEmpty(b)) { - return false; - } - - if (a.size() < b.size()) { - if (a.size() < 8) { - return a.stream().anyMatch(b::contains); - } else { - return a.parallelStream().anyMatch(b::contains); - } - } else { - if (b.size() < 8) { - return b.stream().anyMatch(a::contains); - } else { - return b.parallelStream().anyMatch(a::contains); - } - } - } - - @SafeVarargs - public static Set newHashSet(T... values) { - return Arrays.stream(values).collect(Collectors.toSet()); - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/array/ArrayUtils.java b/firefly-common/src/main/java/com/fireflysource/common/collection/array/ArrayUtils.java deleted file mode 100644 index 9dfc0b79e..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/array/ArrayUtils.java +++ /dev/null @@ -1,1066 +0,0 @@ -package com.fireflysource.common.collection.array; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Utility methods for Array manipulation - */ -@SuppressWarnings("unused") -public class ArrayUtils { - - public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - @SuppressWarnings("rawtypes") - public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - - public static final long[] EMPTY_LONG_ARRAY = new long[0]; - - public static final Long[] EMPTY_LONG_OBJECT_ARRAY = new Long[0]; - - public static final int[] EMPTY_INT_ARRAY = new int[0]; - - public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0]; - - public static final short[] EMPTY_SHORT_ARRAY = new short[0]; - - public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = new Short[0]; - - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = new Byte[0]; - - public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; - - public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = new Double[0]; - - public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; - - public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = new Float[0]; - - public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; - - public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = new Boolean[0]; - - public static final char[] EMPTY_CHAR_ARRAY = new char[0]; - - public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = new Character[0]; - - public static T[] removeFromArray(T[] array, Object item) { - if (item == null || array == null) - return array; - for (int i = array.length; i-- > 0; ) { - if (item.equals(array[i])) { - Class c = array.getClass().getComponentType(); - @SuppressWarnings("unchecked") - T[] na = (T[]) Array.newInstance(c, Array.getLength(array) - 1); - if (i > 0) - System.arraycopy(array, 0, na, 0, i); - if (i + 1 < array.length) - System.arraycopy(array, i + 1, na, i, array.length - (i + 1)); - return na; - } - } - return array; - } - - /** - * Add element to an array - * - * @param array The array to add to (or null) - * @param item The item to add - * @param type The type of the array (in case of null array) - * @param the array entry type - * @return new array with contents of array plus item - */ - public static T[] addToArray(T[] array, T item, Class type) { - if (array == null) { - if (type == null && item != null) { - type = item.getClass(); - } - @SuppressWarnings("unchecked") - T[] na = (T[]) Array.newInstance(type, 1); - na[0] = item; - return na; - } else { - T[] na = Arrays.copyOf(array, array.length + 1); - na[array.length] = item; - return na; - } - } - - /** - * Add element to the start of an array - * - * @param array The array to add to (or null) - * @param item The item to add - * @param type The type of the array (in case of null array) - * @param the array entry type - * @return new array with contents of array plus item - */ - public static T[] prependToArray(T item, T[] array, Class type) { - if (array == null) { - if (type == null && item != null) { - type = item.getClass(); - } - @SuppressWarnings("unchecked") - T[] na = (T[]) Array.newInstance(type, 1); - na[0] = item; - return na; - } else { - Class c = array.getClass().getComponentType(); - @SuppressWarnings("unchecked") - T[] na = (T[]) Array.newInstance(c, Array.getLength(array) + 1); - System.arraycopy(array, 0, na, 1, array.length); - na[0] = item; - return na; - } - } - - /** - * @param array Any array of object - * @param the array entry type - * @return A new modifiable list initialised with the elements from - * array. - */ - public static List asMutableList(E[] array) { - if (array == null || array.length == 0) - return new ArrayList<>(); - return new ArrayList<>(Arrays.asList(array)); - } - - public static T[] removeNulls(T[] array) { - for (T t : array) { - if (t == null) { - List list = new ArrayList<>(); - for (T t2 : array) { - if (t2 != null) { - list.add(t2); - } - } - return list.toArray(Arrays.copyOf(array, list.size())); - } - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Object[] nullToEmpty(Object[] array) { - if (array == null || array.length == 0) { - return EMPTY_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static String[] nullToEmpty(String[] array) { - if (array == null || array.length == 0) { - return EMPTY_STRING_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static long[] nullToEmpty(long[] array) { - if (array == null || array.length == 0) { - return EMPTY_LONG_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static int[] nullToEmpty(int[] array) { - if (array == null || array.length == 0) { - return EMPTY_INT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static short[] nullToEmpty(short[] array) { - if (array == null || array.length == 0) { - return EMPTY_SHORT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static char[] nullToEmpty(char[] array) { - if (array == null || array.length == 0) { - return EMPTY_CHAR_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static byte[] nullToEmpty(byte[] array) { - if (array == null || array.length == 0) { - return EMPTY_BYTE_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static double[] nullToEmpty(double[] array) { - if (array == null || array.length == 0) { - return EMPTY_DOUBLE_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static float[] nullToEmpty(float[] array) { - if (array == null || array.length == 0) { - return EMPTY_FLOAT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static boolean[] nullToEmpty(boolean[] array) { - if (array == null || array.length == 0) { - return EMPTY_BOOLEAN_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Long[] nullToEmpty(Long[] array) { - if (array == null || array.length == 0) { - return EMPTY_LONG_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Integer[] nullToEmpty(Integer[] array) { - if (array == null || array.length == 0) { - return EMPTY_INTEGER_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Short[] nullToEmpty(Short[] array) { - if (array == null || array.length == 0) { - return EMPTY_SHORT_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Character[] nullToEmpty(Character[] array) { - if (array == null || array.length == 0) { - return EMPTY_CHARACTER_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Byte[] nullToEmpty(Byte[] array) { - if (array == null || array.length == 0) { - return EMPTY_BYTE_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Double[] nullToEmpty(Double[] array) { - if (array == null || array.length == 0) { - return EMPTY_DOUBLE_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Float[] nullToEmpty(Float[] array) { - if (array == null || array.length == 0) { - return EMPTY_FLOAT_OBJECT_ARRAY; - } - return array; - } - - /** - *

    Defensive programming technique to change a null - * reference to an empty one.

    - *

    - *

    This method returns an empty array for a null input array.

    - *

    - *

    As a memory optimizing technique an empty array passed in will be overridden with - * the empty public static references in this class.

    - * - * @param array the array to check for null or empty - * @return the same array, public static empty array if null or empty input - * @since 2.5 - */ - public static Boolean[] nullToEmpty(Boolean[] array) { - if (array == null || array.length == 0) { - return EMPTY_BOOLEAN_OBJECT_ARRAY; - } - return array; - } - - // Primitive/Object array converters - // ---------------------------------------------------------------------- - - // Character array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Characters to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Character array, may be null - * @return a char array, null if null array input - * @throws NullPointerException if array content is null - */ - public static char[] toPrimitive(Character[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_CHAR_ARRAY; - } - final char[] result = new char[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Character to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Character array, may be null - * @param valueForNull the value to insert if null found - * @return a char array, null if null array input - */ - public static char[] toPrimitive(Character[] array, char valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_CHAR_ARRAY; - } - final char[] result = new char[array.length]; - for (int i = 0; i < array.length; i++) { - Character b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive chars to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a char array - * @return a Character array, null if null array input - */ - public static Character[] toObject(char[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_CHARACTER_OBJECT_ARRAY; - } - final Character[] result = new Character[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Long array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Longs to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Long array, may be null - * @return a long array, null if null array input - * @throws NullPointerException if array content is null - */ - public static long[] toPrimitive(Long[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_LONG_ARRAY; - } - final long[] result = new long[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Long to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Long array, may be null - * @param valueForNull the value to insert if null found - * @return a long array, null if null array input - */ - public static long[] toPrimitive(Long[] array, long valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_LONG_ARRAY; - } - final long[] result = new long[array.length]; - for (int i = 0; i < array.length; i++) { - Long b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive longs to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a long array - * @return a Long array, null if null array input - */ - public static Long[] toObject(long[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_LONG_OBJECT_ARRAY; - } - final Long[] result = new Long[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Int array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Integers to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Integer array, may be null - * @return an int array, null if null array input - * @throws NullPointerException if array content is null - */ - public static int[] toPrimitive(Integer[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_INT_ARRAY; - } - final int[] result = new int[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Integer to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Integer array, may be null - * @param valueForNull the value to insert if null found - * @return an int array, null if null array input - */ - public static int[] toPrimitive(Integer[] array, int valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_INT_ARRAY; - } - final int[] result = new int[array.length]; - for (int i = 0; i < array.length; i++) { - Integer b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive ints to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array an int array - * @return an Integer array, null if null array input - */ - public static Integer[] toObject(int[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_INTEGER_OBJECT_ARRAY; - } - final Integer[] result = new Integer[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Short array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Shorts to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Short array, may be null - * @return a byte array, null if null array input - * @throws NullPointerException if array content is null - */ - public static short[] toPrimitive(Short[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_SHORT_ARRAY; - } - final short[] result = new short[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Short to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Short array, may be null - * @param valueForNull the value to insert if null found - * @return a byte array, null if null array input - */ - public static short[] toPrimitive(Short[] array, short valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_SHORT_ARRAY; - } - final short[] result = new short[array.length]; - for (int i = 0; i < array.length; i++) { - Short b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive shorts to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a short array - * @return a Short array, null if null array input - */ - public static Short[] toObject(short[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_SHORT_OBJECT_ARRAY; - } - final Short[] result = new Short[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Byte array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Bytes to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Byte array, may be null - * @return a byte array, null if null array input - * @throws NullPointerException if array content is null - */ - public static byte[] toPrimitive(Byte[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_BYTE_ARRAY; - } - final byte[] result = new byte[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Bytes to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Byte array, may be null - * @param valueForNull the value to insert if null found - * @return a byte array, null if null array input - */ - public static byte[] toPrimitive(Byte[] array, byte valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_BYTE_ARRAY; - } - final byte[] result = new byte[array.length]; - for (int i = 0; i < array.length; i++) { - Byte b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive bytes to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a byte array - * @return a Byte array, null if null array input - */ - public static Byte[] toObject(byte[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_BYTE_OBJECT_ARRAY; - } - final Byte[] result = new Byte[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Double array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Doubles to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Double array, may be null - * @return a double array, null if null array input - * @throws NullPointerException if array content is null - */ - public static double[] toPrimitive(Double[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_DOUBLE_ARRAY; - } - final double[] result = new double[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Doubles to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Double array, may be null - * @param valueForNull the value to insert if null found - * @return a double array, null if null array input - */ - public static double[] toPrimitive(Double[] array, double valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_DOUBLE_ARRAY; - } - final double[] result = new double[array.length]; - for (int i = 0; i < array.length; i++) { - Double b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive doubles to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a double array - * @return a Double array, null if null array input - */ - public static Double[] toObject(double[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_DOUBLE_OBJECT_ARRAY; - } - final Double[] result = new Double[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Float array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Floats to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Float array, may be null - * @return a float array, null if null array input - * @throws NullPointerException if array content is null - */ - public static float[] toPrimitive(Float[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_FLOAT_ARRAY; - } - final float[] result = new float[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Floats to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Float array, may be null - * @param valueForNull the value to insert if null found - * @return a float array, null if null array input - */ - public static float[] toPrimitive(Float[] array, float valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_FLOAT_ARRAY; - } - final float[] result = new float[array.length]; - for (int i = 0; i < array.length; i++) { - Float b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive floats to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a float array - * @return a Float array, null if null array input - */ - public static Float[] toObject(float[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_FLOAT_OBJECT_ARRAY; - } - final Float[] result = new Float[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - // Boolean array converters - // ---------------------------------------------------------------------- - - /** - *

    Converts an array of object Booleans to primitives.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Boolean array, may be null - * @return a boolean array, null if null array input - * @throws NullPointerException if array content is null - */ - public static boolean[] toPrimitive(Boolean[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_BOOLEAN_ARRAY; - } - final boolean[] result = new boolean[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - - /** - *

    Converts an array of object Booleans to primitives handling null.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a Boolean array, may be null - * @param valueForNull the value to insert if null found - * @return a boolean array, null if null array input - */ - public static boolean[] toPrimitive(Boolean[] array, boolean valueForNull) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_BOOLEAN_ARRAY; - } - final boolean[] result = new boolean[array.length]; - for (int i = 0; i < array.length; i++) { - Boolean b = array[i]; - result[i] = (b == null ? valueForNull : b); - } - return result; - } - - /** - *

    Converts an array of primitive booleans to objects.

    - *

    - *

    This method returns null for a null input array.

    - * - * @param array a boolean array - * @return a Boolean array, null if null array input - */ - public static Boolean[] toObject(boolean[] array) { - if (array == null) { - return null; - } else if (array.length == 0) { - return EMPTY_BOOLEAN_OBJECT_ARRAY; - } - final Boolean[] result = new Boolean[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = (array[i] ? Boolean.TRUE : Boolean.FALSE); - } - return result; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/list/LazyList.java b/firefly-common/src/main/java/com/fireflysource/common/collection/list/LazyList.java deleted file mode 100644 index 7cf4576ab..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/list/LazyList.java +++ /dev/null @@ -1,385 +0,0 @@ -package com.fireflysource.common.collection.list; - -import java.io.Serializable; -import java.lang.reflect.Array; -import java.util.*; - -/** - * Lazy List creation. - *

    - * A List helper class that attempts to avoid unnecessary List creation. If a - * method needs to create a List to return, but it is expected that this will - * either be empty or frequently contain a single item, then using LazyList will - * avoid additional object creations by using {@link Collections#EMPTY_LIST} or - * {@link Collections#singletonList(Object)} where possible. - *

    - *

    - * LazyList works by passing an opaque representation of the list in and out of - * all the LazyList methods. This opaque object is either null for an empty - * list, an Object for a list with a single entry or an {@link ArrayList} for a - * list of items. - *

    - * Usage - * - *
    - * Object lazylist = null;
    - * while (loopCondition) {
    - * 	Object item = getItem();
    - * 	if (item.isToBeAdded())
    - * 		lazylist = LazyList.add(lazylist, item);
    - * }
    - * return LazyList.getList(lazylist);
    - * 
    - *

    - * An ArrayList of default size is used as the initial LazyList. - * - * @see List - */ -@SuppressWarnings("serial") -public class LazyList implements Cloneable, Serializable { - private static final String[] __EMPTY_STRING_ARRAY = new String[0]; - - private LazyList() { - } - - /** - * Add an item to a LazyList - * - * @param list The list to add to or null if none yet created. - * @param item The item to add. - * @return The lazylist created or added to. - */ - @SuppressWarnings("unchecked") - public static Object add(Object list, Object item) { - if (list == null) { - if (item instanceof List || item == null) { - List l = new ArrayList<>(); - l.add(item); - return l; - } - - return item; - } - - if (list instanceof List) { - ((List) list).add(item); - return list; - } - - List l = new ArrayList<>(); - l.add(list); - l.add(item); - return l; - } - - /** - * Add an item to a LazyList - * - * @param list The list to add to or null if none yet created. - * @param index The index to add the item at. - * @param item The item to add. - * @return The lazylist created or added to. - */ - @SuppressWarnings("unchecked") - public static Object add(Object list, int index, Object item) { - if (list == null) { - if (index > 0 || item instanceof List || item == null) { - List l = new ArrayList<>(); - l.add(index, item); - return l; - } - return item; - } - - if (list instanceof List) { - ((List) list).add(index, item); - return list; - } - - List l = new ArrayList<>(); - l.add(list); - l.add(index, item); - return l; - } - - /** - * Add the contents of a Collection to a LazyList - * - * @param list The list to add to or null if none yet created. - * @param collection The Collection whose contents should be added. - * @return The lazylist created or added to. - */ - public static Object addCollection(Object list, Collection collection) { - Iterator i = collection.iterator(); - while (i.hasNext()) - list = LazyList.add(list, i.next()); - return list; - } - - /** - * Add the contents of an array to a LazyList - * - * @param list The list to add to or null if none yet created. - * @param array The array whose contents should be added. - * @return The lazylist created or added to. - */ - public static Object addArray(Object list, Object[] array) { - for (int i = 0; array != null && i < array.length; i++) - list = LazyList.add(list, array[i]); - return list; - } - - /** - * Ensure the capacity of the underlying list. - * - * @param list the list to grow - * @param initialSize the size to grow to - * @return the new List with new size - */ - public static Object ensureSize(Object list, int initialSize) { - if (list == null) - return new ArrayList(initialSize); - if (list instanceof ArrayList) { - ArrayList ol = (ArrayList) list; - if (ol.size() > initialSize) - return ol; - ArrayList nl = new ArrayList<>(initialSize); - nl.addAll(ol); - return nl; - } - List l = new ArrayList<>(initialSize); - l.add(list); - return l; - } - - public static Object remove(Object list, Object o) { - if (list == null) - return null; - - if (list instanceof List) { - List l = (List) list; - l.remove(o); - if (l.size() == 0) - return null; - return list; - } - - if (list.equals(o)) - return null; - return list; - } - - public static Object remove(Object list, int i) { - if (list == null) - return null; - - if (list instanceof List) { - List l = (List) list; - l.remove(i); - if (l.size() == 0) - return null; - return list; - } - - if (i == 0) - return null; - return list; - } - - /** - * Get the real List from a LazyList. - * - * @param list A LazyList returned from LazyList.add(Object) - * @param the list entry type - * @return The List of added items, which may be an EMPTY_LIST or a - * SingletonList. - */ - public static List getList(Object list) { - return getList(list, false); - } - - /** - * Get the real List from a LazyList. - * - * @param list A LazyList returned from LazyList.add(Object) or null - * @param nullForEmpty If true, null is returned instead of an empty list. - * @param the list entry type - * @return The List of added items, which may be null, an EMPTY_LIST or a - * SingletonList. - */ - @SuppressWarnings("unchecked") - public static List getList(Object list, boolean nullForEmpty) { - if (list == null) { - if (nullForEmpty) - return null; - return Collections.emptyList(); - } - if (list instanceof List) - return (List) list; - - return (List) Collections.singletonList(list); - } - - /** - * Simple utility method to test if List has at least 1 entry. - * - * @param list a LazyList, {@link List} or {@link Object} - * @return true if not-null and is not empty - */ - public static boolean hasEntry(Object list) { - if (list == null) - return false; - if (list instanceof List) - return !((List) list).isEmpty(); - return true; - } - - /** - * Simple utility method to test if List is empty - * - * @param list a LazyList, {@link List} or {@link Object} - * @return true if null or is empty - */ - public static boolean isEmpty(Object list) { - if (list == null) - return true; - if (list instanceof List) - return ((List) list).isEmpty(); - return false; - } - - public static String[] toStringArray(Object list) { - if (list == null) - return __EMPTY_STRING_ARRAY; - - if (list instanceof List) { - List l = (List) list; - String[] a = new String[l.size()]; - for (int i = l.size(); i-- > 0; ) { - Object o = l.get(i); - if (o != null) - a[i] = o.toString(); - } - return a; - } - - return new String[]{list.toString()}; - } - - /** - * Convert a lazylist to an array - * - * @param list The list to convert - * @param clazz The class of the array, which may be a primitive type - * @return array of the lazylist entries passed in - */ - public static Object toArray(Object list, Class clazz) { - if (list == null) - return Array.newInstance(clazz, 0); - - if (list instanceof List) { - List l = (List) list; - if (clazz.isPrimitive()) { - Object a = Array.newInstance(clazz, l.size()); - for (int i = 0; i < l.size(); i++) - Array.set(a, i, l.get(i)); - return a; - } - return l.toArray((Object[]) Array.newInstance(clazz, l.size())); - - } - - Object a = Array.newInstance(clazz, 1); - Array.set(a, 0, list); - return a; - } - - /** - * The size of a lazy List - * - * @param list A LazyList returned from LazyList.add(Object) or null - * @return the size of the list. - */ - public static int size(Object list) { - if (list == null) - return 0; - if (list instanceof List) - return ((List) list).size(); - return 1; - } - - /** - * Get item from the list - * - * @param list A LazyList returned from LazyList.add(Object) or null - * @param i int index - * @param the list entry type - * @return the item from the list. - */ - @SuppressWarnings("unchecked") - public static E get(Object list, int i) { - if (list == null) - throw new IndexOutOfBoundsException(); - - if (list instanceof List) - return (E) ((List) list).get(i); - - if (i == 0) - return (E) list; - - throw new IndexOutOfBoundsException(); - } - - public static boolean contains(Object list, Object item) { - if (list == null) - return false; - - if (list instanceof List) - return ((List) list).contains(item); - - return list.equals(item); - } - - public static Object clone(Object list) { - if (list == null) - return null; - if (list instanceof List) - return new ArrayList((List) list); - return list; - } - - public static String toString(Object list) { - if (list == null) - return "[]"; - if (list instanceof List) - return list.toString(); - return "[" + list + "]"; - } - - @SuppressWarnings("unchecked") - public static Iterator iterator(Object list) { - if (list == null) { - List empty = Collections.emptyList(); - return empty.iterator(); - } - if (list instanceof List) { - return ((List) list).iterator(); - } - List l = getList(list); - return l.iterator(); - } - - @SuppressWarnings("unchecked") - public static ListIterator listIterator(Object list) { - if (list == null) { - List empty = Collections.emptyList(); - return empty.listIterator(); - } - if (list instanceof List) - return ((List) list).listIterator(); - - List l = getList(list); - return l.listIterator(); - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/map/MultiMap.java b/firefly-common/src/main/java/com/fireflysource/common/collection/map/MultiMap.java deleted file mode 100644 index 827ce1490..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/map/MultiMap.java +++ /dev/null @@ -1,320 +0,0 @@ -package com.fireflysource.common.collection.map; - -import java.util.*; - -/** - * A multi valued Map. - * - * @param the entry type for multimap values - */ -public class MultiMap extends HashMap> { - private static final long serialVersionUID = -1127515104096783129L; - - public MultiMap() { - super(); - } - - public MultiMap(Map> map) { - super(map); - } - - public MultiMap(MultiMap map) { - super(map); - } - - /** - * Get multiple values. Single valued entries are converted to singleton - * lists. - * - * @param name The entry key. - * @return Unmodifieable List of values. - */ - public List getValues(String name) { - List vals = super.get(name); - if ((vals == null) || vals.isEmpty()) { - return null; - } - return vals; - } - - /** - * Get a value from a multiple value. If the value is not a multivalue, then - * index 0 retrieves the value or null. - * - * @param name The entry key. - * @param i Index of element to get. - * @return Unmodifieable List of values. - */ - public V getValue(String name, int i) { - List vals = getValues(name); - if (vals == null) { - return null; - } - if (i == 0 && vals.isEmpty()) { - return null; - } - return vals.get(i); - } - - /** - * Get value as String. Single valued items are converted to a String with - * the toString() Object method. Multi valued entries are converted to a - * comma separated List. No quoting of commas within values is performed. - * - * @param name The entry key. - * @return String value. - */ - public String getString(String name) { - List vals = get(name); - if ((vals == null) || (vals.isEmpty())) { - return null; - } - - if (vals.size() == 1) { - // simple form. - return vals.get(0).toString(); - } - - // delimited form - StringBuilder values = new StringBuilder(128); - for (V e : vals) { - if (e != null) { - if (values.length() > 0) - values.append(','); - values.append(e.toString()); - } - } - return values.toString(); - } - - /** - * Put multi valued entry. - * - * @param name The entry key. - * @param value The simple value - * @return The previous value or null. - */ - public List put(String name, V value) { - if (value == null) { - return super.put(name, null); - } - List vals = new ArrayList<>(); - vals.add(value); - return put(name, vals); - } - - /** - * Shorthand version of putAll - * - * @param input the input map - */ - public void putAllValues(Map input) { - for (Entry entry : input.entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } - - /** - * Put multi valued entry. - * - * @param name The entry key. - * @param values The List of multiple values. - * @return The previous value or null. - */ - public List putValues(String name, List values) { - return super.put(name, values); - } - - /** - * Put multi valued entry. - * - * @param name The entry key. - * @param values The array of multiple values. - * @return The previous value or null. - */ - @SafeVarargs - public final List putValues(String name, V... values) { - List list = new ArrayList<>(); - list.addAll(Arrays.asList(values)); - return super.put(name, list); - } - - /** - * Add value to multi valued entry. If the entry is single valued, it is - * converted to the first value of a multi valued entry. - * - * @param name The entry key. - * @param value The entry value. - */ - public void add(String name, V value) { - List lo = get(name); - if (lo == null) { - lo = new ArrayList<>(); - } - lo.add(value); - super.put(name, lo); - } - - /** - * Add values to multi valued entry. If the entry is single valued, it is - * converted to the first value of a multi valued entry. - * - * @param name The entry key. - * @param values The List of multiple values. - */ - public void addValues(String name, List values) { - List lo = get(name); - if (lo == null) { - lo = new ArrayList<>(); - } - lo.addAll(values); - put(name, lo); - } - - /** - * Add values to multi valued entry. If the entry is single valued, it is - * converted to the first value of a multi valued entry. - * - * @param name The entry key. - * @param values The String array of multiple values. - */ - public void addValues(String name, V[] values) { - List lo = get(name); - if (lo == null) { - lo = new ArrayList<>(); - } - lo.addAll(Arrays.asList(values)); - put(name, lo); - } - - /** - * Merge values. - * - * @param map the map to overlay on top of this one, merging together values - * if needed. - * @return true if an existing key was merged with potentially new values, - * false if either no change was made, or there were only new keys. - */ - public boolean addAllValues(MultiMap map) { - boolean merged = false; - - if ((map == null) || (map.isEmpty())) { - // done - return merged; - } - - for (Entry> entry : map.entrySet()) { - String name = entry.getKey(); - List values = entry.getValue(); - - if (this.containsKey(name)) { - merged = true; - } - - this.addValues(name, values); - } - - return merged; - } - - /** - * Remove value. - * - * @param name The entry key. - * @param value The entry value. - * @return true if it was removed. - */ - public boolean removeValue(String name, V value) { - List lo = get(name); - if ((lo == null) || (lo.isEmpty())) { - return false; - } - boolean ret = lo.remove(value); - if (lo.isEmpty()) { - remove(name); - } else { - put(name, lo); - } - return ret; - } - - /** - * Test for a specific single value in the map. - *

    - * NOTE: This is a SLOW operation, and is actively discouraged. - * - * @param value the value to search for - * @return true if contains simple value - */ - public boolean containsSimpleValue(V value) { - for (List vals : values()) { - if ((vals.size() == 1) && vals.contains(value)) { - return true; - } - } - return false; - } - - @Override - public String toString() { - Iterator>> iter = entrySet().iterator(); - StringBuilder sb = new StringBuilder(); - sb.append('{'); - boolean delim = false; - while (iter.hasNext()) { - Entry> e = iter.next(); - if (delim) { - sb.append(", "); - } - String key = e.getKey(); - List vals = e.getValue(); - sb.append(key); - sb.append('='); - if (vals.size() == 1) { - sb.append(vals.get(0)); - } else { - sb.append(vals); - } - delim = true; - } - sb.append('}'); - return sb.toString(); - } - - /** - * @return Map of String arrays - */ - public Map toStringArrayMap() { - HashMap map = new HashMap(size() * 3 / 2) { - - private static final long serialVersionUID = -6129887569971781626L; - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append('{'); - for (String k : super.keySet()) { - if (b.length() > 1) - b.append(','); - b.append(k); - b.append('='); - b.append(Arrays.asList(super.get(k))); - } - - b.append('}'); - return b.toString(); - } - }; - - for (Entry> entry : entrySet()) { - String[] a = null; - if (entry.getValue() != null) { - a = new String[entry.getValue().size()]; - a = entry.getValue().toArray(a); - } - map.put(entry.getKey(), a); - } - return map; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/AbstractTrie.java b/firefly-common/src/main/java/com/fireflysource/common/collection/trie/AbstractTrie.java deleted file mode 100644 index b9ea94f01..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/AbstractTrie.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.common.collection.trie; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -public abstract class AbstractTrie implements Trie { - final boolean _caseInsensitive; - - protected AbstractTrie(boolean insensitive) { - _caseInsensitive = insensitive; - } - - @Override - public boolean put(V v) { - return put(v.toString(), v); - } - - @Override - public V remove(String s) { - V o = get(s); - put(s, null); - return o; - } - - @Override - public V get(String s) { - return get(s, 0, s.length()); - } - - @Override - public V get(ByteBuffer b) { - return get(b, 0, b.remaining()); - } - - @Override - public V getBest(String s) { - return getBest(s, 0, s.length()); - } - - @Override - public V getBest(byte[] b, int offset, int len) { - return getBest(new String(b, offset, len, StandardCharsets.UTF_8)); - } - - @Override - public boolean isCaseInsensitive() { - return _caseInsensitive; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/ArrayTernaryTrie.java b/firefly-common/src/main/java/com/fireflysource/common/collection/trie/ArrayTernaryTrie.java deleted file mode 100644 index c27f65ee5..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/ArrayTernaryTrie.java +++ /dev/null @@ -1,582 +0,0 @@ -package com.fireflysource.common.collection.trie; - -import java.nio.ByteBuffer; -import java.util.*; - -/** - *

    A Ternary Trie String lookup data structure.

    - *

    - * This Trie is of a fixed size and cannot grow (which can be a good thing with the regards to DOS when used as a cache). - *

    - *

    - * The Trie is stored in 3 arrays: - *

    - *
    - *
    char[] tree
    This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension - * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a - * ternary trie of key strings.
    - *
    String[] key
    An array of key values where each element matches a row in the tree array. A non-zero key element - * indicates that the tree row is a complete key rather than an intermediate character of a longer key.
    - *
    V[] value
    An array of values corresponding to the key array
    - *
    - *

    The lookup of a value will iterate through the tree array matching characters. If the equal tree branch follows, - * then the key array looks up to see if this is a complete match. If a match finds then the value array looks up - * to return the matching value. - *

    - *

    - * This Trie may instantiate either as case-sensitive or insensitive. - *

    - *

    This Trie is not Threadsafe and contains no mutual exclusion - * or deliberate memory barriers. It is intended for an ArrayTrie to be - * built by a single thread and then used concurrently by multiple threads - * and not mutated during that access. If concurrent mutations of the - * Trie is required external locks need to be applied. - *

    - * - * @param the Entry type - */ -@SuppressWarnings("unchecked") -public class ArrayTernaryTrie extends AbstractTrie { - - public static final char[] LOWER_CASES = {'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\010', - '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\020', '\021', '\022', '\023', '\024', '\025', - '\026', '\027', '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037', '\040', '\041', '\042', - '\043', '\044', '\045', '\046', '\047', '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057', - '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067', '\070', '\071', '\072', '\073', '\074', - '\075', '\076', '\077', '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147', '\150', '\151', - '\152', '\153', '\154', '\155', '\156', '\157', '\160', '\161', '\162', '\163', '\164', '\165', '\166', - '\167', '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137', '\140', '\141', '\142', '\143', - '\144', '\145', '\146', '\147', '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157', '\160', - '\161', '\162', '\163', '\164', '\165', '\166', '\167', '\170', '\171', '\172', '\173', '\174', '\175', - '\176', '\177'}; - /** - * The Size of a Trie row is the char, and the low, equal and high - * child pointers - */ - private static final int ROW_SIZE = 4; - private static int LO = 1; - private static int EQ = 2; - private static int HI = 3; - /** - * The Trie rows in a single array which allows a lookup of row,character - * to the next row in the Trie. This is actually a 2 dimensional - * array that has been flattened to achieve locality of reference. - */ - private final char[] tree; - - /** - * The key (if any) for a Trie row. - * A row may be a leaf, a node or both in the Trie tree. - */ - private final String[] key; - - /** - * The value (if any) for a Trie row. - * A row may be a leaf, a node or both in the Trie tree. - */ - private final V[] value; - - /** - * The number of rows allocated - */ - private char rows; - - /** - * Create a case-insensitive Trie of default capacity. - */ - public ArrayTernaryTrie() { - this(128); - } - - /** - * Create a Trie of default capacity - * - * @param insensitive true if the Trie is insensitive to the case of the key. - */ - public ArrayTernaryTrie(boolean insensitive) { - this(insensitive, 128); - } - - /** - * Create a case-insensitive Trie - * - * @param capacity The capacity of the Trie, which is in the worst case - * is the total number of characters of all keys stored in the Trie. - * The capacity needed is dependent of the shared prefixes of the keys. - * For example, a capacity of 6 nodes require to store the keys "foo" - * and "bar", but a capacity of only 4 is required to - * store "bar" and "bat". - */ - public ArrayTernaryTrie(int capacity) { - this(true, capacity); - } - - /** - * Create a Trie - * - * @param insensitive true if the Trie is insensitive to the case of the key. - * @param capacity The capacity of the Trie, which is in the worst case - * is the total number of characters of all keys stored in the Trie. - * The capacity needed is dependent of the shared prefixes of the keys. - * For example, a capacity of 6 nodes require to store the keys "foo" - * and "bar", but a capacity of only 4 is required to - * store "bar" and "bat". - */ - public ArrayTernaryTrie(boolean insensitive, int capacity) { - super(insensitive); - value = (V[]) new Object[capacity]; - tree = new char[capacity * ROW_SIZE]; - key = new String[capacity]; - } - - /** - * Copy Trie and change capacity by a factor - * - * @param trie the trie to copy from - * @param factor the factor to grow the capacity by - */ - public ArrayTernaryTrie(ArrayTernaryTrie trie, double factor) { - super(trie.isCaseInsensitive()); - int capacity = (int) (trie.value.length * factor); - rows = trie.rows; - value = Arrays.copyOf(trie.value, capacity); - tree = Arrays.copyOf(trie.tree, capacity * ROW_SIZE); - key = Arrays.copyOf(trie.key, capacity); - } - - public static int hilo(int diff) { - // branchless equivalent to return ((diff<0)?LO:HI); - // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1); - return 1 + (diff | Integer.MAX_VALUE) / (Integer.MAX_VALUE / 2); - } - - @Override - public void clear() { - rows = 0; - Arrays.fill(value, null); - Arrays.fill(tree, (char) 0); - Arrays.fill(key, null); - } - - @Override - public boolean put(String s, V v) { - int t = 0; - int limit = s.length(); - int last; - for (int k = 0; k < limit; k++) { - char c = s.charAt(k); - if (isCaseInsensitive() && c < 128) - c = LOWER_CASES[c]; - - while (true) { - int row = ROW_SIZE * t; - - // Do we need to create the new row? - if (t == rows) { - rows++; - if (rows >= key.length) { - rows--; - return false; - } - tree[row] = c; - } - - char n = tree[row]; - int diff = n - c; - if (diff == 0) - t = tree[last = (row + EQ)]; - else if (diff < 0) - t = tree[last = (row + LO)]; - else - t = tree[last = (row + HI)]; - - // do we need a new row? - if (t == 0) { - t = rows; - tree[last] = (char) t; - } - - if (diff == 0) break; - } - } - - // Do we need to create the new row? - if (t == rows) { - rows++; - if (rows >= key.length) { - rows--; - return false; - } - } - - // Put the key and value - key[t] = v == null ? null : s; - value[t] = v; - - return true; - } - - @Override - public V get(String s, int offset, int len) { - int t = 0; - for (int i = 0; i < len; ) { - char c = s.charAt(offset + i++); - if (isCaseInsensitive() && c < 128) - c = LOWER_CASES[c]; - - while (true) { - int row = ROW_SIZE * t; - char n = tree[row]; - int diff = n - c; - - if (diff == 0) { - t = tree[row + EQ]; - if (t == 0) return null; - break; - } - - t = tree[row + hilo(diff)]; - if (t == 0) return null; - } - } - - return value[t]; - } - - @Override - public V get(ByteBuffer b, int offset, int len) { - int t = 0; - offset += b.position(); - - for (int i = 0; i < len; ) { - byte c = (byte) (b.get(offset + i++) & 0x7f); - if (isCaseInsensitive()) - c = (byte) LOWER_CASES[c]; - - while (true) { - int row = ROW_SIZE * t; - char n = tree[row]; - int diff = n - c; - - if (diff == 0) { - t = tree[row + EQ]; - if (t == 0) return null; - break; - } - - t = tree[row + hilo(diff)]; - if (t == 0) return null; - } - } - return value[t]; - } - - @Override - public V getBest(String s) { - return getBest(0, s, 0, s.length()); - } - - @Override - public V getBest(String s, int offset, int length) { - return getBest(0, s, offset, length); - } - - private V getBest(int t, String s, int offset, int len) { - int node = t; - int end = offset + len; - loop: - while (offset < end) { - char c = s.charAt(offset++); - len--; - if (isCaseInsensitive() && c < 128) - c = LOWER_CASES[c]; - - while (true) { - int row = ROW_SIZE * t; - char n = tree[row]; - int diff = n - c; - - if (diff == 0) { - t = tree[row + EQ]; - if (t == 0) break loop; - - // if this node is a match, recurse to remember - if (key[t] != null) { - node = t; - V better = getBest(t, s, offset, len); - if (better != null) return better; - } - break; - } - - t = tree[row + hilo(diff)]; - if (t == 0) break loop; - } - } - return value[node]; - } - - @Override - public V getBest(ByteBuffer b, int offset, int len) { - if (b.hasArray()) return getBest(0, b.array(), b.arrayOffset() + b.position() + offset, len); - else return getBest(0, b, offset, len); - } - - private V getBest(int t, byte[] b, int offset, int len) { - int node = t; - int end = offset + len; - loop: - while (offset < end) { - byte c = (byte) (b[offset++] & 0x7f); - len--; - if (isCaseInsensitive()) - c = (byte) LOWER_CASES[c]; - - while (true) { - int row = ROW_SIZE * t; - char n = tree[row]; - int diff = n - c; - - if (diff == 0) { - t = tree[row + EQ]; - if (t == 0) break loop; - - // if this node is a match, recurse to remember - if (key[t] != null) { - node = t; - V better = getBest(t, b, offset, len); - if (better != null) return better; - } - break; - } - - t = tree[row + hilo(diff)]; - if (t == 0) break loop; - } - } - return (V) value[node]; - } - - private V getBest(int t, ByteBuffer b, int offset, int len) { - int node = t; - int o = offset + b.position(); - - loop: - for (int i = 0; i < len; i++) { - byte c = (byte) (b.get(o + i) & 0x7f); - if (isCaseInsensitive()) - c = (byte) LOWER_CASES[c]; - - while (true) { - int row = ROW_SIZE * t; - char n = tree[row]; - int diff = n - c; - - if (diff == 0) { - t = tree[row + EQ]; - if (t == 0) break loop; - - // if this node is a match, recurse to remember - if (key[t] != null) { - node = t; - V best = getBest(t, b, offset + i + 1, len - i - 1); - if (best != null) return best; - } - break; - } - - t = tree[row + hilo(diff)]; - if (t == 0) break loop; - } - } - return value[node]; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - for (int r = 0; r <= rows; r++) { - if (key[r] != null && value[r] != null) { - buf.append(','); - buf.append(key[r]); - buf.append('='); - buf.append(value[r].toString()); - } - } - if (buf.length() == 0) return "{}"; - - buf.setCharAt(0, '{'); - buf.append('}'); - return buf.toString(); - } - - @Override - public Set keySet() { - Set keys = new HashSet<>(); - - for (int r = 0; r <= rows; r++) { - if (key[r] != null && value[r] != null) - keys.add(key[r]); - } - return keys; - } - - public int size() { - int s = 0; - for (int r = 0; r <= rows; r++) { - if (key[r] != null && value[r] != null) - s++; - } - return s; - } - - public boolean isEmpty() { - for (int r = 0; r <= rows; r++) { - if (key[r] != null && value[r] != null) - return false; - } - return true; - } - - public Set> entrySet() { - Set> entries = new HashSet<>(); - for (int r = 0; r <= rows; r++) { - if (key[r] != null && value[r] != null) - entries.add(new AbstractMap.SimpleEntry<>(key[r], value[r])); - } - return entries; - } - - @Override - public boolean isFull() { - return rows + 1 == key.length; - } - - public void dump() { - for (int r = 0; r < rows; r++) { - char c = tree[r * ROW_SIZE]; - System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n", - r, - (c < ' ' || c > 127) ? ("" + (int) c) : "'" + c + "'", - (int) tree[r * ROW_SIZE + LO], - (int) tree[r * ROW_SIZE + EQ], - (int) tree[r * ROW_SIZE + HI], - key[r], - value[r]); - } - - } - - public static class Growing implements Trie { - private final int growBy; - private ArrayTernaryTrie trie; - - public Growing() { - this(1024, 1024); - } - - public Growing(int capacity, int growBy) { - this.growBy = growBy; - trie = new ArrayTernaryTrie<>(capacity); - } - - public Growing(boolean insensitive, int capacity, int growby) { - growBy = growby; - trie = new ArrayTernaryTrie<>(insensitive, capacity); - } - - public boolean put(V v) { - return put(v.toString(), v); - } - - public int hashCode() { - return trie.hashCode(); - } - - public V remove(String s) { - return trie.remove(s); - } - - public V get(String s) { - return trie.get(s); - } - - public V get(ByteBuffer b) { - return trie.get(b); - } - - public V getBest(byte[] b, int offset, int len) { - return trie.getBest(b, offset, len); - } - - public boolean isCaseInsensitive() { - return trie.isCaseInsensitive(); - } - - public boolean equals(Object obj) { - return trie.equals(obj); - } - - public void clear() { - trie.clear(); - } - - public boolean put(String s, V v) { - boolean added = trie.put(s, v); - while (!added && growBy > 0) { - ArrayTernaryTrie bigger = new ArrayTernaryTrie<>(trie.key.length + growBy); - for (Map.Entry entry : trie.entrySet()) - bigger.put(entry.getKey(), entry.getValue()); - trie = bigger; - added = trie.put(s, v); - } - - return added; - } - - public V get(String s, int offset, int len) { - return trie.get(s, offset, len); - } - - public V get(ByteBuffer b, int offset, int len) { - return trie.get(b, offset, len); - } - - public V getBest(String s) { - return trie.getBest(s); - } - - public V getBest(String s, int offset, int length) { - return trie.getBest(s, offset, length); - } - - public V getBest(ByteBuffer b, int offset, int len) { - return trie.getBest(b, offset, len); - } - - public String toString() { - return trie.toString(); - } - - public Set keySet() { - return trie.keySet(); - } - - public boolean isFull() { - return false; - } - - public void dump() { - trie.dump(); - } - - public boolean isEmpty() { - return trie.isEmpty(); - } - - public int size() { - return trie.size(); - } - - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/ArrayTrie.java b/firefly-common/src/main/java/com/fireflysource/common/collection/trie/ArrayTrie.java deleted file mode 100644 index a36b9fc8b..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/ArrayTrie.java +++ /dev/null @@ -1,401 +0,0 @@ -package com.fireflysource.common.collection.trie; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - *

    A Trie String lookup data structure using a fixed size array.

    - *

    This implementation is always case-insensitive and is optimal for - * a few fixed strings with few special characters. The - * Trie is stored in an array of lookup tables, each indexed by the - * next character of the key. Frequently used characters directly - * index in each lookup table, whilst infrequently used characters - * must use a big character table. - *

    - *

    This Trie is very space efficient if the key characters are - * from ' ', '+', '-', ':', ';', '.', 'A' to 'Z' or 'a' to 'z'. - * Other ISO-8859-1 characters can be used by the key, but less space - * efficiently. - *

    - *

    This Trie is not Threadsafe and contains no mutual exclusion - * or deliberate memory barriers. It is intended for an ArrayTrie to be - * built by a single thread and then used concurrently by multiple threads - * and not mutated during that access. If concurrent mutations of the - * Trie is required external locks need to be applied. - *

    - * - * @param the element of entry - */ -public class ArrayTrie extends AbstractTrie { - /** - * The Size of a Trie row is how many characters can be looked - * up directly without going to a big index. This is set at - * 32 to cover case-insensitive alphabet and a few other common - * characters. - */ - private static final int ROW_SIZE = 32; - - /** - * The index lookup table, this maps a character as a byte - * (ISO-8859-1 or UTF8) to an index within a Trie row - */ - private static final int[] LOOKUP = - { // 0 1 2 3 4 5 6 7 8 9 A B C D E F - /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1, - /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1, - /*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - }; - - /** - * The Trie rows in a single array which allows a lookup of row,character - * to the next row in the Trie. This is actually a 2 dimensional - * array that has been flattened to achieve locality of reference. - * The first ROW_SIZE entries are for row 0, then next ROW_SIZE - * entries are for row 1 etc. So in general instead of using - * rows[row][index], we use rows[row*ROW_SIZE+index] to look up - * the next row for a given character. - *

    - * The array is of characters rather than integers to save space. - */ - private final char[] rowIndex; - - /** - * The key (if any) for a Trie row. - * A row may be a leaf, a node or both in the Trie tree. - */ - private final String[] key; - - /** - * The value (if any) for a Trie row. - * A row may be a leaf, a node or both in the Trie tree. - */ - private final V[] value; - - /** - * A big index for each row. - * If a character outside of the lookup map needs, - * then a big index will be created for the row, with - * 256 entries, one for each possible byte. - */ - private char[][] bigIndex; - - /** - * The number of rows allocated - */ - private char rows; - - public ArrayTrie() { - this(128); - } - - /** - * @param capacity The capacity of the trie, which at the worst case - * is the total number of characters of all keys stored in the Trie. - * The capacity needed is dependent of the shared prefixes of the keys. - * For example, a capacity of 6 nodes require to store the keys "foo" - * and "bar", but a capacity of only 4 is required to - * store "bar" and "bat". - */ - @SuppressWarnings("unchecked") - public ArrayTrie(int capacity) { - super(true); - value = (V[]) new Object[capacity]; - rowIndex = new char[capacity * 32]; - key = new String[capacity]; - } - - @Override - public void clear() { - rows = 0; - Arrays.fill(value, null); - Arrays.fill(rowIndex, (char) 0); - Arrays.fill(key, null); - } - - @Override - public boolean put(String s, V v) { - int t = 0; - int k; - int limit = s.length(); - for (k = 0; k < limit; k++) { - char c = s.charAt(k); - - int index = LOOKUP[c & 0x7f]; - if (index >= 0) { - int idx = t * ROW_SIZE + index; - t = rowIndex[idx]; - if (t == 0) { - if (++rows >= value.length) - return false; - t = rowIndex[idx] = rows; - } - } else if (c > 127) - throw new IllegalArgumentException("non ascii character"); - else { - if (bigIndex == null) - bigIndex = new char[value.length][]; - if (t >= bigIndex.length) - return false; - char[] big = bigIndex[t]; - if (big == null) - big = bigIndex[t] = new char[128]; - t = big[c]; - if (t == 0) { - if (rows == value.length) - return false; - t = big[c] = ++rows; - } - } - } - - if (t >= key.length) { - rows = (char) key.length; - return false; - } - - key[t] = v == null ? null : s; - value[t] = v; - return true; - } - - @Override - public V get(String s, int offset, int len) { - int t = 0; - for (int i = 0; i < len; i++) { - char c = s.charAt(offset + i); - int index = LOOKUP[c & 0x7f]; - if (index >= 0) { - int idx = t * ROW_SIZE + index; - t = rowIndex[idx]; - if (t == 0) - return null; - } else { - char[] big = bigIndex == null ? null : bigIndex[t]; - if (big == null) - return null; - t = big[c]; - if (t == 0) - return null; - } - } - return value[t]; - } - - @Override - public V get(ByteBuffer b, int offset, int len) { - int t = 0; - for (int i = 0; i < len; i++) { - byte c = b.get(offset + i); - int index = LOOKUP[c & 0x7f]; - if (index >= 0) { - int idx = t * ROW_SIZE + index; - t = rowIndex[idx]; - if (t == 0) - return null; - } else { - char[] big = bigIndex == null ? null : bigIndex[t]; - if (big == null) - return null; - t = big[c]; - if (t == 0) - return null; - } - } - return value[t]; - } - - @Override - public V getBest(byte[] b, int offset, int len) { - return getBest(0, b, offset, len); - } - - @Override - public V getBest(ByteBuffer b, int offset, int len) { - if (b.hasArray()) - return getBest(0, b.array(), b.arrayOffset() + b.position() + offset, len); - return getBest(0, b, offset, len); - } - - @Override - public V getBest(String s, int offset, int len) { - return getBest(0, s, offset, len); - } - - private V getBest(int t, String s, int offset, int len) { - int pos = offset; - for (int i = 0; i < len; i++) { - char c = s.charAt(pos++); - int index = LOOKUP[c & 0x7f]; - if (index >= 0) { - int idx = t * ROW_SIZE + index; - int nt = rowIndex[idx]; - if (nt == 0) - break; - t = nt; - } else { - char[] big = bigIndex == null ? null : bigIndex[t]; - if (big == null) - return null; - int nt = big[c]; - if (nt == 0) - break; - t = nt; - } - - // Is the next Trie is a match - if (key[t] != null) { - // Recurse so we can remember this possibility - V best = getBest(t, s, offset + i + 1, len - i - 1); - if (best != null) - return best; - return value[t]; - } - } - return value[t]; - } - - private V getBest(int t, byte[] b, int offset, int len) { - for (int i = 0; i < len; i++) { - byte c = b[offset + i]; - int index = LOOKUP[c & 0x7f]; - if (index >= 0) { - int idx = t * ROW_SIZE + index; - int nt = rowIndex[idx]; - if (nt == 0) - break; - t = nt; - } else { - char[] big = bigIndex == null ? null : bigIndex[t]; - if (big == null) - return null; - int nt = big[c]; - if (nt == 0) - break; - t = nt; - } - - // Is the next Trie is a match - if (key[t] != null) { - // Recurse so we can remember this possibility - V best = getBest(t, b, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return value[t]; - } - - private V getBest(int t, ByteBuffer b, int offset, int len) { - int pos = b.position() + offset; - for (int i = 0; i < len; i++) { - byte c = b.get(pos++); - int index = LOOKUP[c & 0x7f]; - if (index >= 0) { - int idx = t * ROW_SIZE + index; - int nt = rowIndex[idx]; - if (nt == 0) - break; - t = nt; - } else { - char[] big = bigIndex == null ? null : bigIndex[t]; - if (big == null) - return null; - int nt = big[c]; - if (nt == 0) - break; - t = nt; - } - - // Is the next Trie is a match - if (key[t] != null) { - // Recurse so we can remember this possibility - V best = getBest(t, b, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return value[t]; - } - - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - toString(buf, 0); - - if (buf.length() == 0) - return "{}"; - - buf.setCharAt(0, '{'); - buf.append('}'); - return buf.toString(); - } - - - private void toString(Appendable out, int t) { - if (value[t] != null) { - try { - out.append(','); - out.append(key[t]); - out.append('='); - out.append(value[t].toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (int i = 0; i < ROW_SIZE; i++) { - int idx = t * ROW_SIZE + i; - if (rowIndex[idx] != 0) - toString(out, rowIndex[idx]); - } - - char[] big = bigIndex == null ? null : bigIndex[t]; - if (big != null) { - for (int i : big) - if (i != 0) - toString(out, i); - } - - } - - @Override - public Set keySet() { - Set keys = new HashSet<>(); - keySet(keys, 0); - return keys; - } - - private void keySet(Set set, int t) { - if (t < value.length && value[t] != null) - set.add(key[t]); - - for (int i = 0; i < ROW_SIZE; i++) { - int idx = t * ROW_SIZE + i; - if (idx < rowIndex.length && rowIndex[idx] != 0) - keySet(set, rowIndex[idx]); - } - - char[] big = bigIndex == null || t >= bigIndex.length ? null : bigIndex[t]; - if (big != null) { - for (int i : big) - if (i != 0) - keySet(set, i); - } - } - - @Override - public boolean isFull() { - return rows + 1 >= key.length; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/TreeTrie.java b/firefly-common/src/main/java/com/fireflysource/common/collection/trie/TreeTrie.java deleted file mode 100644 index 23c224480..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/TreeTrie.java +++ /dev/null @@ -1,326 +0,0 @@ -package com.fireflysource.common.collection.trie; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.*; - - -/** - * A Trie String lookup data structure using a tree - *

    - * This implementation is always case-insensitive and is optimal for a variable - * number of fixed strings with few special characters. - *

    - *

    - * This Trie is stored in a Tree and is unlimited in capacity - *

    - *

    - *

    - * This Trie is not Threadsafe and contains no mutual exclusion or deliberate - * memory barriers. It is intended for an ArrayTrie to be built by a single - * thread and then used concurrently by multiple threads and not mutated during - * that access. If concurrent mutations of the Trie is required external locks - * need to be applied. - *

    - * - * @param the entry type - */ -public class TreeTrie extends AbstractTrie { - private static final int[] LOOKUP = - { // 0 1 2 3 4 5 6 7 8 9 A B C D E F - /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1, - /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1, - /*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - }; - private static final int INDEX = 32; - private final TreeTrie[] nextIndex; - private final List> nextOther = new ArrayList<>(); - private final char ch; - private String key; - private V value; - - @SuppressWarnings("unchecked") - public TreeTrie() { - super(true); - nextIndex = new TreeTrie[INDEX]; - ch = 0; - } - - @SuppressWarnings("unchecked") - private TreeTrie(char c) { - super(true); - nextIndex = new TreeTrie[INDEX]; - this.ch = c; - } - - private static void toString(Appendable out, TreeTrie t) { - if (t != null) { - if (t.value != null) { - try { - out.append(','); - out.append(t.key); - out.append('='); - out.append(t.value.toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (int i = 0; i < INDEX; i++) { - if (t.nextIndex[i] != null) - toString(out, t.nextIndex[i]); - } - for (int i = t.nextOther.size(); i-- > 0; ) - toString(out, t.nextOther.get(i)); - } - } - - private static void keySet(Set set, TreeTrie t) { - if (t != null) { - if (t.key != null) - set.add(t.key); - - for (int i = 0; i < INDEX; i++) { - if (t.nextIndex[i] != null) - keySet(set, t.nextIndex[i]); - } - for (int i = t.nextOther.size(); i-- > 0; ) - keySet(set, t.nextOther.get(i)); - } - } - - @Override - public void clear() { - Arrays.fill(nextIndex, null); - nextOther.clear(); - key = null; - value = null; - } - - @Override - public boolean put(String s, V v) { - TreeTrie t = this; - int limit = s.length(); - for (int k = 0; k < limit; k++) { - char c = s.charAt(k); - - int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; - if (index >= 0) { - if (t.nextIndex[index] == null) - t.nextIndex[index] = new TreeTrie(c); - t = t.nextIndex[index]; - } else { - TreeTrie n = null; - for (int i = t.nextOther.size(); i-- > 0; ) { - n = t.nextOther.get(i); - if (n.ch == c) - break; - n = null; - } - if (n == null) { - n = new TreeTrie(c); - t.nextOther.add(n); - } - t = n; - } - } - t.key = v == null ? null : s; - t.value = v; - return true; - } - - @Override - public V get(String s, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - char c = s.charAt(offset + i); - int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; - if (index >= 0) { - if (t.nextIndex[index] == null) - return null; - t = t.nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t.nextOther.size(); j-- > 0; ) { - n = t.nextOther.get(j); - if (n.ch == c) - break; - n = null; - } - if (n == null) - return null; - t = n; - } - } - return t.value; - } - - @Override - public V get(ByteBuffer b, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - byte c = b.get(offset + i); - int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; - if (index >= 0) { - if (t.nextIndex[index] == null) - return null; - t = t.nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t.nextOther.size(); j-- > 0; ) { - n = t.nextOther.get(j); - if (n.ch == c) - break; - n = null; - } - if (n == null) - return null; - t = n; - } - } - return t.value; - } - - @Override - public V getBest(byte[] b, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - byte c = b[offset + i]; - int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; - if (index >= 0) { - if (t.nextIndex[index] == null) - break; - t = t.nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t.nextOther.size(); j-- > 0; ) { - n = t.nextOther.get(j); - if (n.ch == c) - break; - n = null; - } - if (n == null) - break; - t = n; - } - - // Is the next Trie is a match - if (t.key != null) { - // Recurse so we can remember this possibility - V best = t.getBest(b, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return t.value; - } - - @Override - public V getBest(String s, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - byte c = (byte) (0xff & s.charAt(offset + i)); - int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; - if (index >= 0) { - if (t.nextIndex[index] == null) - break; - t = t.nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t.nextOther.size(); j-- > 0; ) { - n = t.nextOther.get(j); - if (n.ch == c) - break; - n = null; - } - if (n == null) - break; - t = n; - } - - // Is the next Trie is a match - if (t.key != null) { - // Recurse so we can remember this possibility - V best = t.getBest(s, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return t.value; - } - - @Override - public V getBest(ByteBuffer b, int offset, int len) { - if (b.hasArray()) - return getBest(b.array(), b.arrayOffset() + b.position() + offset, len); - return getBestByteBuffer(b, offset, len); - } - - private V getBestByteBuffer(ByteBuffer b, int offset, int len) { - TreeTrie t = this; - int pos = b.position() + offset; - for (int i = 0; i < len; i++) { - byte c = b.get(pos++); - int index = c >= 0 && c < 0x7f ? LOOKUP[c] : -1; - if (index >= 0) { - if (t.nextIndex[index] == null) - break; - t = t.nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t.nextOther.size(); j-- > 0; ) { - n = t.nextOther.get(j); - if (n.ch == c) - break; - n = null; - } - if (n == null) - break; - t = n; - } - - // Is the next Trie is a match - if (t.key != null) { - // Recurse so we can remember this possibility - V best = t.getBest(b, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return t.value; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - toString(buf, this); - - if (buf.length() == 0) - return "{}"; - - buf.setCharAt(0, '{'); - buf.append('}'); - return buf.toString(); - } - - @Override - public Set keySet() { - Set keys = new HashSet<>(); - keySet(keys, this); - return keys; - } - - @Override - public boolean isFull() { - return false; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/Trie.java b/firefly-common/src/main/java/com/fireflysource/common/collection/trie/Trie.java deleted file mode 100644 index af8581c6c..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/collection/trie/Trie.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.fireflysource.common.collection.trie; - -import java.nio.ByteBuffer; -import java.util.Set; - - -/** - * A Trie String lookup data structure. - * - * @param the Trie entry type - */ -public interface Trie { - - /** - * Put an entry into the Trie - * - * @param s The key for the entry - * @param v The value of the entry - * @return True if the Trie had capacity to add the field. - */ - boolean put(String s, V v); - - /** - * Put a value as both a key and a value. - * - * @param v The value and key - * @return True if the Trie had capacity to add the field. - */ - boolean put(V v); - - V remove(String s); - - /** - * Get an exact match from a String key - * - * @param s The key - * @return the value for the string key - */ - V get(String s); - - /** - * Get an exact match from a String key - * - * @param s The key - * @param offset The offset within the string of the key - * @param len the length of the key - * @return the value for the string / offset / length - */ - V get(String s, int offset, int len); - - /** - * Get an exact match from a segment of a ByteBuufer as key - * - * @param b The buffer - * @return The value or null if not found - */ - V get(ByteBuffer b); - - /** - * Get an exact match from a segment of a ByteBuufer as key - * - * @param b The buffer - * @param offset The offset within the buffer of the key - * @param len the length of the key - * @return The value or null if not found - */ - V get(ByteBuffer b, int offset, int len); - - /** - * Get the best match from key in a String. - * - * @param s The string - * @return The value or null if not found - */ - V getBest(String s); - - /** - * Get the best match from key in a String. - * - * @param s The string - * @param offset The offset within the string of the key - * @param len the length of the key - * @return The value or null if not found - */ - V getBest(String s, int offset, int len); - - /** - * Get the best match from key in a byte array. - * The key is assumed to by ISO_8859_1 characters. - * - * @param b The buffer - * @param offset The offset within the array of the key - * @param len the length of the key - * @return The value or null if not found - */ - V getBest(byte[] b, int offset, int len); - - /** - * Get the best match from key in a byte buffer. - * The key is assumed to by ISO_8859_1 characters. - * - * @param b The buffer - * @param offset The offset within the buffer of the key - * @param len the length of the key - * @return The value or null if not found - */ - V getBest(ByteBuffer b, int offset, int len); - - Set keySet(); - - boolean isFull(); - - boolean isCaseInsensitive(); - - void clear(); - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/AtomicBiInteger.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/AtomicBiInteger.java deleted file mode 100644 index 22bb06211..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/AtomicBiInteger.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.fireflysource.common.concurrent; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * An AtomicLong with additional methods to treat it as two hi/lo integers. - */ -public class AtomicBiInteger extends AtomicLong { - - public AtomicBiInteger() { - } - - public AtomicBiInteger(long encoded) { - super(encoded); - } - - public AtomicBiInteger(int hi, int lo) { - super(encode(hi, lo)); - } - - /** - * @return the hi value - */ - public int getHi() { - return getHi(get()); - } - - /** - * @return the lo value - */ - public int getLo() { - return getLo(get()); - } - - /** - * Atomically sets the hi value without changing the lo value. - * - * @param hi the new hi value - * @return the previous hi value - */ - public int getAndSetHi(int hi) { - while (true) { - long encoded = get(); - long update = encodeHi(encoded, hi); - if (compareAndSet(encoded, update)) - return getHi(encoded); - } - } - - /** - * Atomically sets the lo value without changing the hi value. - * - * @param lo the new lo value - * @return the previous lo value - */ - public int getAndSetLo(int lo) { - while (true) { - long encoded = get(); - long update = encodeLo(encoded, lo); - if (compareAndSet(encoded, update)) - return getLo(encoded); - } - } - - /** - * Sets the hi and lo values. - * - * @param hi the new hi value - * @param lo the new lo value - */ - public void set(int hi, int lo) { - set(encode(hi, lo)); - } - - /** - *

    Atomically sets the hi value to the given updated value - * only if the current value {@code ==} the expected value.

    - *

    Concurrent changes to the lo value result in a retry.

    - * - * @param expectHi the expected hi value - * @param hi the new hi value - * @return {@code true} if successful. False return indicates that - * the actual hi value was not equal to the expected hi value. - */ - public boolean compareAndSetHi(int expectHi, int hi) { - while (true) { - long encoded = get(); - if (getHi(encoded) != expectHi) - return false; - long update = encodeHi(encoded, hi); - if (compareAndSet(encoded, update)) - return true; - } - } - - /** - *

    Atomically sets the lo value to the given updated value - * only if the current value {@code ==} the expected value.

    - *

    Concurrent changes to the hi value result in a retry.

    - * - * @param expectLo the expected lo value - * @param lo the new lo value - * @return {@code true} if successful. False return indicates that - * the actual lo value was not equal to the expected lo value. - */ - public boolean compareAndSetLo(int expectLo, int lo) { - while (true) { - long encoded = get(); - if (getLo(encoded) != expectLo) - return false; - long update = encodeLo(encoded, lo); - if (compareAndSet(encoded, update)) - return true; - } - } - - /** - * Atomically sets the values to the given updated values only if - * the current encoded value {@code ==} the expected encoded value. - * - * @param encoded the expected encoded value - * @param hi the new hi value - * @param lo the new lo value - * @return {@code true} if successful. False return indicates that - * the actual encoded value was not equal to the expected encoded value. - */ - public boolean compareAndSet(long encoded, int hi, int lo) { - long update = encode(hi, lo); - return compareAndSet(encoded, update); - } - - /** - * Atomically sets the hi and lo values to the given updated values only if - * the current hi and lo values {@code ==} the expected hi and lo values. - * - * @param expectHi the expected hi value - * @param hi the new hi value - * @param expectLo the expected lo value - * @param lo the new lo value - * @return {@code true} if successful. False return indicates that - * the actual hi and lo values were not equal to the expected hi and lo value. - */ - public boolean compareAndSet(int expectHi, int hi, int expectLo, int lo) { - long encoded = encode(expectHi, expectLo); - long update = encode(hi, lo); - return compareAndSet(encoded, update); - } - - /** - * Atomically adds the given delta to the current hi value, returning the updated hi value. - * - * @param delta the delta to apply - * @return the updated hi value - */ - public int addAndGetHi(int delta) { - while (true) { - long encoded = get(); - int hi = getHi(encoded) + delta; - long update = encodeHi(encoded, hi); - if (compareAndSet(encoded, update)) - return hi; - } - } - - /** - * Atomically adds the given delta to the current lo value, returning the updated lo value. - * - * @param delta the delta to apply - * @return the updated lo value - */ - public int addAndGetLo(int delta) { - while (true) { - long encoded = get(); - int lo = getLo(encoded) + delta; - long update = encodeLo(encoded, lo); - if (compareAndSet(encoded, update)) - return lo; - } - } - - /** - * Atomically adds the given deltas to the current hi and lo values. - * - * @param deltaHi the delta to apply to the hi value - * @param deltaLo the delta to apply to the lo value - */ - public void add(int deltaHi, int deltaLo) { - while (true) { - long encoded = get(); - long update = encode(getHi(encoded) + deltaHi, getLo(encoded) + deltaLo); - if (compareAndSet(encoded, update)) - return; - } - } - - /** - * Gets a hi value from the given encoded value. - * - * @param encoded the encoded value - * @return the hi value - */ - public static int getHi(long encoded) { - return (int) ((encoded >> 32) & 0xFFFF_FFFFL); - } - - /** - * Gets a lo value from the given encoded value. - * - * @param encoded the encoded value - * @return the lo value - */ - public static int getLo(long encoded) { - return (int) (encoded & 0xFFFF_FFFFL); - } - - /** - * Encodes hi and lo values into a long. - * - * @param hi the hi value - * @param lo the lo value - * @return the encoded value - */ - public static long encode(int hi, int lo) { - long h = ((long) hi) & 0xFFFF_FFFFL; - long l = ((long) lo) & 0xFFFF_FFFFL; - return (h << 32) + l; - } - - /** - * Sets the hi value into the given encoded value. - * - * @param encoded the encoded value - * @param hi the hi value - * @return the new encoded value - */ - public static long encodeHi(long encoded, int hi) { - long h = ((long) hi) & 0xFFFF_FFFFL; - long l = encoded & 0xFFFF_FFFFL; - return (h << 32) + l; - } - - /** - * Sets the lo value into the given encoded value. - * - * @param encoded the encoded value - * @param lo the lo value - * @return the new encoded value - */ - public static long encodeLo(long encoded, int lo) { - long h = (encoded >> 32) & 0xFFFF_FFFFL; - long l = ((long) lo) & 0xFFFF_FFFFL; - return (h << 32) + l; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/Atomics.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/Atomics.java deleted file mode 100644 index 0cfcdf584..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/Atomics.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.fireflysource.common.concurrent; - -import java.util.concurrent.atomic.AtomicInteger; - -abstract public class Atomics { - - public static int getAndDecrement(AtomicInteger i, int minValue) { - return i.getAndUpdate(prev -> { - if (prev > minValue) { - return prev - 1; - } else { - return minValue; - } - }); - } - - public static int getAndIncrement(AtomicInteger i, int maxValue) { - return i.getAndUpdate(prev -> { - if (prev < maxValue) { - return prev + 1; - } else { - return maxValue; - } - }); - } - - public static boolean updateMin(AtomicInteger currentMin, int newValue) { - int oldValue = currentMin.get(); - while (newValue < oldValue) { - if (currentMin.compareAndSet(oldValue, newValue)) { - return true; - } - oldValue = currentMin.get(); - } - return false; - } - - public static boolean updateMax(AtomicInteger currentMax, int newValue) { - int oldValue = currentMax.get(); - while (newValue > oldValue) { - if (currentMax.compareAndSet(oldValue, newValue)) { - return true; - } - oldValue = currentMax.get(); - } - return false; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/AutoLock.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/AutoLock.java deleted file mode 100644 index 9291d0029..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/AutoLock.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.fireflysource.common.concurrent; - -import java.io.Serializable; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; - -/** - *

    Reentrant lock that can be used in a try-with-resources statement.

    - *

    Typical usage:

    - *
    - * try (AutoLock lock = this.lock.lock())
    - * {
    - *     // Something
    - * }
    - * 
    - */ -public class AutoLock implements AutoCloseable, Serializable { - private static final long serialVersionUID = 3300696774541816341L; - - private final ReentrantLock _lock = new ReentrantLock(); - - /** - *

    Acquires the lock.

    - * - * @return this AutoLock for unlocking - */ - public AutoLock lock() { - _lock.lock(); - return this; - } - - public void lock(Runnable runnable) { - try (AutoLock ignored = this.lock()) { - runnable.run(); - } - } - - public T lock(Supplier supplier) { - try (AutoLock ignored = this.lock()) { - return supplier.get(); - } - } - - /** - * @return whether this lock is held by the current thread - * @see ReentrantLock#isHeldByCurrentThread() - */ - public boolean isHeldByCurrentThread() { - return _lock.isHeldByCurrentThread(); - } - - /** - * @return a {@link Condition} associated with this lock - */ - public Condition newCondition() { - return _lock.newCondition(); - } - - // Package-private for testing only. - boolean isLocked() { - return _lock.isLocked(); - } - - @Override - public void close() { - _lock.unlock(); - } - - /** - *

    A reentrant lock with a condition that can be used in a try-with-resources statement.

    - *

    Typical usage:

    - *
    -     * // Waiting
    -     * try (AutoLock lock = _lock.lock())
    -     * {
    -     *     lock.await();
    -     * }
    -     *
    -     * // Signaling
    -     * try (AutoLock lock = _lock.lock())
    -     * {
    -     *     lock.signalAll();
    -     * }
    -     * 
    - */ - public static class WithCondition extends AutoLock { - private final Condition _condition = newCondition(); - - @Override - public AutoLock.WithCondition lock() { - return (WithCondition) super.lock(); - } - - /** - * @see Condition#signal() - */ - public void signal() { - _condition.signal(); - } - - /** - * @see Condition#signalAll() - */ - public void signalAll() { - _condition.signalAll(); - } - - /** - * @throws InterruptedException if the current thread is interrupted - * @see Condition#await() - */ - public void await() throws InterruptedException { - _condition.await(); - } - - /** - * @param time the time to wait - * @param unit the time unit - * @return false if the waiting time elapsed - * @throws InterruptedException if the current thread is interrupted - * @see Condition#await(long, TimeUnit) - */ - public boolean await(long time, TimeUnit unit) throws InterruptedException { - return _condition.await(time, unit); - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/CompletableFutures.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/CompletableFutures.java deleted file mode 100644 index 252755f48..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/CompletableFutures.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.fireflysource.common.concurrent; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; - -abstract public class CompletableFutures { - /** - * Returns a new stage that, when this stage completes - * exceptionally, is executed with this stage's exception as the - * argument to the supplied function. Otherwise, if this stage - * completes normally, then the returned stage also completes - * normally with the same value. - * - *

    This differs from - * {@link java.util.concurrent.CompletionStage#exceptionally(java.util.function.Function)} - * in that the function should return a {@link java.util.concurrent.CompletionStage} rather than - * the value directly. - * - * @param stage the {@link CompletionStage} to compose - * @param fn the function computes the value of the - * returned {@link CompletionStage} if this stage completed - * exceptionally - * @param the type of the input stage's value. - * @return the new {@link CompletionStage} - */ - public static CompletionStage exceptionallyCompose(CompletionStage stage, Function> fn) { - return dereference(wrap(stage).exceptionally(fn)); - } - - public static CompletionStage dereference(CompletionStage> stage) { - return stage.thenCompose(Function.identity()); - } - - private static CompletionStage> wrap(CompletionStage future) { - return future.thenApply(CompletableFuture::completedFuture); - } - - /** - * Create a failed future. - * - * @param t The exception. - * @param The future result type. - * @return The failed future. - */ - public static CompletableFuture failedFuture(Throwable t) { - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(t); - return future; - } - - /** - * Retry the async operation. - * - * @param retryCount The max retry times. - * @param supplier The async operation function. - * @param beforeRetry The callback before retries async operation. - * @param The future result type. - * @return The operation result future. - */ - public static CompletableFuture retry(int retryCount, Supplier> supplier, BiConsumer beforeRetry) { - return exceptionallyCompose(supplier.get(), e -> { - if (retryCount > 0) { - beforeRetry.accept(e, retryCount); - return retry(retryCount - 1, supplier, beforeRetry); - } else { - return failedFuture(e); - } - }).toCompletableFuture(); - } - - public static CompletableFuture doFinally(CompletionStage stage, BiFunction> function) { - CompletableFuture future = new CompletableFuture<>(); - stage.handle((value, throwable) -> { - function.apply(value, throwable).handle((v, t) -> { - if (throwable != null) { - future.completeExceptionally(throwable); - } else { - future.complete(value); - } - return v; - }); - return value; - }); - return future; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/ExecutorServiceUtils.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/ExecutorServiceUtils.java deleted file mode 100644 index 03909eb7e..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/ExecutorServiceUtils.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fireflysource.common.concurrent; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * The executor service common utilities. - * - * @author Pengtao Qiu - */ -abstract public class ExecutorServiceUtils { - - /** - * Blocks until all tasks have completed execution after a shutdown - * request, or the timeout occurs, or the current thread is - * interrupted, whichever happens first. - * - * @param pool The thread pool that will shutdown. - * @param timeout The maximum time to wait. The time unit is second. - */ - public static void shutdownAndAwaitTermination(ExecutorService pool, long timeout) { - shutdownAndAwaitTermination(pool, timeout, TimeUnit.SECONDS); - } - - /** - * Blocks until all tasks have completed execution after a shutdown - * request, or the timeout occurs, or the current thread is - * interrupted, whichever happens first. - * - * @param pool The thread pool that will shutdown. - * @param timeout The maximum time to wait. - * @param unit The time unit of the timeout argument. - */ - public static void shutdownAndAwaitTermination(ExecutorService pool, long timeout, TimeUnit unit) { - try { - // Disable new tasks from being submitted - pool.shutdown(); - // Wait a while for existing tasks to terminate - if (!pool.awaitTermination(timeout, unit)) { - pool.shutdownNow(); // Cancel currently executing tasks - // Wait a while for tasks to respond to being cancelled - if (!pool.awaitTermination(timeout, unit)) - System.err.println("Pool did not terminate"); - } - } catch (InterruptedException ie) { - // (Re-)Cancel if current thread also interrupted - pool.shutdownNow(); - // Preserve interrupt status - Thread.currentThread().interrupt(); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/IteratingCallback.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/IteratingCallback.java deleted file mode 100644 index 5444fa5d2..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/IteratingCallback.java +++ /dev/null @@ -1,437 +0,0 @@ -package com.fireflysource.common.concurrent; - -import com.fireflysource.common.sys.Result; - -import java.io.IOException; -import java.util.function.Consumer; - -/** - * This specialized callback implements a pattern that allows - * a large job to be broken into smaller tasks using iteration - * rather than recursion. - *

    - * A typical example is the write of a large content to a socket, - * divided in chunks. Chunk C1 is written by thread T1, which - * also invokes the callback, which writes chunk C2, which invokes - * the callback again, which writes chunk C3, and so forth. - *

    - *

    - * The problem with the example is that if the callback thread - * is the same that performs the I/O operation, then the process - * is recursive and may result in a stack overflow. - * To avoid the stack overflow, a thread dispatch must be performed, - * causing context switching and cache misses, affecting performance. - *

    - *

    - * To avoid this issue, this callback uses an AtomicReference to - * record whether success callback has been called during the processing - * of a sub task, and if so then the processing iterates rather than - * recurring. - *

    - *

    - * Subclasses must implement method {@link #process()} where the sub - * task is executed and a suitable {@link Action} is - * returned to this callback to indicate the overall progress of the job. - * This callback is passed to the asynchronous execution of each sub - * task and a call the success result on this callback represents - * the completion of the sub task. - *

    - */ -public abstract class IteratingCallback implements Consumer> { - /** - * The internal states of this callback - */ - private enum State { - /** - * This callback is IDLE, ready to iterate. - */ - IDLE, - - /** - * This callback is iterating calls to {@link #process()} and is dealing with - * the returns. To get into processing state, it much of held the lock state - * and set iterating to true. - */ - PROCESSING, - - /** - * Waiting for a schedule callback - */ - PENDING, - - /** - * Called by a schedule callback - */ - CALLED, - - /** - * The overall job has succeeded as indicated by a {@link Action#SUCCEEDED} return - * from {@link IteratingCallback#process()} - */ - SUCCEEDED, - - /** - * The overall job has failed as indicated by a call to {@link IteratingCallback#failed(Throwable)} - */ - FAILED, - - /** - * This callback has been closed and cannot be reset. - */ - CLOSED - } - - /** - * The indication of the overall progress of the overall job that - * implementations of {@link #process()} must return. - */ - protected enum Action { - /** - * Indicates that {@link #process()} has no more work to do, - * but the overall job is not completed yet, probably waiting - * for additional events to trigger more work. - */ - IDLE, - /** - * Indicates that {@link #process()} is executing asynchronously - * a sub task, where the execution has started but the callback - * may have not yet been invoked. - */ - SCHEDULED, - - /** - * Indicates that {@link #process()} has completed the overall job. - */ - SUCCEEDED - } - - private final AutoLock _lock = new AutoLock(); - private State _state; - private boolean _iterate; - - protected IteratingCallback() { - _state = State.IDLE; - } - - protected IteratingCallback(boolean needReset) { - _state = needReset ? State.SUCCEEDED : State.IDLE; - } - - /** - * Method called by {@link #iterate()} to process the sub task. - *

    - * Implementations must start the asynchronous execution of the sub task - * (if any) and return an appropriate action: - *

    - *
      - *
    • {@link Action#IDLE} when no sub tasks are available for execution - * but the overall job is not completed yet
    • - *
    • {@link Action#SCHEDULED} when the sub task asynchronous execution - * has been started
    • - *
    • {@link Action#SUCCEEDED} when the overall job is completed
    • - *
    - * - * @return the appropriate Action - * @throws Throwable if the sub task processing throws - */ - protected abstract Action process() throws Throwable; - - /** - * Invoked when the overall task has completed successfully. - * - * @see #onCompleteFailure(Throwable) - */ - protected void onCompleteSuccess() { - } - - /** - * Invoked when the overall task has completed with a failure. - * - * @param cause the throwable to indicate cause of failure - * @see #onCompleteSuccess() - */ - protected void onCompleteFailure(Throwable cause) { - } - - /** - * This method must be invoked by applications to start the processing - * of sub tasks. It can be called at any time by any thread, and it's - * contract is that when called, then the {@link #process()} method will - * be called during or soon after, either by the calling thread or by - * another thread. - */ - public void iterate() { - boolean process = false; - - try (AutoLock lock = _lock.lock()) { - switch (_state) { - case PENDING: - case CALLED: - // process will be called when callback is handled - break; - - case IDLE: - _state = State.PROCESSING; - process = true; - break; - - case PROCESSING: - _iterate = true; - break; - - case FAILED: - case SUCCEEDED: - break; - - case CLOSED: - default: - throw new IllegalStateException(toString()); - } - } - if (process) - processing(); - } - - private void processing() { - // This should only ever be called when in processing state, however a failed or close call - // may happen concurrently, so state is not assumed. - - boolean onCompleteSuccess = false; - - // While we are processing - processing: - while (true) { - // Call process to get the action that we have to take. - Action action; - try { - action = process(); - } catch (Throwable x) { - failed(x); - break; - } - - // acted on the action we have just received - try (AutoLock lock = _lock.lock()) { - switch (_state) { - case PROCESSING: { - switch (action) { - case IDLE: { - // Has iterate been called while we were processing? - if (_iterate) { - // yes, so skip idle and keep processing - _iterate = false; - _state = State.PROCESSING; - continue processing; - } - - // No, so we can go idle - _state = State.IDLE; - break processing; - } - - case SCHEDULED: { - // we won the race against the callback, so the callback has to process and we can break processing - _state = State.PENDING; - break processing; - } - - case SUCCEEDED: { - // we lost the race against the callback, - _iterate = false; - _state = State.SUCCEEDED; - onCompleteSuccess = true; - break processing; - } - - default: - break; - } - throw new IllegalStateException(String.format("%s[action=%s]", this, action)); - } - - case CALLED: { - if (action != Action.SCHEDULED) - throw new IllegalStateException(String.format("%s[action=%s]", this, action)); - // we lost the race, so we have to keep processing - _state = State.PROCESSING; - continue processing; - } - - case SUCCEEDED: - case FAILED: - case CLOSED: - break processing; - - case IDLE: - case PENDING: - default: - throw new IllegalStateException(String.format("%s[action=%s]", this, action)); - } - } - } - - if (onCompleteSuccess) - onCompleteSuccess(); - } - - @Override - public void accept(Result result) { - if (result.isSuccess()) { - this.succeeded(); - } else { - this.failed(result.getThrowable()); - } - } - - /** - * Invoked when the sub task succeeds. - * Subclasses that override this method must always remember to call - * {@code super.succeeded()}. - */ - public void succeeded() { - boolean process = false; - try (AutoLock lock = _lock.lock()) { - switch (_state) { - case PROCESSING: { - _state = State.CALLED; - break; - } - case PENDING: { - _state = State.PROCESSING; - process = true; - break; - } - case CLOSED: - case FAILED: { - // Too late! - break; - } - default: { - throw new IllegalStateException(toString()); - } - } - } - if (process) - processing(); - } - - /** - * Invoked when the sub task fails. - * Subclasses that override this method must always remember to call - * {@code super.failed(Throwable)}. - */ - public void failed(Throwable x) { - boolean failure = false; - try (AutoLock lock = _lock.lock()) { - switch (_state) { - case SUCCEEDED: - case FAILED: - case IDLE: - case CLOSED: - case CALLED: - // too late!. - break; - - case PENDING: - case PROCESSING: { - _state = State.FAILED; - failure = true; - break; - } - default: - throw new IllegalStateException(toString()); - } - } - if (failure) - onCompleteFailure(x); - } - - public void close() { - String failure = null; - try (AutoLock lock = _lock.lock()) { - switch (_state) { - case IDLE: - case SUCCEEDED: - case FAILED: - _state = State.CLOSED; - break; - - case CLOSED: - break; - - default: - failure = String.format("Close %s in state %s", this, _state); - _state = State.CLOSED; - } - } - - if (failure != null) - onCompleteFailure(new IOException(failure)); - } - - /* - * only for testing - * @return whether this callback is idle and {@link #iterate()} needs to be called - */ - boolean isIdle() { - try (AutoLock lock = _lock.lock()) { - return _state == State.IDLE; - } - } - - public boolean isClosed() { - try (AutoLock lock = _lock.lock()) { - return _state == State.CLOSED; - } - } - - /** - * @return whether this callback has failed - */ - public boolean isFailed() { - try (AutoLock lock = _lock.lock()) { - return _state == State.FAILED; - } - } - - /** - * @return whether this callback has succeeded - */ - public boolean isSucceeded() { - try (AutoLock lock = _lock.lock()) { - return _state == State.SUCCEEDED; - } - } - - /** - * Resets this callback. - *

    - * A callback can only be reset to IDLE from the - * SUCCEEDED or FAILED states or if it is already IDLE. - *

    - * - * @return true if the reset was successful - */ - public boolean reset() { - try (AutoLock lock = _lock.lock()) { - switch (_state) { - case IDLE: - return true; - - case SUCCEEDED: - case FAILED: - _iterate = false; - _state = State.IDLE; - return true; - - default: - return false; - } - } - } - - @Override - public String toString() { - return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), _state); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/concurrent/SingleThreadExecutorService.java b/firefly-common/src/main/java/com/fireflysource/common/concurrent/SingleThreadExecutorService.java deleted file mode 100644 index 227c30da1..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/concurrent/SingleThreadExecutorService.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.jctools.queues.MpscBlockingConsumerArrayQueue; - -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class SingleThreadExecutorService extends AbstractExecutorService { - - private static final ThreadFactory defaultThreadFactory = r -> new Thread(r, "Firefly-MPSC-thread"); - private final Termination termination = new Termination(); - private final List notExecutedTasks = new LinkedList<>(); - private final AtomicBoolean isShutdown = new AtomicBoolean(false); - private final MpscBlockingConsumerArrayQueue queue; - private final Thread thread; - - public SingleThreadExecutorService(int capacity) { - this(capacity, defaultThreadFactory); - } - - public SingleThreadExecutorService(int capacity, ThreadFactory threadFactory) { - this.queue = new MpscBlockingConsumerArrayQueue<>(capacity); - thread = threadFactory.newThread(() -> { - while (true) { - if (executeTasks()) { - break; - } - } - }); - thread.start(); - } - - private boolean executeTasks() { - boolean exit; - try { - Runnable task = queue.take(); - if (task == termination) { - exit = true; - } else { - task.run(); - exit = false; - } - } catch (InterruptedException e) { - queue.drain(notExecutedTasks::add); - exit = true; - } - return exit; - } - - @Override - public void shutdown() { - if (queue.offer(termination)) { - isShutdown.set(true); - } - } - - @Override - public List shutdownNow() { - shutdown(); - thread.interrupt(); - return notExecutedTasks; - } - - @Override - public boolean isShutdown() { - return isShutdown.get(); - } - - @Override - public boolean isTerminated() { - return !thread.isAlive(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - if (isShutdown.get()) { - thread.join(unit.toMillis(timeout)); - } - return isTerminated(); - } - - @Override - public void execute(Runnable command) { - if (!queue.offer(command)) { - throw new RejectedExecutionException(); - } - } - - private static class Termination implements Runnable { - @Override - public void run() { - - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/exception/UnknownTypeException.java b/firefly-common/src/main/java/com/fireflysource/common/exception/UnknownTypeException.java deleted file mode 100644 index bbb3998e3..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/exception/UnknownTypeException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fireflysource.common.exception; - -public class UnknownTypeException extends RuntimeException { - - public UnknownTypeException() { - } - - public UnknownTypeException(String message) { - super(message); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/func/Callback.java b/firefly-common/src/main/java/com/fireflysource/common/func/Callback.java deleted file mode 100644 index c06943e61..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/func/Callback.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.common.func; - -/** - * @author Pengtao Qiu - */ -@FunctionalInterface -public interface Callback { - void call() throws Exception; -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/func/FunctionInterfaceUtils.java b/firefly-common/src/main/java/com/fireflysource/common/func/FunctionInterfaceUtils.java deleted file mode 100644 index acae59bf3..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/func/FunctionInterfaceUtils.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.common.func; - -import java.util.function.Consumer; - -/** - * @author Pengtao Qiu - */ -abstract public class FunctionInterfaceUtils { - - public static final Callback NOOP_CALLBACK = () -> { - }; - - - public static Consumer createEmptyConsumer() { - return t -> { - }; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/io/AsyncCloseable.java b/firefly-common/src/main/java/com/fireflysource/common/io/AsyncCloseable.java deleted file mode 100644 index e9243ba72..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/io/AsyncCloseable.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.common.io; - -import java.io.Closeable; -import java.util.concurrent.CompletableFuture; - -public interface AsyncCloseable extends Closeable { - - CompletableFuture closeAsync(); - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/io/BufferUtils.java b/firefly-common/src/main/java/com/fireflysource/common/io/BufferUtils.java deleted file mode 100644 index c25eb51a6..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/io/BufferUtils.java +++ /dev/null @@ -1,1047 +0,0 @@ -package com.fireflysource.common.io; - -import com.fireflysource.common.collection.CollectionUtils; -import com.fireflysource.common.object.TypeUtils; - -import java.io.*; -import java.nio.Buffer; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Buffer utility methods. - *

    The standard JVM {@link ByteBuffer} can exist in two modes: In fill mode the valid - * data is between 0 and pos; In flush mode the valid data is between the pos and the limit. - * The various ByteBuffer methods assume a mode and some of them will switch or enforce a mode: - * Allocate and clear set fill mode; flip and compact switch modes; read and write assume fill - * and flush modes. This duality can result in confusing code such as: - *

    - *
    - *     buffer.clear();
    - *     channel.write(buffer);
    - * 
    - *

    - * Which looks as if it should write no data, but in fact writes the buffer worth of garbage. - *

    - *

    - * The BufferUtils class provides a set of utilities that operate on the convention that ByteBuffers - * will always be left, passed in an API or returned from a method in the flush mode - ie with - * valid data between the pos and limit. This convention is adopted so as to avoid confusion as to - * what state a buffer is in and to avoid excessive copying of data that can result with the usage - * of compress.

    - *

    - * Thus this class provides alternate implementations of {@link #allocate(int)}, - * {@link #allocateDirect(int)} and {@link #clear(ByteBuffer)} that leave the buffer - * in flush mode. Thus the following tests will pass: - *

    - *
    - *     ByteBuffer buffer = BufferUtils.allocate(1024);
    - *     assert(buffer.remaining()==0);
    - *     BufferUtils.clear(buffer);
    - *     assert(buffer.remaining()==0);
    - * 
    - *

    If the BufferUtils methods {@link #fill(ByteBuffer, byte[], int, int)}, - * {@link #append(ByteBuffer, byte[], int, int)} or {@link #put(ByteBuffer, ByteBuffer)} are used, - * then the caller does not need to explicitly switch the buffer to fill mode. - * If the caller wishes to use other ByteBuffer bases libraries to fill a buffer, - * then they can use explicit calls of #flipToFill(ByteBuffer) and #flipToFlush(ByteBuffer, int) - * to change modes. Note because this convention attempts to avoid the copies of compact, the position - * is not set to zero on each fill cycle and so its value must be remembered: - *

    - *
    - *      int pos = BufferUtils.flipToFill(buffer);
    - *      try
    - *      {
    - *          buffer.put(data);
    - *      }
    - *      finally
    - *      {
    - *          flipToFlush(buffer, pos);
    - *      }
    - * 
    - *

    - * The flipToFill method will effectively clear the buffer if it is empty and will compact the buffer if there is no space. - *

    - */ -public class BufferUtils { - public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); - static final int TEMP_BUFFER_SIZE = 4096; - static final byte SPACE = 0x20; - static final byte MINUS = '-'; - static final byte[] DIGIT = {(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F'}; - private final static int[] decDivisors = {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1}; - private final static int[] hexDivisors = {0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 0x1}; - private final static long[] decDivisorsL = {1000000000000000000L, 100000000000000000L, 10000000000000000L, 1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L, 100000000000L, 10000000000L, 1000000000L, 100000000L, 10000000L, 1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L}; - - /** - * Allocate ByteBuffer in flush mode. - * The position and limit will both be zero, indicating that the buffer is - * empty and must be flipped before any data is put to it. - * - * @param capacity capacity of the allocated ByteBuffer - * @return Buffer - */ - public static ByteBuffer allocate(int capacity) { - ByteBuffer buf = ByteBuffer.allocate(capacity); - buf.limit(0); - return buf; - } - - /** - * Allocate ByteBuffer in flush mode. - * The position and limit will both be zero, indicating that the buffer is - * empty and in flush mode. - * - * @param capacity capacity of the allocated ByteBuffer - * @return Buffer - */ - public static ByteBuffer allocateDirect(int capacity) { - ByteBuffer buf = ByteBuffer.allocateDirect(capacity); - buf.limit(0); - return buf; - } - - /** - * Clear the buffer to be empty in flush mode. - * The position and limit are set to 0; - * - * @param buffer The buffer to clear. - */ - public static void clear(ByteBuffer buffer) { - if (buffer != null) { - buffer.position(0); - buffer.limit(0); - } - } - - /** - * Clear the buffer to be empty in fill mode. - * The position is set to 0 and the limit is set to the capacity. - * - * @param buffer The buffer to clear. - */ - public static void clearToFill(ByteBuffer buffer) { - if (buffer != null) { - buffer.position(0); - buffer.limit(buffer.capacity()); - } - } - - /** - * Flip the buffer to fill mode. - * The position is set to the first unused position in the buffer - * (the old limit) and the limit is set to the capacity. - * If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}. - * If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt - * to create space. - *

    - * This method is used as a replacement to {@link ByteBuffer#compact()}. - * - * @param buffer The buffer to flip - * @return The position of the valid data before the flipped position. This value should be - * passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)} - */ - public static int flipToFill(ByteBuffer buffer) { - int position = buffer.position(); - int limit = buffer.limit(); - if (position == limit) { - buffer.position(0); - buffer.limit(buffer.capacity()); - return 0; - } - - int capacity = buffer.capacity(); - if (limit == capacity) { - buffer.compact(); - return 0; - } - - buffer.position(limit); - buffer.limit(capacity); - return position; - } - - /** - * Flip the buffer to Flush mode. - * The limit is set to the first unused byte(the old position) and - * the position is set to the passed position. - *

    - * This method is used as a replacement of {@link Buffer#flip()}. - * - * @param buffer the buffer to be flipped - * @param position The position of valid data to flip to. This should - * be the return value of the previous call to {@link #flipToFill(ByteBuffer)} - */ - public static void flipToFlush(ByteBuffer buffer, int position) { - buffer.limit(buffer.position()); - buffer.position(position); - } - - /** - * Convert a ByteBuffer to a byte array. - * - * @param buffer The buffer to convert in flush mode. The buffer is not altered. - * @return An array of bytes duplicated from the buffer. - */ - public static byte[] toArray(ByteBuffer buffer) { - if (buffer.hasArray()) { - byte[] array = buffer.array(); - int from = buffer.arrayOffset() + buffer.position(); - return Arrays.copyOfRange(array, from, from + buffer.remaining()); - } else { - byte[] to = new byte[buffer.remaining()]; - buffer.slice().get(to); - return to; - } - } - - /** - * Convert a buffer collection to a byte array. - * - * @param buffers The buffer to convert in flush mode. The buffer is not altered. - * @return An array of bytes duplicated from the buffer. - */ - public static byte[] toArray(Collection buffers) { - List list = buffers.stream().map(BufferUtils::toArray).collect(Collectors.toList()); - int count = list.stream().mapToInt(arr -> arr.length).sum(); - if (count < 0) { - throw new IllegalArgumentException("The buffers are too big"); - } - byte[] result = new byte[count]; - int index = 0; - for (byte[] bytes : list) { - System.arraycopy(bytes, 0, result, index, bytes.length); - index += bytes.length; - } - return result; - } - - /** - * @param buf the buffer to check - * @return true if buf is equal to EMPTY_BUFFER - */ - public static boolean isTheEmptyBuffer(ByteBuffer buf) { - @SuppressWarnings("ReferenceEquality") - boolean isTheEmptyBuffer_ = (buf == EMPTY_BUFFER); - return isTheEmptyBuffer_; - } - - /** - * Check for an empty or null buffer. - * - * @param buf the buffer to check - * @return true if the buffer is null or empty. - */ - public static boolean isEmpty(ByteBuffer buf) { - return buf == null || buf.remaining() == 0; - } - - /** - * Check for a non null and non empty buffer. - * - * @param buf the buffer to check - * @return true if the buffer is not null and not empty. - */ - public static boolean hasContent(ByteBuffer buf) { - return buf != null && buf.remaining() > 0; - } - - /** - * Check for a non null and full buffer. - * - * @param buf the buffer to check - * @return true if the buffer is not null and the limit equals the capacity. - */ - public static boolean isFull(ByteBuffer buf) { - return buf != null && buf.limit() == buf.capacity(); - } - - /** - * Get remaining from null checked buffer - * - * @param buffer The buffer to get the remaining from, in flush mode. - * @return 0 if the buffer is null, else the bytes remaining in the buffer. - */ - public static int length(ByteBuffer buffer) { - return buffer == null ? 0 : buffer.remaining(); - } - - /** - * Get the space from the limit to the capacity - * - * @param buffer the buffer to get the space from - * @return space - */ - public static int space(ByteBuffer buffer) { - if (buffer == null) - return 0; - return buffer.capacity() - buffer.limit(); - } - - /** - * Compact the buffer - * - * @param buffer the buffer to compact - * @return true if the compact made a full buffer have space - */ - public static boolean compact(ByteBuffer buffer) { - if (buffer.position() == 0) - return false; - boolean full = buffer.limit() == buffer.capacity(); - buffer.compact().flip(); - return full && buffer.limit() < buffer.capacity(); - } - - /** - * Put data from one buffer into another, avoiding over/under flows - * - * @param from Buffer to take bytes from in flush mode - * @param to Buffer to put bytes to in fill mode. - * @return number of bytes moved - */ - public static int put(ByteBuffer from, ByteBuffer to) { - int put; - int remaining = from.remaining(); - if (remaining > 0) { - if (remaining <= to.remaining()) { - to.put(from); - put = remaining; - from.position(from.limit()); - } else if (from.hasArray()) { - put = to.remaining(); - to.put(from.array(), from.arrayOffset() + from.position(), put); - from.position(from.position() + put); - } else { - put = to.remaining(); - ByteBuffer slice = from.slice(); - slice.limit(put); - to.put(slice); - from.position(from.position() + put); - } - } else { - put = 0; - } - return put; - } - - /** - * Put data from one buffer into another, avoiding over/under flows - * - * @param from Buffer to take bytes from in flush mode - * @param to Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after. - * @return number of bytes moved - * @deprecated use {@link #append(ByteBuffer, ByteBuffer)} - */ - public static int flipPutFlip(ByteBuffer from, ByteBuffer to) { - return append(to, from); - } - - /** - * Append bytes to a buffer. - * - * @param to Buffer is flush mode - * @param b bytes to append - * @param off offset into byte - * @param len length to append - * @throws BufferOverflowException if unable to append buffer due to space limits - */ - public static void append(ByteBuffer to, byte[] b, int off, int len) throws BufferOverflowException { - int pos = flipToFill(to); - try { - to.put(b, off, len); - } finally { - flipToFlush(to, pos); - } - } - - /** - * Appends a byte to a buffer - * - * @param to Buffer is flush mode - * @param b byte to append - */ - public static void append(ByteBuffer to, byte b) { - int pos = flipToFill(to); - try { - to.put(b); - } finally { - flipToFlush(to, pos); - } - } - - /** - * Appends a buffer to a buffer - * - * @param to Buffer is flush mode - * @param b buffer to append - * @return The position of the valid data before the flipped position. - */ - public static int append(ByteBuffer to, ByteBuffer b) { - int pos = flipToFill(to); - try { - return put(b, to); - } finally { - flipToFlush(to, pos); - } - } - - /** - * Like append, but does not throw {@link BufferOverflowException} - * - * @param to Buffer The buffer to fill to. The buffer will be flipped to fill mode and then flipped back to flush mode. - * @param b bytes The bytes to fill - * @param off offset into bytes - * @param len length to fill - * @return the number of bytes taken from the buffer. - */ - public static int fill(ByteBuffer to, byte[] b, int off, int len) { - int pos = flipToFill(to); - try { - int remaining = to.remaining(); - int take = remaining < len ? remaining : len; - to.put(b, off, take); - return take; - } finally { - flipToFlush(to, pos); - } - } - - public static void readFrom(File file, ByteBuffer buffer) throws IOException { - try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { - FileChannel channel = raf.getChannel(); - long needed = raf.length(); - - while (needed > 0 && buffer.hasRemaining()) - needed = needed - channel.read(buffer); - } - } - - public static void readFrom(InputStream is, int needed, ByteBuffer buffer) throws IOException { - ByteBuffer tmp = allocate(8192); - - while (needed > 0 && buffer.hasRemaining()) { - int l = is.read(tmp.array(), 0, 8192); - if (l < 0) - break; - tmp.position(0); - tmp.limit(l); - buffer.put(tmp); - } - } - - public static void writeTo(ByteBuffer buffer, OutputStream out) throws IOException { - if (buffer.hasArray()) { - out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); - // update buffer position, in way similar to non-array version of writeTo - buffer.position(buffer.position() + buffer.remaining()); - } else { - byte[] bytes = new byte[TEMP_BUFFER_SIZE]; - while (buffer.hasRemaining()) { - int byteCountToWrite = Math.min(buffer.remaining(), TEMP_BUFFER_SIZE); - buffer.get(bytes, 0, byteCountToWrite); - out.write(bytes, 0, byteCountToWrite); - } - } - } - - /** - * Convert the buffer to an ISO-8859-1 String - * - * @param buffer The buffer to convert in flush mode. The buffer is unchanged - * @return The buffer as a string. - */ - public static String toString(ByteBuffer buffer) { - return toString(buffer, StandardCharsets.ISO_8859_1); - } - - /** - * Convert the buffer to an UTF-8 String - * - * @param buffer The buffer to convert in flush mode. The buffer is unchanged - * @return The buffer as a string. - */ - public static String toUTF8String(ByteBuffer buffer) { - return toString(buffer, StandardCharsets.UTF_8); - } - - /** - * Convert the buffer to an ISO-8859-1 String - * - * @param buffer The buffer to convert in flush mode. The buffer is unchanged - * @param charset The {@link Charset} to use to convert the bytes - * @return The buffer as a string. - */ - public static String toString(ByteBuffer buffer, Charset charset) { - if (buffer == null) - return null; - byte[] array = buffer.hasArray() ? buffer.array() : null; - if (array == null) { - byte[] to = new byte[buffer.remaining()]; - buffer.slice().get(to); - return new String(to, 0, to.length, charset); - } - return new String(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset); - } - - public static String toString(List buffers, Charset charset) { - ByteBuffer buffer = merge(buffers); - if (buffer.hasRemaining()) { - return toString(buffer, charset); - } else { - return ""; - } - } - - /** - * Convert a partial buffer to a String. - * - * @param buffer the buffer to convert - * @param position The position in the buffer to start the string from - * @param length The length of the buffer - * @param charset The {@link Charset} to use to convert the bytes - * @return The buffer as a string. - */ - public static String toString(ByteBuffer buffer, int position, int length, Charset charset) { - if (buffer == null) - return null; - byte[] array = buffer.hasArray() ? buffer.array() : null; - if (array == null) { - ByteBuffer ro = buffer.asReadOnlyBuffer(); - ro.position(position); - ro.limit(position + length); - byte[] to = new byte[length]; - ro.get(to); - return new String(to, 0, to.length, charset); - } - return new String(array, buffer.arrayOffset() + position, length, charset); - } - - /** - * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown - * - * @param buffer A buffer containing an integer in flush mode. The position is not changed. - * @return an int - */ - public static int toInt(ByteBuffer buffer) { - return toInt(buffer, buffer.position(), buffer.remaining()); - } - - /** - * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an - * IllegalArgumentException is thrown - * - * @param buffer A buffer containing an integer in flush mode. The position is not changed. - * @param position the position in the buffer to start reading from - * @param length the length of the buffer to use for conversion - * @return an int of the buffer bytes - */ - public static int toInt(ByteBuffer buffer, int position, int length) { - int val = 0; - boolean started = false; - boolean minus = false; - - int limit = position + length; - - if (length <= 0) - throw new NumberFormatException(toString(buffer, position, length, StandardCharsets.UTF_8)); - - for (int i = position; i < limit; i++) { - byte b = buffer.get(i); - if (b <= SPACE) { - if (started) - break; - } else if (b >= '0' && b <= '9') { - val = val * 10 + (b - '0'); - started = true; - } else if (b == MINUS && !started) { - minus = true; - } else - break; - } - - if (started) - return minus ? (-val) : val; - throw new NumberFormatException(toString(buffer)); - } - - /** - * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown - * - * @param buffer A buffer containing an integer in flush mode. The position is updated. - * @return an int - */ - public static int takeInt(ByteBuffer buffer) { - int val = 0; - boolean started = false; - boolean minus = false; - int i; - for (i = buffer.position(); i < buffer.limit(); i++) { - byte b = buffer.get(i); - if (b <= SPACE) { - if (started) - break; - } else if (b >= '0' && b <= '9') { - val = val * 10 + (b - '0'); - started = true; - } else if (b == MINUS && !started) { - minus = true; - } else - break; - } - - if (started) { - buffer.position(i); - return minus ? (-val) : val; - } - throw new NumberFormatException(toString(buffer)); - } - - /** - * Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown - * - * @param buffer A buffer containing an integer in flush mode. The position is not changed. - * @return an int - */ - public static long toLong(ByteBuffer buffer) { - long val = 0; - boolean started = false; - boolean minus = false; - - for (int i = buffer.position(); i < buffer.limit(); i++) { - byte b = buffer.get(i); - if (b <= SPACE) { - if (started) - break; - } else if (b >= '0' && b <= '9') { - val = val * 10L + (b - '0'); - started = true; - } else if (b == MINUS && !started) { - minus = true; - } else - break; - } - - if (started) - return minus ? (-val) : val; - throw new NumberFormatException(toString(buffer)); - } - - public static void putHexInt(ByteBuffer buffer, int n) { - if (n < 0) { - buffer.put((byte) '-'); - - if (n == Integer.MIN_VALUE) { - buffer.put((byte) (0x7f & '8')); - buffer.put((byte) (0x7f & '0')); - buffer.put((byte) (0x7f & '0')); - buffer.put((byte) (0x7f & '0')); - buffer.put((byte) (0x7f & '0')); - buffer.put((byte) (0x7f & '0')); - buffer.put((byte) (0x7f & '0')); - buffer.put((byte) (0x7f & '0')); - - return; - } - n = -n; - } - - if (n < 0x10) { - buffer.put(DIGIT[n]); - } else { - boolean started = false; - // This assumes constant time int arithmatic - for (int hexDivisor : hexDivisors) { - if (n < hexDivisor) { - if (started) - buffer.put((byte) '0'); - continue; - } - - started = true; - int d = n / hexDivisor; - buffer.put(DIGIT[d]); - n = n - d * hexDivisor; - } - } - } - - public static void putDecInt(ByteBuffer buffer, int n) { - if (n < 0) { - buffer.put((byte) '-'); - - if (n == Integer.MIN_VALUE) { - buffer.put((byte) '2'); - n = 147483648; - } else - n = -n; - } - - if (n < 10) { - buffer.put(DIGIT[n]); - } else { - boolean started = false; - // This assumes constant time int arithmatic - for (int decDivisor : decDivisors) { - if (n < decDivisor) { - if (started) - buffer.put((byte) '0'); - continue; - } - - started = true; - int d = n / decDivisor; - buffer.put(DIGIT[d]); - n = n - d * decDivisor; - } - } - } - - public static void putDecLong(ByteBuffer buffer, long n) { - if (n < 0) { - buffer.put((byte) '-'); - - if (n == Long.MIN_VALUE) { - buffer.put((byte) '9'); - n = 223372036854775808L; - } else - n = -n; - } - - if (n < 10) { - buffer.put(DIGIT[(int) n]); - } else { - boolean started = false; - // This assumes constant time int arithmatic - for (long aDecDivisorsL : decDivisorsL) { - if (n < aDecDivisorsL) { - if (started) - buffer.put((byte) '0'); - continue; - } - - started = true; - long d = n / aDecDivisorsL; - buffer.put(DIGIT[(int) d]); - n = n - d * aDecDivisorsL; - } - } - } - - public static ByteBuffer toBuffer(int value) { - ByteBuffer buf = ByteBuffer.allocate(32); - putDecInt(buf, value); - return buf; - } - - public static ByteBuffer toBuffer(long value) { - ByteBuffer buf = ByteBuffer.allocate(32); - putDecLong(buf, value); - return buf; - } - - public static ByteBuffer toBuffer(String s) { - return toBuffer(s, StandardCharsets.ISO_8859_1); - } - - public static ByteBuffer toBuffer(String s, Charset charset) { - if (s == null) - return EMPTY_BUFFER; - return toBuffer(s.getBytes(charset)); - } - - /** - * Create a new ByteBuffer using provided byte array. - * - * @param array the byte array to back buffer with. - * @return ByteBuffer with provided byte array, in flush mode - */ - public static ByteBuffer toBuffer(byte[] array) { - if (array == null) - return EMPTY_BUFFER; - return toBuffer(array, 0, array.length); - } - - /** - * Create a new ByteBuffer using the provided byte array. - * - * @param array the byte array to use. - * @param offset the offset within the byte array to use from - * @param length the length in bytes of the array to use - * @return ByteBuffer with provided byte array, in flush mode - */ - public static ByteBuffer toBuffer(byte[] array, int offset, int length) { - if (array == null) - return EMPTY_BUFFER; - return ByteBuffer.wrap(array, offset, length); - } - - public static ByteBuffer toDirectBuffer(String s) { - return toDirectBuffer(s, StandardCharsets.ISO_8859_1); - } - - public static ByteBuffer toDirectBuffer(String s, Charset charset) { - if (s == null) - return EMPTY_BUFFER; - byte[] bytes = s.getBytes(charset); - ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length); - buf.put(bytes); - buf.flip(); - return buf; - } - - public static ByteBuffer toMappedBuffer(File file) throws IOException { - try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ)) { - return channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); - } - } - - /** - * @param buffer the buffer to test - * @return {@code false} - * @deprecated don't use - there is no way to reliably tell if a ByteBuffer is mapped. - */ - @Deprecated - public static boolean isMappedBuffer(ByteBuffer buffer) { - return false; - } - - public static String toSummaryString(ByteBuffer buffer) { - if (buffer == null) - return "null"; - StringBuilder buf = new StringBuilder(); - buf.append("[p="); - buf.append(buffer.position()); - buf.append(",l="); - buf.append(buffer.limit()); - buf.append(",c="); - buf.append(buffer.capacity()); - buf.append(",r="); - buf.append(buffer.remaining()); - buf.append("]"); - return buf.toString(); - } - - public static String toDetailString(ByteBuffer[] buffer) { - StringBuilder builder = new StringBuilder(); - builder.append('['); - for (int i = 0; i < buffer.length; i++) { - if (i > 0) builder.append(','); - builder.append(toDetailString(buffer[i])); - } - builder.append(']'); - return builder.toString(); - } - - /** - * Convert Buffer to string ID independent of content - */ - private static void idString(ByteBuffer buffer, StringBuilder out) { - out.append(buffer.getClass().getSimpleName()); - out.append("@"); - if (buffer.hasArray() && buffer.arrayOffset() == 4) { - out.append('T'); - byte[] array = buffer.array(); - TypeUtils.toHex(array[0], out); - TypeUtils.toHex(array[1], out); - TypeUtils.toHex(array[2], out); - TypeUtils.toHex(array[3], out); - } else - out.append(Integer.toHexString(System.identityHashCode(buffer))); - } - - /** - * Convert Buffer to string ID independent of content - * - * @param buffer the buffet to generate a string ID from - * @return A string showing the buffer ID - */ - public static String toIDString(ByteBuffer buffer) { - StringBuilder buf = new StringBuilder(); - idString(buffer, buf); - return buf.toString(); - } - - /** - * Convert Buffer to a detail debug string of pointers and content - * - * @param buffer the buffer to generate a detail string from - * @return A string showing the pointers and content of the buffer - */ - public static String toDetailString(ByteBuffer buffer) { - if (buffer == null) - return "null"; - - StringBuilder buf = new StringBuilder(); - idString(buffer, buf); - buf.append("[p="); - buf.append(buffer.position()); - buf.append(",l="); - buf.append(buffer.limit()); - buf.append(",c="); - buf.append(buffer.capacity()); - buf.append(",r="); - buf.append(buffer.remaining()); - buf.append("]={"); - - appendDebugString(buf, buffer); - - buf.append("}"); - - return buf.toString(); - } - - private static void appendDebugString(StringBuilder buf, ByteBuffer buffer) { - try { - for (int i = 0; i < buffer.position(); i++) { - appendContentChar(buf, buffer.get(i)); - if (i == 16 && buffer.position() > 32) { - buf.append("..."); - i = buffer.position() - 16; - } - } - buf.append("<<<"); - for (int i = buffer.position(); i < buffer.limit(); i++) { - appendContentChar(buf, buffer.get(i)); - if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32) { - buf.append("..."); - i = buffer.limit() - 16; - } - } - buf.append(">>>"); - int limit = buffer.limit(); - buffer.limit(buffer.capacity()); - for (int i = limit; i < buffer.capacity(); i++) { - appendContentChar(buf, buffer.get(i)); - if (i == limit + 16 && buffer.capacity() > limit + 32) { - buf.append("..."); - i = buffer.capacity() - 16; - } - } - buffer.limit(limit); - } catch (Throwable x) { - buf.append("!!concurrent mod!!"); - } - } - - private static void appendContentChar(StringBuilder buf, byte b) { - if (b == '\\') - buf.append("\\\\"); - else if ((b >= 0x20) && (b <= 0x7E)) // limit to 7-bit printable US-ASCII character space - buf.append((char) b); - else if (b == '\r') - buf.append("\\r"); - else if (b == '\n') - buf.append("\\n"); - else if (b == '\t') - buf.append("\\t"); - else - buf.append("\\x").append(TypeUtils.toHexString(b)); - } - - /** - * Convert buffer to a Hex Summary String. - * - * @param buffer the buffer to generate a hex byte summary from - * @return A string showing a summary of the content in hex - */ - public static String toHexSummary(ByteBuffer buffer) { - if (buffer == null) - return "null"; - StringBuilder buf = new StringBuilder(); - - buf.append("b[").append(buffer.remaining()).append("]="); - for (int i = buffer.position(); i < buffer.limit(); i++) { - TypeUtils.toHex(buffer.get(i), buf); - if (i == buffer.position() + 24 && buffer.limit() > buffer.position() + 32) { - buf.append("..."); - i = buffer.limit() - 8; - } - } - return buf.toString(); - } - - /** - * Convert buffer to a Hex String. - * - * @param buffer the buffer to generate a hex byte summary from - * @return A hex string - */ - public static String toHexString(ByteBuffer buffer) { - if (buffer == null) - return "null"; - return TypeUtils.toHexString(toArray(buffer)); - } - - public static void putCRLF(ByteBuffer buffer) { - buffer.put((byte) 13); - buffer.put((byte) 10); - } - - public static boolean isPrefix(ByteBuffer prefix, ByteBuffer buffer) { - if (prefix.remaining() > buffer.remaining()) - return false; - int bi = buffer.position(); - for (int i = prefix.position(); i < prefix.limit(); i++) { - if (prefix.get(i) != buffer.get(bi++)) { - return false; - } - } - return true; - } - - public static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity) { - if (buffer == null) { - return allocate(capacity); - } - if (buffer.capacity() >= capacity) { - return buffer; - } - if (buffer.hasArray()) { - return ByteBuffer.wrap( - Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + capacity), - buffer.position(), buffer.remaining()); - } else { - ByteBuffer newBuffer = allocateDirect(capacity); - append(newBuffer, buffer); - flipToFill(buffer); - return newBuffer; - } - } - - public static ByteBuffer addCapacity(ByteBuffer buffer, int capacity) { - int srcPos = buffer.position(); - int newCapacity = srcPos + capacity; - ByteBuffer newBuffer; - if (buffer.hasArray()) { - newBuffer = ByteBuffer.wrap(Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + newCapacity)); - newBuffer.position(srcPos); - } else { - newBuffer = BufferUtils.allocateDirect(newCapacity); - BufferUtils.flipToFill(newBuffer); - BufferUtils.flipToFlush(buffer, 0); - newBuffer.put(buffer); - } - return newBuffer; - } - - public static ByteBuffer merge(List buffers) { - if (CollectionUtils.isEmpty(buffers)) return EMPTY_BUFFER; - - int size = buffers.stream().collect(Collectors.summingInt(ByteBuffer::remaining)); - if (size == 0) return EMPTY_BUFFER; - - ByteBuffer newBuffer = allocate(size); - int pos = flipToFill(newBuffer); - buffers.forEach(srcBuffer -> put(srcBuffer, newBuffer)); - flipToFlush(newBuffer, pos); - return newBuffer; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/io/ByteArrayOutputStream2.java b/firefly-common/src/main/java/com/fireflysource/common/io/ByteArrayOutputStream2.java deleted file mode 100644 index e80bdf3c9..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/io/ByteArrayOutputStream2.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.fireflysource.common.io; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; - -/** - * ByteArrayOutputStream with public internals - */ -public class ByteArrayOutputStream2 extends ByteArrayOutputStream { - public ByteArrayOutputStream2() { - super(); - } - - public ByteArrayOutputStream2(int size) { - super(size); - } - - public byte[] getBuf() { - return buf; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public void reset(int minSize) { - reset(); - if (buf.length < minSize) { - buf = new byte[minSize]; - } - } - - public void writeUnchecked(int b) { - buf[count++] = (byte) b; - } - - public String toString(Charset charset) { - return new String(buf, 0, count, charset); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/io/IO.java b/firefly-common/src/main/java/com/fireflysource/common/io/IO.java deleted file mode 100644 index 048fbea5d..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/io/IO.java +++ /dev/null @@ -1,425 +0,0 @@ -package com.fireflysource.common.io; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.channels.GatheringByteChannel; -import java.nio.charset.Charset; - -public class IO { - - public final static String CRLF = "\015\012"; - - public final static byte[] CRLF_BYTES = {(byte) '\015', (byte) '\012'}; - - public static final int bufferSize = 64 * 1024; - private static final NullOS NULL_STREAM = new NullOS(); - private static final ClosedIS CLOSED_STREAM = new ClosedIS(); - private static final NullWrite NULL_WRITER = new NullWrite(); - private static final PrintWriter NULL_PRINT_WRITER = new PrintWriter(NULL_WRITER); - - /** - * Copy Stream in to Stream out until EOF or exception. - * - * @param in the input stream to read from (until EOF) - * @param out the output stream to write to - * @throws IOException if unable to copy streams - */ - public static void copy(InputStream in, OutputStream out) throws IOException { - copy(in, out, -1); - } - - /** - * Copy Reader to Writer out until EOF or exception. - * - * @param in the read to read from (until EOF) - * @param out the writer to write to - * @throws IOException if unable to copy the streams - */ - public static void copy(Reader in, Writer out) throws IOException { - copy(in, out, -1); - } - - /** - * Copy Stream in to Stream for byteCount bytes or until EOF or exception. - * - * @param in the stream to read from - * @param out the stream to write to - * @param byteCount the number of bytes to copy - * @throws IOException if unable to copy the streams - */ - public static void copy(InputStream in, OutputStream out, long byteCount) throws IOException { - byte[] buffer = new byte[bufferSize]; - int len; - - if (byteCount >= 0) { - while (byteCount > 0) { - int max = byteCount < bufferSize ? (int) byteCount : bufferSize; - len = in.read(buffer, 0, max); - - if (len == -1) { - break; - } - byteCount -= len; - out.write(buffer, 0, len); - } - } else { - while (true) { - len = in.read(buffer, 0, bufferSize); - if (len < 0) { - break; - } - out.write(buffer, 0, len); - } - } - } - - /** - * Copy Reader to Writer for byteCount bytes or until EOF or exception. - * - * @param in the Reader to read from - * @param out the Writer to write to - * @param byteCount the number of bytes to copy - * @throws IOException if unable to copy streams - */ - public static void copy(Reader in, Writer out, long byteCount) throws IOException { - char[] buffer = new char[bufferSize]; - int len; - - if (byteCount >= 0) { - while (byteCount > 0) { - if (byteCount < bufferSize) { - len = in.read(buffer, 0, (int) byteCount); - } else { - len = in.read(buffer, 0, bufferSize); - } - if (len == -1) { - break; - } - byteCount -= len; - out.write(buffer, 0, len); - } - } else if (out instanceof PrintWriter) { - PrintWriter pout = (PrintWriter) out; - while (!pout.checkError()) { - len = in.read(buffer, 0, bufferSize); - if (len == -1) - break; - out.write(buffer, 0, len); - } - } else { - while (true) { - len = in.read(buffer, 0, bufferSize); - if (len == -1) { - break; - } - out.write(buffer, 0, len); - } - } - } - - /** - * Copy files or directories - * - * @param from the file to copy - * @param to the destination to copy to - * @throws IOException if unable to copy - */ - public static void copy(File from, File to) throws IOException { - if (from.isDirectory()) { - copyDir(from, to); - } else { - copyFile(from, to); - } - } - - public static void copyDir(File from, File to) throws IOException { - if (to.exists()) { - if (!to.isDirectory()) { - throw new IllegalArgumentException(to.toString()); - } - } else { - boolean success = to.mkdirs(); - if (!success) { - return; - } - } - - File[] files = from.listFiles(); - if (files != null) { - for (File file : files) { - String name = file.getName(); - if (".".equals(name) || "..".equals(name)) { - continue; - } - copy(file, new File(to, name)); - } - } - } - - public static void copyFile(File from, File to) throws IOException { - try (InputStream in = new FileInputStream(from); OutputStream out = new FileOutputStream(to)) { - copy(in, out); - } - } - - /** - * Read input stream to string. - * - * @param in the stream to read from (until EOF) - * @return the String parsed from stream (default Charset) - * @throws IOException if unable to read the stream (or handle the charset) - */ - public static String toString(InputStream in) throws IOException { - return toString(in, (Charset) null); - } - - /** - * Read input stream to string. - * - * @param in the stream to read from (until EOF) - * @param encoding the encoding to use (can be null to use default Charset) - * @return the String parsed from the stream - * @throws IOException if unable to read the stream (or handle the charset) - */ - public static String toString(InputStream in, String encoding) throws IOException { - return toString(in, encoding == null ? null : Charset.forName(encoding)); - } - - /** - * Read input stream to string. - * - * @param in the stream to read from (until EOF) - * @param encoding the Charset to use (can be null to use default Charset) - * @return the String parsed from the stream - * @throws IOException if unable to read the stream (or handle the charset) - */ - public static String toString(InputStream in, Charset encoding) throws IOException { - StringWriter writer = new StringWriter(); - InputStreamReader reader = encoding == null ? new InputStreamReader(in) : new InputStreamReader(in, encoding); - - copy(reader, writer); - return writer.toString(); - } - - /** - * Read input stream to string. - * - * @param in the reader to read from (until EOF) - * @return the String parsed from the reader - * @throws IOException if unable to read the stream (or handle the charset) - */ - public static String toString(Reader in) throws IOException { - StringWriter writer = new StringWriter(); - copy(in, writer); - return writer.toString(); - } - - /** - * Delete File. This delete will recursively delete directories - BE - * CAREFULL - * - * @param file The file (or directory) to be deleted. - * @return true if anything was deleted. (note: this does not mean that all - * content in a directory was deleted) - */ - public static boolean delete(File file) { - if (!file.exists()) { - return false; - } - if (file.isDirectory()) { - File[] files = file.listFiles(); - for (int i = 0; files != null && i < files.length; i++) { - delete(files[i]); - } - } - return file.delete(); - } - - /** - * Closes an arbitrary closable, and logs exceptions at ignore level - * - * @param closeable the closeable to close - */ - public static void close(Closeable closeable) { - try { - if (closeable != null) - closeable.close(); - } catch (IOException ignore) { - } - } - - /** - * closes an input stream, and logs exceptions - * - * @param is the input stream to close - */ - public static void close(InputStream is) { - close((Closeable) is); - } - - /** - * closes an output stream, and logs exceptions - * - * @param os the output stream to close - */ - public static void close(OutputStream os) { - close((Closeable) os); - } - - /** - * closes a reader, and logs exceptions - * - * @param reader the reader to close - */ - public static void close(Reader reader) { - close((Closeable) reader); - } - - /** - * closes a writer, and logs exceptions - * - * @param writer the writer to close - */ - public static void close(Writer writer) { - close((Closeable) writer); - } - - public static byte[] readBytes(InputStream in) throws IOException { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - copy(in, bout); - return bout.toByteArray(); - } - - /** - * A gathering write utility wrapper. - *

    - * This method wraps a gather write with a loop that handles the limitations - * of some operating systems that have a limit on the number of buffers - * written. The method loops on the write until either all the content is - * written or no progress is made. - * - * @param out The GatheringByteChannel to write to - * @param buffers The buffers to write - * @param offset The offset into the buffers array - * @param length The length in buffers to write - * @return The total bytes written - * @throws IOException if unable write to the GatheringByteChannel - */ - public static long write(GatheringByteChannel out, ByteBuffer[] buffers, int offset, int length) - throws IOException { - long total = 0; - write: - while (length > 0) { - // Write as much as we can - long wrote = out.write(buffers, offset, length); - - // If we can't write any more, give up - if (wrote == 0) - break; - - // count the total - total += wrote; - - // Look for unwritten content - for (int i = offset; i < buffers.length; i++) { - if (buffers[i].hasRemaining()) { - // loop with new offset and length; - length = length - (i - offset); - offset = i; - continue write; - } - } - length = 0; - } - - return total; - } - - /** - * @return An outputstream to nowhere - */ - public static OutputStream getNullStream() { - return NULL_STREAM; - } - - /** - * @return An outputstream to nowhere - */ - public static InputStream getClosedStream() { - return CLOSED_STREAM; - } - - /** - * @return An writer to nowhere - */ - public static Writer getNullWriter() { - return NULL_WRITER; - } - - /** - * @return An writer to nowhere - */ - public static PrintWriter getNullPrintWriter() { - return NULL_PRINT_WRITER; - } - - private static class NullOS extends OutputStream { - @Override - public void close() { - } - - @Override - public void flush() { - } - - @Override - public void write(byte[] b) { - } - - @Override - public void write(byte[] b, int i, int l) { - } - - @Override - public void write(int b) { - } - } - - private static class ClosedIS extends InputStream { - @Override - public int read() throws IOException { - return -1; - } - } - - private static class NullWrite extends Writer { - @Override - public void close() { - } - - @Override - public void flush() { - } - - @Override - public void write(char[] b) { - } - - @Override - public void write(char[] b, int o, int l) { - } - - @Override - public void write(int b) { - } - - @Override - public void write(String s) { - } - - @Override - public void write(String s, int o, int l) { - } - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/io/InputChannel.java b/firefly-common/src/main/java/com/fireflysource/common/io/InputChannel.java deleted file mode 100644 index 14787c9b1..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/io/InputChannel.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.common.io; - -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousChannel; -import java.util.concurrent.CompletableFuture; - -public interface InputChannel extends AsynchronousChannel, AsyncCloseable { - - int END_OF_STREAM_FLAG = -1; - - /** - * Read the content asynchronously. - * - * @param byteBuffer The buffer into which bytes are to be transferred. - * @return The number of bytes read. If return -1, it presents the end of the content. - */ - CompletableFuture read(ByteBuffer byteBuffer); - - /** - * Return the end of stream flag future wrap. - * - * @return The end of stream flag. - */ - default CompletableFuture endStream() { - CompletableFuture future = new CompletableFuture<>(); - future.complete(END_OF_STREAM_FLAG); - return future; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/io/OutputChannel.java b/firefly-common/src/main/java/com/fireflysource/common/io/OutputChannel.java deleted file mode 100644 index 703a1cdab..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/io/OutputChannel.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.common.io; - -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousChannel; -import java.util.concurrent.CompletableFuture; - -public interface OutputChannel extends AsynchronousChannel, AsyncCloseable { - - /** - * Write the content asynchronously. - * - * @param byteBuffer The buffer into which bytes are to be transferred. - * @return The number of bytes write. If return -1, it presents the end of the content. - */ - CompletableFuture write(ByteBuffer byteBuffer); - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibLoader.java b/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibLoader.java deleted file mode 100644 index 4d842c9fd..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibLoader.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.fireflysource.common.jni; - -import com.fireflysource.common.concurrent.AutoLock; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.HashSet; -import java.util.Set; - -public class JniLibLoader { - - private static final LazyLogger logger = SystemLogger.create(JniLibLoader.class); - private static final Set loadedLibs = new HashSet<>(); - private static final AutoLock lock = new AutoLock(); - - /** - * Load JNI lib by lib name. - * - * @param libName The lib name. - */ - public static void load(String libName) { - String libPath = getLibPath(libName); - loadByLibPath(libPath); - } - - /** - * Load JNI lib by lib file path. - * - * @param libPath The lib file path of the lib file. - */ - public static void loadByLibPath(String libPath) { - lock.lock(() -> { - if (libPath.startsWith("/")) { - throw new IllegalArgumentException("The lib path must be not start with /"); - } - - if (loadedLibs.contains(libPath)) { - logger.info("The lib is loaded. path: {}", libPath); - return; - } - - File file = createLibTempFile(libPath); - copyLibToTempFile(libPath, file); - - logger.info("Start to load lib. path: {}", libPath); - System.load(getLibCanonicalPath(libPath, file)); - loadedLibs.add(libPath); - logger.info("Load lib success. path: {}", libPath); - }); - } - - public static String getLibPath(String libName) { - String osName = System.getProperty("os.name").toLowerCase(); - String libSuffix; - String libDir; - if (osName.contains("mac")) { - libSuffix = ".dylib"; - libDir = "macos"; - } else if (osName.contains("win")) { - libSuffix = ".dll"; - libDir = "windows"; - } else { - libSuffix = ".so"; - libDir = "linux"; - } - return "lib/" + libDir + "/lib" + libName + libSuffix; - } - - public static String getLibFileName(String libPath) { - int pos = libPath.lastIndexOf("/"); - String libFileName; - if (pos >= 0) { - libFileName = libPath.substring(pos + 1); - } else { - libFileName = libPath; - } - return libFileName; - } - - private static String getLibCanonicalPath(String libPath, File file) { - String tempFilePath; - try { - tempFilePath = file.getCanonicalPath(); - } catch (IOException e) { - logger.error("Get lib temp file path exception.", e); - throw new JniLibTempFileException("get lib temp file path exception. path: " + libPath); - } - return tempFilePath; - } - - private static void copyLibToTempFile(String libPath, File file) { - try (InputStream input = JniLibLoader.class.getResourceAsStream("/" + libPath)) { - if (input == null) { - throw new JniLibNotFoundException("The lib not found. path: " + libPath); - } - - logger.info("Copy lib to temp file. lib path: {}, temp file: {}", libPath, file.toPath()); - Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - logger.error("Copy lib exception.", e); - throw new JniLibTempFileException("Copy lib exception. lib file path: " + file.toPath()); - } - } - - private static File createLibTempFile(String libPath) { - String libFileName = getLibFileName(libPath); - File file; - try { - file = Files.createTempFile("jni", libFileName).toFile(); - } catch (IOException e) { - logger.error("Create lib temp file exception.", e); - throw new JniLibTempFileException("create lib temp file exception. file name: " + libFileName); - } - file.deleteOnExit(); - return file; - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibNotFoundException.java b/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibNotFoundException.java deleted file mode 100644 index 14d0fb342..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fireflysource.common.jni; - -public class JniLibNotFoundException extends RuntimeException { - public JniLibNotFoundException(String message) { - super(message); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibTempFileException.java b/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibTempFileException.java deleted file mode 100644 index e8e50c3bc..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/jni/JniLibTempFileException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fireflysource.common.jni; - -public class JniLibTempFileException extends RuntimeException { - public JniLibTempFileException(String message) { - super(message); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/lifecycle/AbstractLifeCycle.java b/firefly-common/src/main/java/com/fireflysource/common/lifecycle/AbstractLifeCycle.java deleted file mode 100644 index f247112c5..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/lifecycle/AbstractLifeCycle.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.fireflysource.common.lifecycle; - -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class AbstractLifeCycle implements LifeCycle { - - protected AtomicBoolean start = new AtomicBoolean(false); - - @Override - public boolean isStarted() { - return start.get(); - } - - @Override - public boolean isStopped() { - return !start.get(); - } - - @Override - public void start() { - if (start.compareAndSet(false, true)) { - init(); - } - } - - @Override - public void stop() { - if (start.compareAndSet(true, false)) { - try { - destroy(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - abstract protected void init(); - - abstract protected void destroy(); - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/lifecycle/LifeCycle.java b/firefly-common/src/main/java/com/fireflysource/common/lifecycle/LifeCycle.java deleted file mode 100644 index 97f1a63d8..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/lifecycle/LifeCycle.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fireflysource.common.lifecycle; - -public interface LifeCycle { - void start(); - - void stop(); - - boolean isStarted(); - - boolean isStopped(); -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/lifecycle/ShutdownTasks.java b/firefly-common/src/main/java/com/fireflysource/common/lifecycle/ShutdownTasks.java deleted file mode 100644 index 2cd097e02..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/lifecycle/ShutdownTasks.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.common.lifecycle; - -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class ShutdownTasks { - - private static final Queue tasks = new ConcurrentLinkedQueue<>(); - - static { - Runtime.getRuntime().addShutdownHook(new Thread(ShutdownTasks::stop, "Firefly-Shutdown-Tasks-Thread")); - } - - public static void register(Runnable runnable) { - tasks.add(runnable); - } - - public static boolean remove(Runnable runnable) { - return tasks.remove(runnable); - } - - public static void stop() { - while (true) { - Runnable task = tasks.poll(); - if (task == null) { - break; - } - - try { - task.run(); - } catch (Exception e) { - System.out.println(e.getMessage()); - } - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/math/MathUtils.java b/firefly-common/src/main/java/com/fireflysource/common/math/MathUtils.java deleted file mode 100644 index a37ddb698..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/math/MathUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.common.math; - -public class MathUtils { - private MathUtils() { - } - - /** - * Returns whether the sum of the arguments overflows an {@code int}. - * - * @param a the first value - * @param b the second value - * @return whether the sum of the arguments overflows an {@code int} - */ - public static boolean sumOverflows(int a, int b) { - try { - Math.addExact(a, b); - return false; - } catch (ArithmeticException x) { - return true; - } - } - - /** - * Returns the sum of its arguments, capping to {@link Long#MAX_VALUE} if they overflow. - * - * @param a the first value - * @param b the second value - * @return the sum of the values, capped to {@link Long#MAX_VALUE} - */ - public static long cappedAdd(long a, long b) { - try { - return Math.addExact(a, b); - } catch (ArithmeticException x) { - return Long.MAX_VALUE; - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/object/Assert.java b/firefly-common/src/main/java/com/fireflysource/common/object/Assert.java deleted file mode 100644 index edd9b3702..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/object/Assert.java +++ /dev/null @@ -1,590 +0,0 @@ -package com.fireflysource.common.object; - -import com.fireflysource.common.collection.CollectionUtils; -import com.fireflysource.common.string.StringUtils; - -import java.util.Collection; -import java.util.Map; -import java.util.function.Supplier; - -/** - * Assertion utility class that assists in validating arguments. - * - *

    Useful for identifying programmer errors early and clearly at runtime. - * - *

    For example, if the contract of a public method states it does not - * allow {@code null} arguments, {@code Assert} can be used to validate that - * contract. Doing this clearly indicates a contract violation when it - * occurs and protects the class's invariants. - * - *

    Typically used to validate method arguments rather than configuration - * properties, to check for cases that are usually programmer errors rather - * than configuration errors. In contrast to configuration initialization - * code, there is usually no point in falling back to defaults in such methods. - * - *

    This class is similar to JUnit's assertion library. If an argument value is - * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). - * For example: - * - *

    - * Assert.notNull(clazz, "The class must not be null");
    - * Assert.isTrue(i > 0, "The value must be greater than zero");
    - * - *

    Mainly for internal use within the framework; consider - * Apache's Commons Lang - * for a more comprehensive suite of {@code String} utilities. - * - * @author Keith Donald - * @author Juergen Hoeller - * @author Sam Brannen - * @author Colin Sampaleanu - * @author Rob Harrop - * @since 1.1.2 - */ -public abstract class Assert { - - /** - * Assert a boolean expression, throwing an {@code IllegalStateException} - * if the expression evaluates to {@code false}. - *

    Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} - * on an assertion failure. - *

    Assert.state(id == null, "The id property must not already be initialized");
    - * - * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * @throws IllegalStateException if {@code expression} is {@code false} - */ - public static void state(boolean expression, String message) { - if (!expression) { - throw new IllegalStateException(message); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalStateException} - * if the expression evaluates to {@code false}. - *

    Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} - * on an assertion failure. - *

    -     * Assert.state(id == null,
    -     *     () -> "ID for " + entity.getName() + " must not already be initialized");
    -     * 
    - * - * @param expression a boolean expression - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalStateException if {@code expression} is {@code false} - * @since 5.0 - */ - public static void state(boolean expression, Supplier messageSupplier) { - if (!expression) { - throw new IllegalStateException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalArgumentException} - * if the expression evaluates to {@code false}. - *
    Assert.isTrue(i > 0, "The value must be greater than zero");
    - * - * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if {@code expression} is {@code false} - */ - public static void isTrue(boolean expression, String message) { - if (!expression) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalArgumentException} - * if the expression evaluates to {@code false}. - *
    -     * Assert.isTrue(i > 0, () -> "The value '" + i + "' must be greater than zero");
    -     * 
    - * - * @param expression a boolean expression - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if {@code expression} is {@code false} - * @since 5.0 - */ - public static void isTrue(boolean expression, Supplier messageSupplier) { - if (!expression) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an object is {@code null}. - *
    Assert.isNull(value, "The value must be null");
    - * - * @param object the object to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object is not {@code null} - */ - public static void isNull(Object object, String message) { - if (object != null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is {@code null}. - *
    -     * Assert.isNull(value, () -> "The value '" + value + "' must be null");
    -     * 
    - * - * @param object the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object is not {@code null} - * @since 5.0 - */ - public static void isNull(Object object, Supplier messageSupplier) { - if (object != null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an object is not {@code null}. - *
    Assert.notNull(clazz, "The class must not be null");
    - * - * @param object the object to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object is {@code null} - */ - public static void notNull(Object object, String message) { - if (object == null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is not {@code null}. - *
    -     * Assert.notNull(clazz, () -> "The class '" + clazz.getName() + "' must not be null");
    -     * 
    - * - * @param object the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object is {@code null} - * @since 5.0 - */ - public static void notNull(Object object, Supplier messageSupplier) { - if (object == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that the given String is not empty; that is, - * it must not be {@code null} and not the empty String. - *
    Assert.hasLength(name, "Name must not be empty");
    - * - * @param text the String to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the text is empty - * @see StringUtils#hasLength - */ - public static void hasLength(String text, String message) { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given String is not empty; that is, - * it must not be {@code null} and not the empty String. - *
    -     * Assert.hasLength(name, () -> "Name for account '" + account.getId() + "' must not be empty");
    -     * 
    - * - * @param text the String to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the text is empty - * @see StringUtils#hasLength - * @since 5.0 - */ - public static void hasLength(String text, Supplier messageSupplier) { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that the given String contains valid text content; that is, it must not - * be {@code null} and must contain at least one non-whitespace character. - *
    Assert.hasText(name, "'name' must not be empty");
    - * - * @param text the String to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the text does not contain valid text content - * @see StringUtils#hasText - */ - public static void hasText(String text, String message) { - if (!StringUtils.hasText(text)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given String contains valid text content; that is, it must not - * be {@code null} and must contain at least one non-whitespace character. - *
    -     * Assert.hasText(name, () -> "Name for account '" + account.getId() + "' must not be empty");
    -     * 
    - * - * @param text the String to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the text does not contain valid text content - * @see StringUtils#hasText - * @since 5.0 - */ - public static void hasText(String text, Supplier messageSupplier) { - if (!StringUtils.hasText(text)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that the given text does not contain the given substring. - *
    Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
    - * - * @param textToSearch the text to search - * @param substring the substring to find within the text - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the text contains the substring - */ - public static void doesNotContain(String textToSearch, String substring, String message) { - if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) && - textToSearch.contains(substring)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given text does not contain the given substring. - *
    -     * Assert.doesNotContain(name, forbidden, () -> "Name must not contain '" + forbidden + "'");
    -     * 
    - * - * @param textToSearch the text to search - * @param substring the substring to find within the text - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the text contains the substring - * @since 5.0 - */ - public static void doesNotContain(String textToSearch, String substring, Supplier messageSupplier) { - if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) && - textToSearch.contains(substring)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an array contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
    Assert.notEmpty(array, "The array must contain elements");
    - * - * @param array the array to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object array is {@code null} or contains no elements - */ - public static void notEmpty(Object[] array, String message) { - if (CollectionUtils.isEmpty(array)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an array contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
    -     * Assert.notEmpty(array, () -> "The " + arrayType + " array must contain elements");
    -     * 
    - * - * @param array the array to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object array is {@code null} or contains no elements - * @since 5.0 - */ - public static void notEmpty(Object[] array, Supplier messageSupplier) { - if (CollectionUtils.isEmpty(array)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an array contains no {@code null} elements. - *

    Note: Does not complain if the array is empty! - *

    Assert.noNullElements(array, "The array must contain non-null elements");
    - * - * @param array the array to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the object array contains a {@code null} element - */ - public static void noNullElements(Object[] array, String message) { - if (array != null) { - for (Object element : array) { - if (element == null) { - throw new IllegalArgumentException(message); - } - } - } - } - - /** - * Assert that an array contains no {@code null} elements. - *

    Note: Does not complain if the array is empty! - *

    -     * Assert.noNullElements(array, () -> "The " + arrayType + " array must contain non-null elements");
    -     * 
    - * - * @param array the array to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the object array contains a {@code null} element - * @since 5.0 - */ - public static void noNullElements(Object[] array, Supplier messageSupplier) { - if (array != null) { - for (Object element : array) { - if (element == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - } - } - - /** - * Assert that a collection contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
    Assert.notEmpty(collection, "Collection must contain elements");
    - * - * @param collection the collection to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the collection is {@code null} or - * contains no elements - */ - public static void notEmpty(Collection collection, String message) { - if (CollectionUtils.isEmpty(collection)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that a collection contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
    -     * Assert.notEmpty(collection, () -> "The " + collectionType + " collection must contain elements");
    -     * 
    - * - * @param collection the collection to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the collection is {@code null} or - * contains no elements - * @since 5.0 - */ - public static void notEmpty(Collection collection, Supplier messageSupplier) { - if (CollectionUtils.isEmpty(collection)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that a Map contains entries; that is, it must not be {@code null} - * and must contain at least one entry. - *
    Assert.notEmpty(map, "Map must contain entries");
    - * - * @param map the map to check - * @param message the exception message to use if the assertion fails - * @throws IllegalArgumentException if the map is {@code null} or contains no entries - */ - public static void notEmpty(Map map, String message) { - if (CollectionUtils.isEmpty(map)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that a Map contains entries; that is, it must not be {@code null} - * and must contain at least one entry. - *
    -     * Assert.notEmpty(map, () -> "The " + mapType + " map must contain entries");
    -     * 
    - * - * @param map the map to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * @throws IllegalArgumentException if the map is {@code null} or contains no entries - * @since 5.0 - */ - public static void notEmpty(Map map, Supplier messageSupplier) { - if (CollectionUtils.isEmpty(map)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
    Assert.instanceOf(Foo.class, foo, "Foo expected");
    - * - * @param type the type to check against - * @param obj the object to check - * @param message a message which will be prepended to provide further context. - * If it is empty or ends in ":" or ";" or "," or ".", a full exception message - * will be appended. If it ends in a space, the name of the offending object's - * type will be appended. In any other case, a ":" with a space and the name - * of the offending object's type will be appended. - * @throws IllegalArgumentException if the object is not an instance of type - */ - public static void isInstanceOf(Class type, Object obj, String message) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - instanceCheckFailed(type, obj, message); - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
    -     * Assert.instanceOf(Foo.class, foo, () -> "Processing " + Foo.class.getSimpleName() + ":");
    -     * 
    - * - * @param type the type to check against - * @param obj the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails. See {@link #isInstanceOf(Class, Object, String)} for details. - * @throws IllegalArgumentException if the object is not an instance of type - * @since 5.0 - */ - public static void isInstanceOf(Class type, Object obj, Supplier messageSupplier) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - instanceCheckFailed(type, obj, nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
    Assert.instanceOf(Foo.class, foo);
    - * - * @param type the type to check against - * @param obj the object to check - * @throws IllegalArgumentException if the object is not an instance of type - */ - public static void isInstanceOf(Class type, Object obj) { - isInstanceOf(type, obj, ""); - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
    Assert.isAssignable(Number.class, myClass, "Number expected");
    - * - * @param superType the super type to check against - * @param subType the sub type to check - * @param message a message which will be prepended to provide further context. - * If it is empty or ends in ":" or ";" or "," or ".", a full exception message - * will be appended. If it ends in a space, the name of the offending sub type - * will be appended. In any other case, a ":" with a space and the name of the - * offending sub type will be appended. - * @throws IllegalArgumentException if the classes are not assignable - */ - public static void isAssignable(Class superType, Class subType, String message) { - notNull(superType, "Super type to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - assignableCheckFailed(superType, subType, message); - } - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
    -     * Assert.isAssignable(Number.class, myClass, () -> "Processing " + myAttributeName + ":");
    -     * 
    - * - * @param superType the super type to check against - * @param subType the sub type to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails. See {@link #isAssignable(Class, Class, String)} for details. - * @throws IllegalArgumentException if the classes are not assignable - * @since 5.0 - */ - public static void isAssignable(Class superType, Class subType, Supplier messageSupplier) { - notNull(superType, "Super type to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
    Assert.isAssignable(Number.class, myClass);
    - * - * @param superType the super type to check - * @param subType the sub type to check - * @throws IllegalArgumentException if the classes are not assignable - */ - public static void isAssignable(Class superType, Class subType) { - isAssignable(superType, subType, ""); - } - - - private static void instanceCheckFailed(Class type, Object obj, String msg) { - String className = (obj != null ? obj.getClass().getName() : "null"); - String result = ""; - boolean defaultMessage = true; - if (StringUtils.hasLength(msg)) { - if (endsWithSeparator(msg)) { - result = msg + " "; - } else { - result = messageWithTypeName(msg, className); - defaultMessage = false; - } - } - if (defaultMessage) { - result = result + ("Object of class [" + className + "] must be an instance of " + type); - } - throw new IllegalArgumentException(result); - } - - private static void assignableCheckFailed(Class superType, Class subType, String msg) { - String result = ""; - boolean defaultMessage = true; - if (StringUtils.hasLength(msg)) { - if (endsWithSeparator(msg)) { - result = msg + " "; - } else { - result = messageWithTypeName(msg, subType); - defaultMessage = false; - } - } - if (defaultMessage) { - result = result + (subType + " is not assignable to " + superType); - } - throw new IllegalArgumentException(result); - } - - private static boolean endsWithSeparator(String msg) { - return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith(".")); - } - - private static String messageWithTypeName(String msg, Object typeName) { - return msg + (msg.endsWith(" ") ? "" : ": ") + typeName; - } - - - private static String nullSafeGet(Supplier messageSupplier) { - return (messageSupplier != null ? messageSupplier.get() : null); - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/object/TypeUtils.java b/firefly-common/src/main/java/com/fireflysource/common/object/TypeUtils.java deleted file mode 100644 index 5ac058bc4..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/object/TypeUtils.java +++ /dev/null @@ -1,441 +0,0 @@ -package com.fireflysource.common.object; - -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -/** - * TYPE Utilities. Provides various static utility methods for manipulating - * types and their string representations. - */ -public class TypeUtils { - - public static final Class[] NO_ARGS = new Class[]{}; - public static final int CR = '\015'; - public static final int LF = '\012'; - - private static final HashMap> name2Class = new HashMap<>(); - /* ------------------------------------------------------------ */ - private static final HashMap, String> class2Name = new HashMap<>(); - private static final HashMap, Method> class2Value = new HashMap<>(); - - static { - name2Class.put("boolean", Boolean.TYPE); - name2Class.put("byte", Byte.TYPE); - name2Class.put("char", Character.TYPE); - name2Class.put("double", Double.TYPE); - name2Class.put("float", Float.TYPE); - name2Class.put("int", Integer.TYPE); - name2Class.put("long", Long.TYPE); - name2Class.put("short", Short.TYPE); - name2Class.put("void", Void.TYPE); - - name2Class.put("java.lang.Boolean.TYPE", Boolean.TYPE); - name2Class.put("java.lang.Byte.TYPE", Byte.TYPE); - name2Class.put("java.lang.Character.TYPE", Character.TYPE); - name2Class.put("java.lang.Double.TYPE", Double.TYPE); - name2Class.put("java.lang.Float.TYPE", Float.TYPE); - name2Class.put("java.lang.Integer.TYPE", Integer.TYPE); - name2Class.put("java.lang.Long.TYPE", Long.TYPE); - name2Class.put("java.lang.Short.TYPE", Short.TYPE); - name2Class.put("java.lang.Void.TYPE", Void.TYPE); - - name2Class.put("java.lang.Boolean", Boolean.class); - name2Class.put("java.lang.Byte", Byte.class); - name2Class.put("java.lang.Character", Character.class); - name2Class.put("java.lang.Double", Double.class); - name2Class.put("java.lang.Float", Float.class); - name2Class.put("java.lang.Integer", Integer.class); - name2Class.put("java.lang.Long", Long.class); - name2Class.put("java.lang.Short", Short.class); - - name2Class.put("Boolean", Boolean.class); - name2Class.put("Byte", Byte.class); - name2Class.put("Character", Character.class); - name2Class.put("Double", Double.class); - name2Class.put("Float", Float.class); - name2Class.put("Integer", Integer.class); - name2Class.put("Long", Long.class); - name2Class.put("Short", Short.class); - - name2Class.put(null, Void.TYPE); - name2Class.put("string", String.class); - name2Class.put("String", String.class); - name2Class.put("java.lang.String", String.class); - } - - static { - class2Name.put(Boolean.TYPE, "boolean"); - class2Name.put(Byte.TYPE, "byte"); - class2Name.put(Character.TYPE, "char"); - class2Name.put(Double.TYPE, "double"); - class2Name.put(Float.TYPE, "float"); - class2Name.put(Integer.TYPE, "int"); - class2Name.put(Long.TYPE, "long"); - class2Name.put(Short.TYPE, "short"); - class2Name.put(Void.TYPE, "void"); - - class2Name.put(Boolean.class, "java.lang.Boolean"); - class2Name.put(Byte.class, "java.lang.Byte"); - class2Name.put(Character.class, "java.lang.Character"); - class2Name.put(Double.class, "java.lang.Double"); - class2Name.put(Float.class, "java.lang.Float"); - class2Name.put(Integer.class, "java.lang.Integer"); - class2Name.put(Long.class, "java.lang.Long"); - class2Name.put(Short.class, "java.lang.Short"); - - class2Name.put(null, "void"); - class2Name.put(String.class, "java.lang.String"); - } - - static { - try { - Class[] s = {String.class}; - - class2Value.put(Boolean.TYPE, - Boolean.class.getMethod("valueOf", s)); - class2Value.put(Byte.TYPE, - Byte.class.getMethod("valueOf", s)); - class2Value.put(Double.TYPE, - Double.class.getMethod("valueOf", s)); - class2Value.put(Float.TYPE, - Float.class.getMethod("valueOf", s)); - class2Value.put(Integer.TYPE, - Integer.class.getMethod("valueOf", s)); - class2Value.put(Long.TYPE, - Long.class.getMethod("valueOf", s)); - class2Value.put(Short.TYPE, - Short.class.getMethod("valueOf", s)); - - class2Value.put(Boolean.class, - Boolean.class.getMethod("valueOf", s)); - class2Value.put(Byte.class, - Byte.class.getMethod("valueOf", s)); - class2Value.put(Double.class, - Double.class.getMethod("valueOf", s)); - class2Value.put(Float.class, - Float.class.getMethod("valueOf", s)); - class2Value.put(Integer.class, - Integer.class.getMethod("valueOf", s)); - class2Value.put(Long.class, - Long.class.getMethod("valueOf", s)); - class2Value.put(Short.class, - Short.class.getMethod("valueOf", s)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - /** - * Array to List. - *

    - * Works like {@link Arrays#asList(Object...)}, but handles null arrays. - * - * @param a the array to convert to a list - * @param the array and list entry type - * @return a list backed by the array. - */ - public static List asList(T[] a) { - if (a == null) - return Collections.emptyList(); - return Arrays.asList(a); - } - - - /** - * Class from a canonical name for a type. - * - * @param name A class or type name. - * @return A class , which may be a primitive TYPE field.. - */ - public static Class fromName(String name) { - return name2Class.get(name); - } - - /** - * The canonical name for a type. - * - * @param type A class , which may be a primitive TYPE field. - * @return Canonical name. - */ - public static String toName(Class type) { - return class2Name.get(type); - } - - /** - * Convert String value to instance. - * - * @param type The class of the instance, which may be a primitive TYPE field. - * @param value The value as string. - * @return The value as an Object. - */ - public static Object valueOf(Class type, String value) { - try { - if (type.equals(String.class)) - return value; - - Method m = class2Value.get(type); - if (m != null) - return m.invoke(null, value); - - if (type.equals(Character.TYPE) || - type.equals(Character.class)) - return value.charAt(0); - - Constructor c = type.getConstructor(String.class); - return c.newInstance(value); - } catch (NoSuchMethodException | IllegalAccessException | InstantiationException x) { - throw new IllegalArgumentException(x); - } catch (InvocationTargetException x) { - if (x.getTargetException() instanceof Error) - throw (Error) x.getTargetException(); - - } - return null; - } - - /** - * Convert String value to instance. - * - * @param type classname or type (eg int) - * @param value The value as a string. - * @return The value as an Object. - */ - public static Object valueOf(String type, String value) { - return valueOf(fromName(type), value); - } - - /** - * Parse an int from a substring. - * Negative numbers are not handled. - * - * @param s String - * @param offset Offset within string - * @param length Length of integer or -1 for remainder of string - * @param base base of the integer - * @return the parsed integer - * @throws NumberFormatException if the string cannot be parsed - */ - public static int parseInt(String s, int offset, int length, int base) - throws NumberFormatException { - int value = 0; - - if (length < 0) - length = s.length() - offset; - - for (int i = 0; i < length; i++) { - char c = s.charAt(offset + i); - - int digit = convertHexDigit((int) c); - if (digit < 0 || digit >= base) - throw new NumberFormatException(s.substring(offset, offset + length)); - value = value * base + digit; - } - return value; - } - - /** - * Parse an int from a byte array of ascii characters. - * Negative numbers are not handled. - * - * @param b byte array - * @param offset Offset within string - * @param length Length of integer or -1 for remainder of string - * @param base base of the integer - * @return the parsed integer - * @throws NumberFormatException if the array cannot be parsed into an integer - */ - public static int parseInt(byte[] b, int offset, int length, int base) - throws NumberFormatException { - int value = 0; - - if (length < 0) - length = b.length - offset; - - for (int i = 0; i < length; i++) { - char c = (char) (0xff & b[offset + i]); - - int digit = c - '0'; - if (digit < 0 || digit >= base || digit >= 10) { - digit = 10 + c - 'A'; - if (digit < 10 || digit >= base) - digit = 10 + c - 'a'; - } - if (digit < 0 || digit >= base) - throw new NumberFormatException(new String(b, offset, length)); - value = value * base + digit; - } - return value; - } - - public static byte[] parseBytes(String s, int base) { - byte[] bytes = new byte[s.length() / 2]; - for (int i = 0; i < s.length(); i += 2) - bytes[i / 2] = (byte) TypeUtils.parseInt(s, i, 2, base); - return bytes; - } - - public static String toString(byte[] bytes, int base) { - StringBuilder buf = new StringBuilder(); - for (byte b : bytes) { - int bi = 0xff & b; - int c = '0' + (bi / base) % base; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - c = '0' + bi % base; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - } - return buf.toString(); - } - - /** - * @param c An ASCII encoded character 0-9 a-f A-F - * @return The byte value of the character 0-16. - */ - public static byte convertHexDigit(byte c) { - byte b = (byte) ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); - if (b < 0 || b > 15) - throw new NumberFormatException("!hex " + c); - return b; - } - - /** - * @param c An ASCII encoded character 0-9 a-f A-F - * @return The byte value of the character 0-16. - */ - public static int convertHexDigit(char c) { - int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); - if (d < 0 || d > 15) - throw new NumberFormatException("!hex " + c); - return d; - } - - /** - * @param c An ASCII encoded character 0-9 a-f A-F - * @return The byte value of the character 0-16. - */ - public static int convertHexDigit(int c) { - int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); - if (d < 0 || d > 15) - throw new NumberFormatException("!hex " + c); - return d; - } - - public static void toHex(byte b, Appendable buf) { - try { - int d = 0xf & ((0xF0 & b) >> 4); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & b; - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static void toHex(int value, Appendable buf) throws IOException { - int d = 0xf & ((0xF0000000 & value) >> 28); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & ((0x0F000000 & value) >> 24); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & ((0x00F00000 & value) >> 20); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & ((0x000F0000 & value) >> 16); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & ((0x0000F000 & value) >> 12); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & ((0x00000F00 & value) >> 8); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & ((0x000000F0 & value) >> 4); - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & value; - buf.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - - Integer.toString(0, 36); - } - - public static void toHex(long value, Appendable buf) throws IOException { - toHex((int) (value >> 32), buf); - toHex((int) value, buf); - } - - public static String toHexString(byte b) { - return toHexString(new byte[]{b}, 0, 1); - } - - public static String toHexString(byte[] b) { - return toHexString(b, 0, b.length); - } - - public static String toHexString(byte[] b, int offset, int length) { - StringBuilder buf = new StringBuilder(); - for (int i = offset; i < offset + length; i++) { - int bi = 0xff & b[i]; - int c = '0' + (bi / 16) % 16; - if (c > '9') - c = 'A' + (c - '0' - 10); - buf.append((char) c); - c = '0' + bi % 16; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - } - return buf.toString(); - } - - public static byte[] fromHexString(String s) { - if (s.length() % 2 != 0) - throw new IllegalArgumentException(s); - byte[] array = new byte[s.length() / 2]; - for (int i = 0; i < array.length; i++) { - int b = Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16); - array[i] = (byte) (0xff & b); - } - return array; - } - - public static void dump(Class c) { - System.err.println("Dump: " + c); - dump(c.getClassLoader()); - } - - public static void dump(ClassLoader cl) { - System.err.println("Dump Loaders:"); - while (cl != null) { - System.err.println(" loader " + cl); - cl = cl.getParent(); - } - } - - /** - * @param o Object to test for true - * @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true. - */ - public static boolean isTrue(Object o) { - if (o == null) - return false; - if (o instanceof Boolean) - return (Boolean) o; - return Boolean.parseBoolean(o.toString()); - } - - /** - * @param o Object to test for false - * @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false. - */ - public static boolean isFalse(Object o) { - if (o == null) - return false; - if (o instanceof Boolean) - return !(Boolean) o; - return "false".equalsIgnoreCase(o.toString()); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/ref/Cleaner.java b/firefly-common/src/main/java/com/fireflysource/common/ref/Cleaner.java deleted file mode 100644 index 8dcb546c3..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/ref/Cleaner.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.fireflysource.common.ref; - -import java.lang.ref.PhantomReference; -import java.lang.ref.ReferenceQueue; -import java.util.Objects; -import java.util.concurrent.ThreadFactory; - - -/** - * {@code Cleaner} manages a set of object references and corresponding cleaning actions. - *

    - * Cleaning actions are {@link #register(Object object, Runnable action) registered} - * to run after the cleaner is notified that the object has become - * phantom reachable. - * The cleaner uses {@link PhantomReference} and {@link ReferenceQueue} to be - * notified when the reachability - * changes. - *

    - * Each cleaner operates independently, managing the pending cleaning actions - * and handling threading and termination when the cleaner is no longer in use. - * Registering an object reference and corresponding cleaning action returns - * a {@link Cleanable Cleanable}. The most efficient use is to explicitly invoke - * the {@link Cleanable#clean clean} method when the object is closed or - * no longer needed. - * The cleaning action is a {@link Runnable} to be invoked at most once when - * the object has become phantom reachable unless it has already been explicitly cleaned. - * Note that the cleaning action must not refer to the object being registered. - * If so, the object will not become phantom reachable and the cleaning action - * will not be invoked automatically. - *

    - * The execution of the cleaning action is performed - * by a thread associated with the cleaner. - * All exceptions thrown by the cleaning action are ignored. - * The cleaner and other cleaning actions are not affected by - * exceptions in a cleaning action. - * The thread runs until all registered cleaning actions have - * completed and the cleaner itself is reclaimed by the garbage collector. - *

    - * The behavior of cleaners during {@link System#exit(int) System.exit} - * is implementation specific. No guarantees are made relating - * to whether cleaning actions are invoked or not. - *

    - * Unless otherwise noted, passing a {@code null} argument to a constructor or - * method in this class will cause a - * {@link java.lang.NullPointerException NullPointerException} to be thrown. - * - * @apiNote The cleaning action is invoked only after the associated object becomes - * phantom reachable, so it is important that the object implementing the - * cleaning action does not hold references to the object. - * In this example, a static class encapsulates the cleaning state and action. - * An "inner" class, anonymous or not, must not be used because it implicitly - * contains a reference to the outer instance, preventing it from becoming - * phantom reachable. - * The choice of a new cleaner or sharing an existing cleaner is determined - * by the use case. - *

    - * If the CleaningExample is used in a try-finally block then the - * {@code close} method calls the cleaning action. - * If the {@code close} method is not called, the cleaning action is called - * by the Cleaner when the CleaningExample instance has become phantom reachable. - *

    {@code
    - * public class CleaningExample implements AutoCloseable {
    - *        // A cleaner, preferably one shared within a library
    - *        private static final Cleaner cleaner = ;
    - *
    - *        static class State implements Runnable {
    - *
    - *            State(...) {
    - *                // initialize State needed for cleaning action
    - *            }
    - *
    - *            public void run() {
    - *                // cleanup action accessing State, executed at most once
    - *            }
    - *        }
    - *
    - *        private final State state;
    - *        private final Cleaner.Cleanable cleanable;
    - *
    - *        public CleaningExample() {
    - *            this.state = new State(...);
    - *            this.cleanable = cleaner.register(this, state);
    - *        }
    - *
    - *        public void close() {
    - *            cleanable.clean();
    - *        }
    - *    }
    - * }
    - * The cleaning action could be a lambda but all too easily will capture - * the object reference, by referring to fields of the object being cleaned, - * preventing the object from becoming phantom reachable. - * Using a static nested class, as above, will avoid accidentally retaining the - * object reference. - *

    - * - * Cleaning actions should be prepared to be invoked concurrently with - * other cleaning actions. - * Typically the cleaning actions should be very quick to execute - * and not block. If the cleaning action blocks, it may delay processing - * other cleaning actions registered to the same cleaner. - * All cleaning actions registered to a cleaner should be mutually compatible. - * @since 9 - */ -public class Cleaner { - - /** - * The Cleaner implementation. - */ - final CleanerImpl impl; - - static { - CleanerImpl.setCleanerImplAccess(cleaner -> cleaner.impl); - } - - /** - * Construct a Cleaner implementation and start it. - */ - private Cleaner() { - impl = new CleanerImpl(); - } - - /** - * Returns a new {@code Cleaner}. - *

    - * The cleaner creates a {@link Thread#setDaemon(boolean) daemon thread} - * to process the phantom reachable objects and to invoke cleaning actions. - * The {@linkplain java.lang.Thread#getContextClassLoader context class loader} - * of the thread is set to the - * {@link ClassLoader#getSystemClassLoader() system class loader}. - * The thread has no permissions, enforced only if a - * {@link java.lang.System#setSecurityManager(SecurityManager) SecurityManager is set}. - *

    - * The cleaner terminates when it is phantom reachable and all of the - * registered cleaning actions are complete. - * - * @return a new {@code Cleaner} - * @throws SecurityException if the current thread is not allowed to - * create or start the thread. - */ - public static Cleaner create() { - Cleaner cleaner = new Cleaner(); - cleaner.impl.start(cleaner, r -> new Thread(r, "Firefly-Cleaner-Thread")); - return cleaner; - } - - /** - * Returns a new {@code Cleaner} using a {@code Thread} from the {@code ThreadFactory}. - *

    - * A thread from the thread factory's {@link ThreadFactory#newThread(Runnable) newThread} - * method is set to be a {@link Thread#setDaemon(boolean) daemon thread} - * and started to process phantom reachable objects and invoke cleaning actions. - * On each call the {@link ThreadFactory#newThread(Runnable) thread factory} - * must provide a Thread that is suitable for performing the cleaning actions. - *

    - * The cleaner terminates when it is phantom reachable and all of the - * registered cleaning actions are complete. - * - * @param threadFactory a {@code ThreadFactory} to return a new {@code Thread} - * to process cleaning actions - * @return a new {@code Cleaner} - * @throws IllegalThreadStateException if the thread from the thread - * factory was {@link Thread.State#NEW not a new thread}. - * @throws SecurityException if the current thread is not allowed to - * create or start the thread. - */ - public static Cleaner create(ThreadFactory threadFactory) { - Objects.requireNonNull(threadFactory, "threadFactory"); - Cleaner cleaner = new Cleaner(); - cleaner.impl.start(cleaner, threadFactory); - return cleaner; - } - - /** - * Registers an object and a cleaning action to run when the object - * becomes phantom reachable. - * Refer to the API Note above for - * cautions about the behavior of cleaning actions. - * - * @param obj the object to monitor - * @param action a {@code Runnable} to invoke when the object becomes phantom reachable - * @return a {@code Cleanable} instance - */ - public Cleanable register(Object obj, Runnable action) { - Objects.requireNonNull(obj, "obj"); - Objects.requireNonNull(action, "action"); - return new CleanerImpl.PhantomCleanableRef(obj, this, action); - } - - /** - * {@code Cleanable} represents an object and a - * cleaning action registered in a {@code Cleaner}. - * - * @since 9 - */ - public interface Cleanable { - /** - * Unregisters the cleanable and invokes the cleaning action. - * The cleanable cleaning action is invoked at most once - * regardless of the number of calls to {@code clean}. - */ - void clean(); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/ref/CleanerImpl.java b/firefly-common/src/main/java/com/fireflysource/common/ref/CleanerImpl.java deleted file mode 100644 index a7d4a83f2..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/ref/CleanerImpl.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.fireflysource.common.ref; - -import com.fireflysource.common.ref.Cleaner.Cleanable; - -import java.lang.ref.ReferenceQueue; -import java.util.concurrent.ThreadFactory; -import java.util.function.Function; - -/** - * CleanerImpl manages a set of object references and corresponding cleaning actions. - * CleanerImpl provides the functionality of {@link com.fireflysource.common.ref.Cleaner}. - */ -public final class CleanerImpl implements Runnable { - - /** - * An object to access the CleanerImpl from a Cleaner; set by Cleaner init. - */ - private static Function cleanerImplAccess = null; - - /** - * Heads of a CleanableList for each reference type. - */ - final PhantomCleanable phantomCleanableList; - - // The ReferenceQueue of pending cleaning actions - final ReferenceQueue queue; - - /** - * Called by Cleaner static initialization to provide the function - * to map from Cleaner to CleanerImpl. - * - * @param access a function to map from Cleaner to CleanerImpl - */ - public static void setCleanerImplAccess(Function access) { - if (cleanerImplAccess == null) { - cleanerImplAccess = access; - } else { - throw new InternalError("cleanerImplAccess"); - } - } - - /** - * Called to get the CleanerImpl for a Cleaner. - * - * @param cleaner the cleaner - * @return the corresponding CleanerImpl - */ - static CleanerImpl getCleanerImpl(Cleaner cleaner) { - return cleanerImplAccess.apply(cleaner); - } - - /** - * Constructor for CleanerImpl. - */ - public CleanerImpl() { - queue = new ReferenceQueue<>(); - phantomCleanableList = new PhantomCleanableRef(); - } - - /** - * Starts the Cleaner implementation. - * Ensure this is the CleanerImpl for the Cleaner. - * When started waits for Cleanables to be queued. - * - * @param cleaner the cleaner - * @param threadFactory the thread factory - */ - public void start(Cleaner cleaner, ThreadFactory threadFactory) { - if (getCleanerImpl(cleaner) != this) { - throw new AssertionError("wrong cleaner"); - } - // schedule a nop cleaning action for the cleaner, so the associated thread - // will continue to run at least until the cleaner is reclaimable. - new CleanerCleanable(cleaner); - - // now that there's at least one cleaning action, for the cleaner, - // we can start the associated thread, which runs until - // all cleaning actions have been run. - Thread thread = threadFactory.newThread(this); - thread.setDaemon(true); - thread.start(); - } - - /** - * Process queued Cleanables as long as the cleanable lists are not empty. - * A Cleanable is in one of the lists for each Object and for the Cleaner - * itself. - * Terminates when the Cleaner is no longer reachable and - * has been cleaned and there are no more Cleanable instances - * for which the object is reachable. - *

    - * If the thread is a ManagedLocalsThread, the threadlocals - * are erased before each cleanup - */ - @Override - public void run() { - while (!phantomCleanableList.isListEmpty()) { - try { - // Wait for a Ref, with a timeout to avoid getting hung - // due to a race with clear/clean - Cleanable ref = (Cleanable) queue.remove(60 * 1000L); - if (ref != null) { - ref.clean(); - } - } catch (Throwable e) { - // ignore exceptions from the cleanup action - // (including interruption of cleanup thread) - } - } - } - - /** - * Perform cleaning on an unreachable PhantomReference. - */ - public static final class PhantomCleanableRef extends PhantomCleanable { - private final Runnable action; - - /** - * Constructor for a phantom cleanable reference. - * - * @param obj the object to monitor - * @param cleaner the cleaner - * @param action the action Runnable - */ - public PhantomCleanableRef(Object obj, Cleaner cleaner, Runnable action) { - super(obj, cleaner); - this.action = action; - } - - /** - * Constructor used only for root of phantom cleanable list. - */ - PhantomCleanableRef() { - super(); - this.action = null; - } - - @Override - protected void performCleanup() { - action.run(); - } - - /** - * Prevent access to referent even when it is still alive. - * - * @throws UnsupportedOperationException always - */ - @Override - public Object get() { - throw new UnsupportedOperationException("get"); - } - - /** - * Direct clearing of the referent is not supported. - * - * @throws UnsupportedOperationException always - */ - @Override - public void clear() { - throw new UnsupportedOperationException("clear"); - } - } - - /** - * A PhantomCleanable implementation for tracking the Cleaner itself. - */ - static final class CleanerCleanable extends PhantomCleanable { - CleanerCleanable(Cleaner cleaner) { - super(cleaner, cleaner); - } - - @Override - protected void performCleanup() { - // no action - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/ref/PhantomCleanable.java b/firefly-common/src/main/java/com/fireflysource/common/ref/PhantomCleanable.java deleted file mode 100644 index 8e6a7bf82..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/ref/PhantomCleanable.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.fireflysource.common.ref; - -import com.fireflysource.common.concurrent.AutoLock; - -import java.lang.ref.PhantomReference; -import java.util.Objects; - -/** - * PhantomCleanable subclasses efficiently encapsulate cleanup state and - * the cleaning action. - * Subclasses implement the abstract {@link #performCleanup()} method - * to provide the cleaning action. - * When constructed, the object reference and the {@link Cleaner.Cleanable Cleanable} - * are registered with the {@link Cleaner}. - * The Cleaner invokes {@link Cleaner.Cleanable#clean() clean} after the - * referent becomes phantom reachable. - */ -public abstract class PhantomCleanable extends PhantomReference - implements Cleaner.Cleanable { - - /** - * Links to previous and next in a doubly-linked list. - */ - PhantomCleanable prev = this, next = this; - - /** - * The list of PhantomCleanable; synchronizes insert and remove. - */ - private final PhantomCleanable list; - private final AutoLock lock = new AutoLock(); - - /** - * Constructs new {@code PhantomCleanable} with - * {@code non-null referent} and {@code non-null cleaner}. - * The {@code cleaner} is not retained; it is only used to - * register the newly constructed {@link Cleaner.Cleanable Cleanable}. - * - * @param referent the referent to track - * @param cleaner the {@code Cleaner} to register with - */ - public PhantomCleanable(T referent, Cleaner cleaner) { - super(Objects.requireNonNull(referent), CleanerImpl.getCleanerImpl(cleaner).queue); - this.list = CleanerImpl.getCleanerImpl(cleaner).phantomCleanableList; - insert(); - } - - /** - * Construct a new root of the list; not inserted. - */ - PhantomCleanable() { - super(null, null); - this.list = this; - } - - /** - * Insert this PhantomCleanable after the list head. - */ - private void insert() { - lock.lock(() -> { - prev = list; - next = list.next; - next.prev = this; - list.next = this; - }); - } - - /** - * Remove this PhantomCleanable from the list. - * - * @return true if Cleanable was removed or false if not because - * it had already been removed before - */ - private boolean remove() { - return lock.lock(() -> { - if (next != this) { - next.prev = prev; - prev.next = next; - prev = this; - next = this; - return true; - } - return false; - }); - } - - /** - * Returns true if the list's next reference refers to itself. - * - * @return true if the list is empty - */ - boolean isListEmpty() { - return lock.lock(() -> list == list.next); - } - - /** - * Unregister this PhantomCleanable and invoke {@link #performCleanup()}, - * ensuring at-most-once semantics. - */ - @Override - public final void clean() { - if (remove()) { - super.clear(); - performCleanup(); - } - } - - /** - * Unregister this PhantomCleanable and clear the reference. - * Due to inherent concurrency, {@link #performCleanup()} may still be invoked. - */ - @Override - public void clear() { - if (remove()) { - super.clear(); - } - } - - /** - * The {@code performCleanup} abstract method is overridden - * to implement the cleaning logic. - * The {@code performCleanup} method should not be called except - * by the {@link #clean} method which ensures at most once semantics. - */ - protected abstract void performCleanup(); - - /** - * This method always throws {@link UnsupportedOperationException}. - * Enqueuing details of {@link Cleaner.Cleanable} - * are a private implementation detail. - * - * @throws UnsupportedOperationException always - */ - @SuppressWarnings("deprecation") - @Override - public final boolean isEnqueued() { - throw new UnsupportedOperationException("isEnqueued"); - } - - /** - * This method always throws {@link UnsupportedOperationException}. - * Enqueuing details of {@link Cleaner.Cleanable} - * are a private implementation detail. - * - * @throws UnsupportedOperationException always - */ - @Override - public final boolean enqueue() { - throw new UnsupportedOperationException("enqueue"); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/reflection/ReflectionUtils.java b/firefly-common/src/main/java/com/fireflysource/common/reflection/ReflectionUtils.java deleted file mode 100644 index d129e4bf4..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/reflection/ReflectionUtils.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.fireflysource.common.reflection; - -import com.fireflysource.common.bytecode.*; -import com.fireflysource.common.service.ServiceUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author Pengtao Qiu - */ -public class ReflectionUtils { - public static final ProxyFactory DEFAULT_PROXY_FACTORY = ServiceUtils.loadService(ProxyFactory.class, JavassistReflectionProxyFactory.INSTANCE); - private static final Map, Map> getterCache = new ConcurrentHashMap<>(); - private static final Map, Map> setterCache = new ConcurrentHashMap<>(); - private static final Map, Map> propertyCache = new ConcurrentHashMap<>(); - - public static void setProperty(Object obj, String property, Object value) throws Throwable { - getFields(obj.getClass()).get(property).set(obj, value); - } - - public static Object getProperty(Object obj, String property) throws Throwable { - return getFields(obj.getClass()).get(property).get(obj); - } - - /** - * Invokes an object's "setter" method by property name - * - * @param obj The instance of an object - * @param property The property name of this object - * @param value The parameter of "setter" method that you want to set - * @throws Throwable A runtime exception - */ - public static void set(Object obj, String property, Object value) throws Throwable { - getSetterMethod(obj.getClass(), property).invoke(obj, value); - } - - /** - * Invokes an object's "getter" method by property name - * - * @param obj The instance of an object - * @param property The property name of this object - * @return The value of this property - * @throws Throwable A runtime exception - */ - public static Object get(Object obj, String property) throws Throwable { - return getGetterMethod(obj.getClass(), property).invoke(obj); - } - - public static Object arrayGet(Object array, int index) { - return getArrayProxy(array.getClass()).get(array, index); - } - - public static void arraySet(Object array, int index, Object value) { - getArrayProxy(array.getClass()).set(array, index, value); - } - - public static int arraySize(Object array) { - return getArrayProxy(array.getClass()).size(array); - } - - public static ArrayProxy getArrayProxy(Class clazz) { - return DEFAULT_PROXY_FACTORY.getArrayProxy(clazz); - } - - public static FieldProxy getFieldProxy(Field field) { - return DEFAULT_PROXY_FACTORY.getFieldProxy(field); - } - - public static MethodProxy getMethodProxy(Method method) { - return DEFAULT_PROXY_FACTORY.getMethodProxy(method); - } - - /** - * Gets the all interface names of this class - * - * @param c The class of one object - * @return Returns the all interface names - */ - public static String[] getInterfaceNames(Class c) { - Class[] interfaces = c.getInterfaces(); - List names = new ArrayList<>(); - for (Class i : interfaces) { - names.add(i.getName()); - } - return names.toArray(new String[0]); - } - - public static String getPropertyName(Method method) { - String methodName = method.getName(); - int index = (methodName.charAt(0) == 'i' ? 2 : 3); - char c = methodName.charAt(index); - if (Character.isLowerCase(c)) { - return methodName.substring(index); - } else { - return Character.toLowerCase(methodName.charAt(index)) + methodName.substring(index + 1); - } - } - - public static Method getSetterMethod(Class clazz, String propertyName) { - return getSetterMethods(clazz).get(propertyName); - } - - public static Map getSetterMethods(Class clazz) { - return setterCache.computeIfAbsent(clazz, key -> getSetterMethods(key, null)); - } - - public static Map getSetterMethods(Class clazz, BeanMethodFilter filter) { - Map setMethodMap = new HashMap<>(); - Method[] methods = clazz.getMethods(); - - for (Method method : methods) { - method.setAccessible(true); - - if (Modifier.isStatic(method.getModifiers())) continue; - if (Modifier.isAbstract(method.getModifiers())) continue; - if (method.getName().length() < 4) continue; - if (!method.getName().startsWith("set")) continue; - if (!method.getReturnType().equals(Void.TYPE)) continue; - if (method.getParameterTypes().length != 1) continue; - - String propertyName = getPropertyName(method); - if (filter == null || filter.accept(propertyName, method)) { - setMethodMap.put(propertyName, method); - } - } - return setMethodMap; - } - - public static Method getGetterMethod(Class clazz, String propertyName) { - return getGetterMethods(clazz).get(propertyName); - } - - public static Map getGetterMethods(Class clazz) { - return getterCache.computeIfAbsent(clazz, key -> getGetterMethods(key, null)); - } - - public static Map getGetterMethods(Class clazz, BeanMethodFilter filter) { - Map getMethodMap = new HashMap<>(); - Method[] methods = clazz.getMethods(); - for (Method method : methods) { - method.setAccessible(true); - - if (Modifier.isStatic(method.getModifiers())) continue; - if (Modifier.isAbstract(method.getModifiers())) continue; - if (method.getName().equals("getClass")) continue; - if (!(method.getName().startsWith("is") || method.getName().startsWith("get"))) continue; - if (method.getParameterTypes().length != 0) continue; - if (method.getReturnType() == void.class) continue; - - String methodName = method.getName(); - int index = (methodName.charAt(0) == 'i' ? 2 : 3); - if (methodName.length() < index + 1) continue; - - String propertyName = getPropertyName(method); - if (filter == null || filter.accept(propertyName, method)) { - getMethodMap.put(propertyName, method); - } - } - return getMethodMap; - } - - public static Map getFields(Class clazz) { - return propertyCache.computeIfAbsent(clazz, key -> getFields(key, null)); - } - - public static Map getFields(Class clazz, BeanFieldFilter filter) { - Map fieldMap = new HashMap<>(); - Field[] fields = clazz.getFields(); - for (Field field : fields) { - field.setAccessible(true); - if (Modifier.isStatic(field.getModifiers())) continue; - - String propertyName = field.getName(); - if (filter == null || filter.accept(propertyName, field)) - fieldMap.put(propertyName, field); - } - return fieldMap; - } - - public static void copy(Object src, Object dest) { - Map getterMethodMap = getGetterMethods(src.getClass()); - Map setterMethodMap = getSetterMethods(dest.getClass()); - - for (Map.Entry entry : setterMethodMap.entrySet()) { - Method getter = getterMethodMap.get(entry.getKey()); - if (getter == null) continue; - - try { - Object obj = getter.invoke(src); - if (obj != null) { - entry.getValue().invoke(dest, obj); - } - } catch (Throwable t) { - System.err.println("copy object exception, " + t.getMessage()); - } - } - } - - @FunctionalInterface - public interface BeanMethodFilter { - boolean accept(String propertyName, Method method); - } - - @FunctionalInterface - public interface BeanFieldFilter { - boolean accept(String propertyName, Field field); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/service/ServiceUtils.java b/firefly-common/src/main/java/com/fireflysource/common/service/ServiceUtils.java deleted file mode 100644 index c0e7ab1b5..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/service/ServiceUtils.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.common.service; - -import java.util.ServiceLoader; - -/** - * @author Pengtao Qiu - */ -abstract public class ServiceUtils { - - public static T loadService(Class clazz, T defaultService) { - T service = null; - ServiceLoader serviceLoader = ServiceLoader.load(clazz); - for (T t : serviceLoader) { - service = t; - } - if (service == null) { - service = defaultService; - } - return service; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/slf4j/LazyLogger.java b/firefly-common/src/main/java/com/fireflysource/common/slf4j/LazyLogger.java deleted file mode 100644 index 146102830..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/slf4j/LazyLogger.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.fireflysource.common.slf4j; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.helpers.MarkerIgnoringBase; - -import java.util.function.Supplier; - -/** - * @author Pengtao Qiu - */ -public class LazyLogger extends MarkerIgnoringBase { - - private Logger logger; - - public LazyLogger() { - } - - public LazyLogger(Logger logger) { - this.logger = logger; - } - - public static LazyLogger create() { - StackTraceElement[] arr = Thread.currentThread().getStackTrace(); - return new LazyLogger(LoggerFactory.getLogger(arr[2].getClassName())); - } - - public static LazyLogger create(String name) { - return new LazyLogger(LoggerFactory.getLogger(name)); - } - - public static LazyLogger create(Class clazz) { - return new LazyLogger(LoggerFactory.getLogger(clazz)); - } - - public Logger getLogger() { - return logger; - } - - public void setLogger(Logger logger) { - this.logger = logger; - } - - public void trace(Supplier supplier) { - if (logger.isTraceEnabled()) { - logger.trace(supplier.get()); - } - } - - public void trace(Throwable t, Supplier supplier) { - if (logger.isTraceEnabled()) { - logger.trace(supplier.get(), t); - } - } - - public void debug(Supplier supplier) { - if (logger.isDebugEnabled()) { - logger.debug(supplier.get()); - } - } - - public void debug(Throwable t, Supplier supplier) { - if (logger.isDebugEnabled()) { - logger.debug(supplier.get(), t); - } - } - - public void info(Supplier supplier) { - if (logger.isInfoEnabled()) { - logger.info(supplier.get()); - } - } - - public void info(Throwable t, Supplier supplier) { - if (logger.isInfoEnabled()) { - logger.info(supplier.get(), t); - } - } - - public void warn(Supplier supplier) { - if (logger.isWarnEnabled()) { - logger.warn(supplier.get()); - } - } - - public void warn(Throwable t, Supplier supplier) { - if (logger.isWarnEnabled()) { - logger.warn(supplier.get(), t); - } - } - - public void error(Supplier supplier) { - if (logger.isErrorEnabled()) { - logger.error(supplier.get()); - } - } - - public void error(Throwable t, Supplier supplier) { - if (logger.isErrorEnabled()) { - logger.error(supplier.get(), t); - } - } - - @Override - public boolean isTraceEnabled() { - return logger.isTraceEnabled(); - } - - @Override - public void trace(String msg) { - logger.trace(msg); - } - - @Override - public void trace(String format, Object arg) { - logger.trace(format, arg); - } - - @Override - public void trace(String format, Object arg1, Object arg2) { - logger.trace(format, arg1, arg2); - } - - @Override - public void trace(String format, Object... arguments) { - logger.trace(format, arguments); - } - - @Override - public void trace(String msg, Throwable t) { - logger.trace(msg, t); - } - - public boolean isDebugEnabled() { - return logger.isDebugEnabled(); - } - - @Override - public void debug(String msg) { - logger.debug(msg); - } - - @Override - public void debug(String format, Object arg) { - logger.debug(format, arg); - } - - @Override - public void debug(String format, Object arg1, Object arg2) { - logger.debug(format, arg1, arg2); - } - - @Override - public void debug(String format, Object... arguments) { - logger.debug(format, arguments); - } - - @Override - public void debug(String msg, Throwable t) { - logger.debug(msg, t); - } - - @Override - public boolean isInfoEnabled() { - return logger.isInfoEnabled(); - } - - @Override - public void info(String msg) { - logger.info(msg); - } - - @Override - public void info(String format, Object arg) { - logger.info(format, arg); - } - - @Override - public void info(String format, Object arg1, Object arg2) { - logger.info(format, arg1, arg2); - } - - @Override - public void info(String format, Object... arguments) { - logger.info(format, arguments); - } - - @Override - public void info(String msg, Throwable t) { - logger.info(msg, t); - } - - public boolean isWarnEnabled() { - return logger.isWarnEnabled(); - } - - @Override - public void warn(String msg) { - logger.warn(msg); - } - - @Override - public void warn(String format, Object arg) { - logger.warn(format, arg); - } - - @Override - public void warn(String format, Object... arguments) { - logger.warn(format, arguments); - } - - @Override - public void warn(String format, Object arg1, Object arg2) { - logger.warn(format, arg1, arg2); - } - - @Override - public void warn(String msg, Throwable t) { - logger.warn(msg, t); - } - - @Override - public boolean isErrorEnabled() { - return logger.isErrorEnabled(); - } - - @Override - public void error(String msg) { - logger.error(msg); - } - - @Override - public void error(String format, Object arg) { - logger.error(format, arg); - } - - @Override - public void error(String format, Object arg1, Object arg2) { - logger.error(format, arg1, arg2); - } - - @Override - public void error(String format, Object... arguments) { - logger.error(format, arguments); - } - - @Override - public void error(String msg, Throwable t) { - logger.error(msg, t); - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/string/Pattern.java b/firefly-common/src/main/java/com/fireflysource/common/string/Pattern.java deleted file mode 100644 index e24d7e5cd..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/string/Pattern.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.fireflysource.common.string; - -public abstract class Pattern { - - private static class Holder { - private static final AllMatch ALL_MATCH = new AllMatch(); - } - - /** - * Matches a string according to the specified pattern - * - * @param str Target string - * @return If it returns null, that represents matching failure, - * else it returns an array contains all strings matched. - */ - abstract public String[] match(String str); - - public static Pattern compile(String pattern, String wildcard) { - final boolean startWith = pattern.startsWith(wildcard); - final boolean endWith = pattern.endsWith(wildcard); - final String[] array = StringUtils.split(pattern, wildcard); - - switch (array.length) { - case 0: - return Holder.ALL_MATCH; - case 1: - if (startWith && endWith) - return new HeadAndTailMatch(array[0]); - - if (startWith) - return new HeadMatch(array[0]); - - if (endWith) - return new TailMatch(array[0]); - - return new EqualsMatch(pattern); - default: - return new MultipartMatch(startWith, endWith, array); - } - } - - - private static class MultipartMatch extends Pattern { - - private final boolean startWith, endWith; - private final String[] parts; - private int num; - - MultipartMatch(boolean startWith, boolean endWith, String[] parts) { - super(); - this.startWith = startWith; - this.endWith = endWith; - this.parts = parts; - num = parts.length - 1; - if (startWith) - num++; - if (endWith) - num++; - } - - @Override - public String[] match(String str) { - int currentIndex = -1; - int lastIndex = -1; - String[] ret = new String[num]; - - for (int i = 0; i < parts.length; i++) { - String part = parts[i]; - int j = startWith ? i : i - 1; - currentIndex = str.indexOf(part, lastIndex + 1); - - if (currentIndex > lastIndex) { - if (i != 0 || startWith) - ret[j] = str.substring(lastIndex + 1, currentIndex); - - lastIndex = currentIndex + part.length() - 1; - continue; - } - return null; - } - - if (endWith) - ret[num - 1] = str.substring(lastIndex + 1); - - return ret; - } - - } - - private static class TailMatch extends Pattern { - private final String part; - - TailMatch(String part) { - this.part = part; - } - - @Override - public String[] match(String str) { - int currentIndex = str.indexOf(part); - if (currentIndex == 0) { - return new String[]{str.substring(part.length())}; - } - return null; - } - } - - private static class HeadMatch extends Pattern { - private final String part; - - HeadMatch(String part) { - this.part = part; - } - - @Override - public String[] match(String str) { - int currentIndex = str.indexOf(part); - if (currentIndex != -1 && currentIndex + part.length() == str.length()) { - try { - return new String[]{str.substring(0, currentIndex)}; - } catch (Exception e) { - e.printStackTrace(); - System.out.println(str + ", " + currentIndex + ", " + part); - } - } - return null; - } - - - } - - private static class HeadAndTailMatch extends Pattern { - private final String part; - - HeadAndTailMatch(String part) { - this.part = part; - } - - @Override - public String[] match(String str) { - int currentIndex = str.indexOf(part); - if (currentIndex >= 0) { - return new String[]{str.substring(0, currentIndex), str.substring(currentIndex + part.length())}; - } - return null; - } - } - - private static class EqualsMatch extends Pattern { - private final String pattern; - - EqualsMatch(String pattern) { - this.pattern = pattern; - } - - @Override - public String[] match(String str) { - return pattern.equals(str) ? new String[0] : null; - } - } - - private static class AllMatch extends Pattern { - - @Override - public String[] match(String str) { - return new String[]{str}; - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/string/QuotedStringTokenizer.java b/firefly-common/src/main/java/com/fireflysource/common/string/QuotedStringTokenizer.java deleted file mode 100644 index 6e5531ae0..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/string/QuotedStringTokenizer.java +++ /dev/null @@ -1,472 +0,0 @@ -package com.fireflysource.common.string; - -import com.fireflysource.common.object.TypeUtils; - -import java.io.IOException; -import java.util.Arrays; -import java.util.NoSuchElementException; -import java.util.StringTokenizer; - -/** - * StringTokenizer with Quoting support. - *

    - * This class is a copy of the java.util.StringTokenizer API and the behaviour - * is the same, except that single and double quoted string values are - * recognised. Delimiters within quotes are not considered delimiters. Quotes - * can be escaped with '\'. - * - * @see StringTokenizer - */ -public class QuotedStringTokenizer extends StringTokenizer { - private final static String DEFAULT_DELIMITER = "\t\n\r"; - private static final char[] escapes = new char[32]; - - static { - Arrays.fill(escapes, (char) 0xFFFF); - escapes['\b'] = 'b'; - escapes['\t'] = 't'; - escapes['\n'] = 'n'; - escapes['\f'] = 'f'; - escapes['\r'] = 'r'; - } - - private String string; - private String delimiter = DEFAULT_DELIMITER; - private boolean returnQuotes; - private boolean returnDelimiters; - private StringBuilder token; - private boolean hasToken = false; - private int index = 0; - private int lastStart = 0; - private boolean isDouble = true; - private boolean isSingle = true; - - public QuotedStringTokenizer(String str, String delimiter, boolean returnDelimiters, boolean returnQuotes) { - super(""); - string = str; - if (delimiter != null) - this.delimiter = delimiter; - this.returnDelimiters = returnDelimiters; - this.returnQuotes = returnQuotes; - - if (this.delimiter.indexOf('\'') >= 0 || this.delimiter.indexOf('"') >= 0) - throw new Error("Can't use quotes as delimiters: " + this.delimiter); - - token = new StringBuilder(string.length() > 1024 ? 512 : string.length() / 2); - } - - public QuotedStringTokenizer(String str, String delimiter, boolean returnDelimiters) { - this(str, delimiter, returnDelimiters, false); - } - - public QuotedStringTokenizer(String str, String delimiter) { - this(str, delimiter, false, false); - } - - public QuotedStringTokenizer(String str) { - this(str, null, false, false); - } - - /** - * Quote a string. The string is quoted only if quoting is required due to - * embedded delimiters, quote characters or the empty string. - * - * @param s The string to quote. - * @param delim the delimiter to use to quote the string - * @return quoted string - */ - public static String quoteIfNeeded(String s, String delim) { - if (s == null) - return null; - if (s.length() == 0) - return "\"\""; - - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c == '\\' || c == '"' || c == '\'' || Character.isWhitespace(c) || delim.indexOf(c) >= 0) { - StringBuilder b = new StringBuilder(s.length() + 8); - quote(b, s); - return b.toString(); - } - } - - return s; - } - - /** - * Quote a string. The string is quoted only if quoting is required due to - * embeded delimiters, quote characters or the empty string. - * - * @param s The string to quote. - * @return quoted string - */ - public static String quote(String s) { - if (s == null) - return null; - if (s.length() == 0) - return "\"\""; - - StringBuilder b = new StringBuilder(s.length() + 8); - quote(b, s); - return b.toString(); - - } - - /** - * Quote a string into an Appendable. Only quotes and backslash are escaped. - * - * @param buffer The Appendable - * @param input The String to quote. - */ - public static void quoteOnly(Appendable buffer, String input) { - if (input == null) - return; - - try { - buffer.append('"'); - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - if (c == '"' || c == '\\') - buffer.append('\\'); - buffer.append(c); - } - buffer.append('"'); - } catch (IOException x) { - throw new RuntimeException(x); - } - } - - /** - * Quote a string into an Appendable. The characters ", \, \n, \r, \t, \f - * and \b are escaped - * - * @param buffer The Appendable - * @param input The String to quote. - */ - public static void quote(Appendable buffer, String input) { - if (input == null) - return; - - try { - buffer.append('"'); - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - if (c >= 32) { - if (c == '"' || c == '\\') - buffer.append('\\'); - buffer.append(c); - } else { - char escape = escapes[c]; - if (escape == 0xFFFF) { - // Unicode escape - buffer.append('\\').append('u').append('0').append('0'); - if (c < 0x10) - buffer.append('0'); - buffer.append(Integer.toString(c, 16)); - } else { - buffer.append('\\').append(escape); - } - } - } - buffer.append('"'); - } catch (IOException x) { - throw new RuntimeException(x); - } - } - - public static String unquoteOnly(String s) { - return unquoteOnly(s, false); - } - - /** - * Unquote a string, NOT converting unicode sequences - * - * @param s The string to unquote. - * @param lenient if true, will leave in backslashes that aren't valid escapes - * @return quoted string. - */ - public static String unquoteOnly(String s, boolean lenient) { - if (s == null) - return null; - if (s.length() < 2) - return s; - - char first = s.charAt(0); - char last = s.charAt(s.length() - 1); - if (first != last || (first != '"' && first != '\'')) - return s; - - StringBuilder b = new StringBuilder(s.length() - 2); - boolean escape = false; - for (int i = 1; i < s.length() - 1; i++) { - char c = s.charAt(i); - - if (escape) { - escape = false; - if (lenient && !isValidEscaping(c)) { - b.append('\\'); - } - b.append(c); - } else if (c == '\\') { - escape = true; - } else { - b.append(c); - } - } - - return b.toString(); - } - - public static String unquote(String s) { - return unquote(s, false); - } - - /** - * Unquote a string. - * - * @param s The string to unquote. - * @param lenient true if unquoting should be lenient to escaped content, - * leaving some alone, false if string unescaping - * @return quoted string - */ - public static String unquote(String s, boolean lenient) { - if (s == null) - return null; - if (s.length() < 2) - return s; - - char first = s.charAt(0); - char last = s.charAt(s.length() - 1); - if (first != last || (first != '"' && first != '\'')) - return s; - - StringBuilder b = new StringBuilder(s.length() - 2); - boolean escape = false; - for (int i = 1; i < s.length() - 1; i++) { - char c = s.charAt(i); - - if (escape) { - escape = false; - switch (c) { - case 'n': - b.append('\n'); - break; - case 'r': - b.append('\r'); - break; - case 't': - b.append('\t'); - break; - case 'f': - b.append('\f'); - break; - case 'b': - b.append('\b'); - break; - case '\\': - b.append('\\'); - break; - case '/': - b.append('/'); - break; - case '"': - b.append('"'); - break; - case 'u': - b.append((char) ((TypeUtils.convertHexDigit((byte) s.charAt(i++)) << 24) - + (TypeUtils.convertHexDigit((byte) s.charAt(i++)) << 16) - + (TypeUtils.convertHexDigit((byte) s.charAt(i++)) << 8) - + (TypeUtils.convertHexDigit((byte) s.charAt(i++))))); - break; - default: - if (lenient && !isValidEscaping(c)) { - b.append('\\'); - } - b.append(c); - } - } else if (c == '\\') { - escape = true; - } else { - b.append(c); - } - } - - return b.toString(); - } - - /** - * Check that char c (which is preceded by a backslash) is a valid escape - * sequence. - * - * @param c - * @return - */ - private static boolean isValidEscaping(char c) { - return ((c == 'n') || (c == 'r') || (c == 't') || (c == 'f') || (c == 'b') || (c == '\\') || (c == '/') - || (c == '"') || (c == 'u')); - } - - public static boolean isQuoted(String s) { - return s != null && s.length() > 0 && s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"'; - } - - @Override - public boolean hasMoreTokens() { - // Already found a token - if (hasToken) - return true; - - lastStart = index; - - int state = 0; - boolean escape = false; - while (index < string.length()) { - char c = string.charAt(index++); - - switch (state) { - case 0: // Start - if (delimiter.indexOf(c) >= 0) { - if (returnDelimiters) { - token.append(c); - return hasToken = true; - } - } else if (c == '\'' && isSingle) { - if (returnQuotes) - token.append(c); - state = 2; - } else if (c == '\"' && isDouble) { - if (returnQuotes) - token.append(c); - state = 3; - } else { - token.append(c); - hasToken = true; - state = 1; - } - break; - - case 1: // Token - hasToken = true; - if (delimiter.indexOf(c) >= 0) { - if (returnDelimiters) - index--; - return hasToken; - } else if (c == '\'' && isSingle) { - if (returnQuotes) - token.append(c); - state = 2; - } else if (c == '\"' && isDouble) { - if (returnQuotes) - token.append(c); - state = 3; - } else { - token.append(c); - } - break; - - case 2: // Single Quote - hasToken = true; - if (escape) { - escape = false; - token.append(c); - } else if (c == '\'') { - if (returnQuotes) - token.append(c); - state = 1; - } else if (c == '\\') { - if (returnQuotes) - token.append(c); - escape = true; - } else { - token.append(c); - } - break; - - case 3: // Double Quote - hasToken = true; - if (escape) { - escape = false; - token.append(c); - } else if (c == '\"') { - if (returnQuotes) - token.append(c); - state = 1; - } else if (c == '\\') { - if (returnQuotes) - token.append(c); - escape = true; - } else { - token.append(c); - } - break; - } - } - - return hasToken; - } - - @Override - public String nextToken() throws NoSuchElementException { - if (!hasMoreTokens() || token == null) - throw new NoSuchElementException(); - String t = token.toString(); - token.setLength(0); - hasToken = false; - return t; - } - - @Override - public String nextToken(String delim) throws NoSuchElementException { - delimiter = delim; - index = lastStart; - token.setLength(0); - hasToken = false; - return nextToken(); - } - - @Override - public boolean hasMoreElements() { - return hasMoreTokens(); - } - - @Override - public Object nextElement() throws NoSuchElementException { - return nextToken(); - } - - /** - * Not implemented. - */ - @Override - public int countTokens() { - return -1; - } - - /** - * @return handle double quotes if true - */ - public boolean getDouble() { - return isDouble; - } - - /** - * @param d handle double quotes if true - */ - public void setDouble(boolean d) { - isDouble = d; - } - - /** - * @return handle single quotes if true - */ - public boolean getSingle() { - return isSingle; - } - - /** - * @param single handle single quotes if true - */ - public void setSingle(boolean single) { - isSingle = single; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/string/SearchPattern.java b/firefly-common/src/main/java/com/fireflysource/common/string/SearchPattern.java deleted file mode 100644 index a636abb4f..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/string/SearchPattern.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.fireflysource.common.string; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -/** - * SearchPattern - *

    - * Fast searching for patterns within strings and arrays of bytes. - * Uses an implementation of the Boyer–Moore–Horspool algorithm - * with a 256 character alphabet. - *

    - * The algorithm has an average-case complexity of O(n) - * on random text and O(nm) in the worst case. - * where: - * m = pattern length - * n = length of data to search - */ -public class SearchPattern { - static final int alphabetSize = 256; - private int[] table; - private byte[] pattern; - - /** - * Produces a SearchPattern instance which can be used - * to find matches of the pattern in data - * - * @param pattern byte array containing the pattern - * @return a new SearchPattern instance using the given pattern - */ - public static SearchPattern compile(byte[] pattern) { - return new SearchPattern(Arrays.copyOf(pattern, pattern.length)); - } - - /** - * Produces a SearchPattern instance which can be used - * to find matches of the pattern in data - * - * @param pattern string containing the pattern - * @return a new SearchPattern instance using the given pattern - */ - public static SearchPattern compile(String pattern) { - return new SearchPattern(pattern.getBytes(StandardCharsets.UTF_8)); - } - - /** - * @param pattern byte array containing the pattern used for matching - */ - private SearchPattern(byte[] pattern) { - this.pattern = pattern; - - if (pattern.length == 0) - throw new IllegalArgumentException("Empty Pattern"); - - //Build up the pre-processed table for this pattern. - table = new int[alphabetSize]; - for (int i = 0; i < table.length; ++i) { - table[i] = pattern.length; - } - for (int i = 0; i < pattern.length - 1; ++i) { - table[0xff & pattern[i]] = pattern.length - 1 - i; - } - } - - /** - * Search for a complete match of the pattern within the data - * - * @param data The data in which to search for. The data may be arbitrary binary data, - * but the pattern will always be {@link StandardCharsets#US_ASCII} encoded. - * @param offset The offset within the data to start the search - * @param length The length of the data to search - * @return The index within the data array at which the first instance of the pattern or -1 if not found - */ - public int match(byte[] data, int offset, int length) { - validate(data, offset, length); - - int skip = offset; - while (skip <= offset + length - pattern.length) { - for (int i = pattern.length - 1; data[skip + i] == pattern[i]; i--) { - if (i == 0) - return skip; - } - - skip += table[0xff & data[skip + pattern.length - 1]]; - } - - return -1; - } - - /** - * Search for a partial match of the pattern at the end of the data. - * - * @param data The data in which to search for. The data may be arbitrary binary data, - * but the pattern will always be {@link StandardCharsets#US_ASCII} encoded. - * @param offset The offset within the data to start the search - * @param length The length of the data to search - * @return the length of the partial pattern matched and 0 for no match. - */ - public int endsWith(byte[] data, int offset, int length) { - validate(data, offset, length); - - int skip = (pattern.length <= length) ? (offset + length - pattern.length) : offset; - while (skip < offset + length) { - for (int i = (offset + length - 1) - skip; data[skip + i] == pattern[i]; --i) { - if (i == 0) - return (offset + length - skip); - } - - if (skip + pattern.length - 1 < data.length) - skip += table[0xff & data[skip + pattern.length - 1]]; - else - skip++; - } - - return 0; - } - - /** - * Search for a possibly partial match of the pattern at the start of the data. - * - * @param data The data in which to search for. The data may be arbitrary binary data, - * but the pattern will always be {@link StandardCharsets#US_ASCII} encoded. - * @param offset The offset within the data to start the search - * @param length The length of the data to search - * @param matched The length of the partial pattern already matched - * @return the length of the partial pattern matched and 0 for no match. - */ - public int startsWith(byte[] data, int offset, int length, int matched) { - validate(data, offset, length); - - int matchedCount = 0; - - for (int i = 0; i < pattern.length - matched && i < length; i++) { - if (data[offset + i] == pattern[i + matched]) - matchedCount++; - else - return 0; - } - - return matched + matchedCount; - } - - /** - * Performs legality checks for standard arguments input into SearchPattern methods. - * - * @param data The data in which to search for. The data may be arbitrary binary data, - * but the pattern will always be {@link StandardCharsets#US_ASCII} encoded. - * @param offset The offset within the data to start the search - * @param length The length of the data to search - */ - private void validate(byte[] data, int offset, int length) { - if (offset < 0) - throw new IllegalArgumentException("offset was negative"); - else if (length < 0) - throw new IllegalArgumentException("length was negative"); - else if (offset + length > data.length) - throw new IllegalArgumentException("(offset+length) out of bounds of data[]"); - } - - /** - * @return The length of the pattern in bytes. - */ - public int getLength() { - return pattern.length; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/string/StringUtils.java b/firefly-common/src/main/java/com/fireflysource/common/string/StringUtils.java deleted file mode 100644 index a5fa9b588..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/string/StringUtils.java +++ /dev/null @@ -1,859 +0,0 @@ -package com.fireflysource.common.string; - -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; - -import java.nio.charset.StandardCharsets; -import java.util.*; - -public class StringUtils { - - public static final String EMPTY = ""; - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private static final String FOLDER_SEPARATOR = "/"; - private static final char EXTENSION_SEPARATOR = '.'; - private static final char[] LOWER_CASE = {'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\010', - '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\020', '\021', '\022', '\023', '\024', '\025', - '\026', '\027', '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037', '\040', '\041', '\042', - '\043', '\044', '\045', '\046', '\047', '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057', - '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067', '\070', '\071', '\072', '\073', '\074', - '\075', '\076', '\077', '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147', '\150', '\151', - '\152', '\153', '\154', '\155', '\156', '\157', '\160', '\161', '\162', '\163', '\164', '\165', '\166', - '\167', '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137', '\140', '\141', '\142', '\143', - '\144', '\145', '\146', '\147', '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157', '\160', - '\161', '\162', '\163', '\164', '\165', '\166', '\167', '\170', '\171', '\172', '\173', '\174', '\175', - '\176', '\177'}; - private static final Trie CHARSETS = new ArrayTrie<>(256); - - private static final String ISO_8859_1 = "iso-8859-1"; - private static final String UTF8 = "utf-8"; - private static final String __UTF16 = "utf-16"; - - static { - CHARSETS.put("utf-8", UTF8); - CHARSETS.put("utf8", UTF8); - CHARSETS.put("utf-16", __UTF16); - CHARSETS.put("utf16", __UTF16); - CHARSETS.put("iso-8859-1", ISO_8859_1); - CHARSETS.put("iso_8859_1", ISO_8859_1); - } - - /** - *

    - * Splits the provided text into an array, using whitespace as the - * separator. Whitespace is defined by {@link Character#isWhitespace(char)}. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. For more control over the split - * use the StrTokenizer class. - *

    - *

    - *

    - * A null input String returns null. - *

    - *

    - *

    -     * StringUtils.split(null)       = null
    -     * StringUtils.split("")         = []
    -     * StringUtils.split("abc def")  = ["abc", "def"]
    -     * StringUtils.split("abc  def") = ["abc", "def"]
    -     * StringUtils.split(" abc ")    = ["abc"]
    -     * 
    - * - * @param str the String to parse, may be null - * @return an array of parsed Strings, null if null String - * input - */ - public static String[] split(String str) { - return split(str, null, -1); - } - - /** - *

    - * Splits the provided text into an array, separators specified. This is an - * alternative to using StringTokenizer. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. For more control over the split - * use the StrTokenizer class. - *

    - *

    - *

    - * A null input String returns null. A - * null separatorChars splits on whitespace. - *

    - *

    - *

    -     * StringUtils.split(null, *)         = null
    -     * StringUtils.split("", *)           = []
    -     * StringUtils.split("abc def", null) = ["abc", "def"]
    -     * StringUtils.split("abc def", " ")  = ["abc", "def"]
    -     * StringUtils.split("abc  def", " ") = ["abc", "def"]
    -     * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separatorChars the characters used as the delimiters, null - * splits on whitespace - * @return an array of parsed Strings, null if null String - * input - */ - public static String[] split(String str, String separatorChars) { - return splitWorker(str, separatorChars, -1, false); - } - - /** - *

    - * Splits the provided text into an array, separator specified. This is an - * alternative to using StringTokenizer. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. For more control over the split - * use the StrTokenizer class. - *

    - *

    - *

    - * A null input String returns null. - *

    - *

    - *

    -     * StringUtils.split(null, *)         = null
    -     * StringUtils.split("", *)           = []
    -     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
    -     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
    -     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
    -     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separatorChar the character used as the delimiter - * @return an array of parsed Strings, null if null String - * input - * @since 2.0 - */ - public static String[] split(String str, char separatorChar) { - return splitWorker(str, separatorChar, false); - } - - /** - *

    - * Splits the provided text into an array with a maximum length, separators - * specified. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. - *

    - *

    - *

    - * A null input String returns null. A - * null separatorChars splits on whitespace. - *

    - *

    - *

    - * If more than max delimited substrings are found, the last - * returned string includes all characters after the first - * max - 1 returned strings (including separator characters). - *

    - *

    - *

    -     * StringUtils.split(null, *, *)            = null
    -     * StringUtils.split("", *, *)              = []
    -     * StringUtils.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
    -     * StringUtils.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
    -     * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
    -     * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separatorChars the characters used as the delimiters, null - * splits on whitespace - * @param max the maximum number of elements to include in the array. A zero - * or negative value implies no limit - * @return an array of parsed Strings, null if null String - * input - */ - public static String[] split(String str, String separatorChars, int max) { - return splitWorker(str, separatorChars, max, false); - } - - /** - * Performs the logic for the split and - * splitPreserveAllTokens methods that return a maximum array - * length. - * - * @param str the String to parse, may be null - * @param separatorChars the separate character - * @param max the maximum number of elements to include in the array. A zero - * or negative value implies no limit. - * @param preserveAllTokens if true, adjacent separators are treated as empty - * token separators; if false, adjacent separators - * are treated as one separator. - * @return an array of parsed Strings, null if null String - * input - */ - private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) { - // Performance tuned for 2.0 (JDK1.4) - // Direct code is quicker than StringTokenizer. - // Also, StringTokenizer uses isSpace() not isWhitespace() - - if (str == null) { - return null; - } - int len = str.length(); - if (len == 0) { - return EMPTY_STRING_ARRAY; - } - List list = new ArrayList<>(); - int sizePlus1 = 1; - int i = 0, start = 0; - boolean match = false; - boolean lastMatch = false; - if (separatorChars == null) { - // Null separator means use whitespace - while (i < len) { - if (Character.isWhitespace(str.charAt(i))) { - if (match || preserveAllTokens) { - lastMatch = true; - if (sizePlus1++ == max) { - i = len; - lastMatch = false; - } - list.add(str.substring(start, i)); - match = false; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - } else if (separatorChars.length() == 1) { - // Optimise 1 character case - char sep = separatorChars.charAt(0); - while (i < len) { - if (str.charAt(i) == sep) { - if (match || preserveAllTokens) { - lastMatch = true; - if (sizePlus1++ == max) { - i = len; - lastMatch = false; - } - list.add(str.substring(start, i)); - match = false; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - } else { - // standard case - while (i < len) { - if (separatorChars.indexOf(str.charAt(i)) >= 0) { - if (match || preserveAllTokens) { - lastMatch = true; - if (sizePlus1++ == max) { - i = len; - lastMatch = false; - } - list.add(str.substring(start, i)); - match = false; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - } - if (match || (preserveAllTokens && lastMatch)) { - list.add(str.substring(start, i)); - } - return list.toArray(EMPTY_STRING_ARRAY); - } - - /** - * Performs the logic for the split and - * splitPreserveAllTokens methods that do not return a maximum - * array length. - * - * @param str the String to parse, may be null - * @param separatorChar the separate character - * @param preserveAllTokens if true, adjacent separators are treated as empty - * token separators; if false, adjacent separators - * are treated as one separator. - * @return an array of parsed Strings, null if null String - * input - */ - private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) { - // Performance tuned for 2.0 (JDK1.4) - - if (str == null) { - return null; - } - int len = str.length(); - if (len == 0) { - return EMPTY_STRING_ARRAY; - } - List list = new ArrayList<>(); - int i = 0, start = 0; - boolean match = false; - boolean lastMatch = false; - while (i < len) { - if (str.charAt(i) == separatorChar) { - if (match || preserveAllTokens) { - list.add(str.substring(start, i)); - match = false; - lastMatch = true; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - if (match || (preserveAllTokens && lastMatch)) { - list.add(str.substring(start, i)); - } - return list.toArray(EMPTY_STRING_ARRAY); - } - - /** - *

    - * Splits the provided text into an array, separator string specified. - *

    - *

    - *

    - * The separator(s) will not be included in the returned String array. - * Adjacent separators are treated as one separator. - *

    - *

    - *

    - * A null input String returns null. A - * null separator splits on whitespace. - *

    - *

    - *

    -     * StringUtils.splitByWholeSeparator(null, *)               = null
    -     * StringUtils.splitByWholeSeparator("", *)                 = []
    -     * StringUtils.splitByWholeSeparator("ab de fg", null)      = ["ab", "de", "fg"]
    -     * StringUtils.splitByWholeSeparator("ab   de fg", null)    = ["ab", "de", "fg"]
    -     * StringUtils.splitByWholeSeparator("ab:cd:ef", ":")       = ["ab", "cd", "ef"]
    -     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separator String containing the String to be used as a delimiter, - * null splits on whitespace - * @return an array of parsed Strings, null if null String was - * input - */ - public static String[] splitByWholeSeparator(String str, String separator) { - return splitByWholeSeparatorWorker(str, separator, -1, false); - } - - /** - *

    - * Splits the provided text into an array, separator string specified. - * Returns a maximum of max substrings. - *

    - *

    - *

    - * The separator(s) will not be included in the returned String array. - * Adjacent separators are treated as one separator. - *

    - *

    - *

    - * A null input String returns null. A - * null separator splits on whitespace. - *

    - *

    - *

    -     * StringUtils.splitByWholeSeparator(null, *, *)               = null
    -     * StringUtils.splitByWholeSeparator("", *, *)                 = []
    -     * StringUtils.splitByWholeSeparator("ab de fg", null, 0)      = ["ab", "de", "fg"]
    -     * StringUtils.splitByWholeSeparator("ab   de fg", null, 0)    = ["ab", "de", "fg"]
    -     * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2)       = ["ab", "cd:ef"]
    -     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 5) = ["ab", "cd", "ef"]
    -     * StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-", 2) = ["ab", "cd-!-ef"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separator String containing the String to be used as a delimiter, - * null splits on whitespace - * @param max the maximum number of elements to include in the returned - * array. A zero or negative value implies no limit. - * @return an array of parsed Strings, null if null String was - * input - */ - public static String[] splitByWholeSeparator(String str, String separator, int max) { - return splitByWholeSeparatorWorker(str, separator, max, false); - } - - /** - * Performs the logic for the - * splitByWholeSeparatorPreserveAllTokens methods. - * - * @param str the String to parse, may be null - * @param separator String containing the String to be used as a delimiter, - * null splits on whitespace - * @param max the maximum number of elements to include in the returned - * array. A zero or negative value implies no limit. - * @param preserveAllTokens if true, adjacent separators are treated as empty - * token separators; if false, adjacent separators - * are treated as one separator. - * @return an array of parsed Strings, null if null String - * input - * @since 2.4 - */ - private static String[] splitByWholeSeparatorWorker(String str, String separator, int max, - boolean preserveAllTokens) { - if (str == null) { - return null; - } - - int len = str.length(); - - if (len == 0) { - return EMPTY_STRING_ARRAY; - } - - if ((separator == null) || (EMPTY.equals(separator))) { - // Split on whitespace. - return splitWorker(str, null, max, preserveAllTokens); - } - - int separatorLength = separator.length(); - - ArrayList substrings = new ArrayList<>(); - int numberOfSubstrings = 0; - int beg = 0; - int end = 0; - while (end < len) { - end = str.indexOf(separator, beg); - - if (end > -1) { - if (end > beg) { - numberOfSubstrings += 1; - - if (numberOfSubstrings == max) { - end = len; - substrings.add(str.substring(beg)); - } else { - // The following is OK, because String.substring( beg, - // end ) excludes - // the character at the position 'end'. - // System.out.println("sub " + beg + "|" + end +"|" + - // str.substring(beg, end)); - substrings.add(str.substring(beg, end)); - - // Set the starting point for the next search. - // The following is equivalent to beg = end + - // (separatorLength - 1) + 1, - // which is the right calculation: - beg = end + separatorLength; - } - } else { - // We found a consecutive occurrence of the separator, so - // skip it. - if (preserveAllTokens) { - numberOfSubstrings += 1; - if (numberOfSubstrings == max) { - end = len; - substrings.add(str.substring(beg)); - } else { - substrings.add(EMPTY); - } - } - beg = end + separatorLength; - } - } else { - // String.substring( beg ) goes from 'beg' to the end of the - // String. - // System.out.println("sub~~ " + beg + "|" + end +"|" + - // str.substring(beg)); - String t = str.substring(beg); - if (!t.equals(EMPTY)) - substrings.add(str.substring(beg)); - end = len; - } - } - - return substrings.toArray(EMPTY_STRING_ARRAY); - } - - public static boolean hasText(String str) { - return hasText((CharSequence) str); - } - - public static boolean hasText(CharSequence str) { - if (!hasLength(str)) { - return false; - } - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; - } - - public static boolean hasLength(CharSequence str) { - return (str != null && str.length() > 0); - } - - public static boolean hasLength(String str) { - return hasLength((CharSequence) str); - } - - /** - * Replace the pattern using a map, such as a pattern, such as A pattern is - * "hello ${foo}" and the map is {"foo" : "world"}, when you execute this - * function, the result is "hello world" - * - * @param s The pattern string. - * @param map The key-value - * @return The string replaced. - */ - public static String replace(String s, Map map) { - StringBuilder ret = new StringBuilder((int) (s.length() * 1.5)); - int cursor = 0; - for (int start, end; (start = s.indexOf("${", cursor)) != -1 && (end = s.indexOf("}", start)) != -1; ) { - ret.append(s, cursor, start).append(map.get(s.substring(start + 2, end))); - cursor = end + 1; - } - ret.append(s, cursor, s.length()); - return ret.toString(); - } - - public static String replace(String s, Object... objs) { - if (objs == null || objs.length == 0) - return s; - if (!s.contains("{}")) - return s; - - StringBuilder ret = new StringBuilder((int) (s.length() * 1.5)); - int cursor = 0; - int index = 0; - for (int start; (start = s.indexOf("{}", cursor)) != -1; ) { - ret.append(s, cursor, start); - if (index < objs.length) { - Object obj = objs[index]; - try { - if (obj != null) { - if (obj instanceof AbstractCollection) { - ret.append(Arrays.toString(((AbstractCollection) obj).toArray())); - } else { - ret.append(obj); - } - } else { - ret.append("null"); - } - } catch (Throwable ignored) { - } - } else { - ret.append("{}"); - } - cursor = start + 2; - index++; - } - ret.append(s, cursor, s.length()); - return ret.toString(); - } - - public static String replaceStr(String s, String sub, String with) { - if (s == null) { - return null; - } - - int c = 0; - int i = s.indexOf(sub, c); - if (i == -1) { - return s; - } - StringBuilder buf = new StringBuilder(s.length() + with.length()); - do { - buf.append(s, c, i); - buf.append(with); - c = i + sub.length(); - } - while ((i = s.indexOf(sub, c)) != -1); - if (c < s.length()) { - buf.append(s.substring(c)); - } - return buf.toString(); - } - - public static String escapeXML(String str) { - if (str == null) - return ""; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < str.length(); ++i) { - char c = str.charAt(i); - switch (c) { - case '\u00FF': - case '\u0024': - break; - case '&': - sb.append("&"); - break; - case '<': - sb.append("<"); - break; - case '>': - sb.append(">"); - break; - case '\"': - sb.append("""); - break; - case '\'': - sb.append("'"); - break; - default: - if (c >= '\u0000' && c <= '\u001F') - break; - if (c >= '\uE000' && c <= '\uF8FF') - break; - if (c >= '\uFFF0' && c <= '\uFFFF') - break; - sb.append(c); - break; - } - } - return sb.toString(); - } - - /** - * Convert a string that is unicode form to a normal string. - * - * @param s The unicode form of a string, e.g. "\\u8001\\u9A6C" - * @return Normal string - */ - public static String unicodeToString(String s) { - StringBuilder sb = new StringBuilder(); - StringTokenizer st = new StringTokenizer(s, "\\u"); - while (st.hasMoreTokens()) { - String token = st.nextToken(); - if (token.length() > 4) { - sb.append((char) Integer.parseInt(token.substring(0, 4), 16)); - sb.append(token.substring(4)); - } else { - sb.append((char) Integer.parseInt(token, 16)); - } - } - return sb.toString(); - } - - - /** - * Extract the filename extension from the given Java resource path, - * e.g. "mypath/myfile.txt" -> "txt". - * - * @param path the file path (may be {@code null}) - * @return the extracted filename extension, or {@code null} if none - */ - public static String getFilenameExtension(String path) { - if (path == null) { - return null; - } - int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - if (extIndex == -1) { - return null; - } - int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (folderIndex > extIndex) { - return null; - } - return path.substring(extIndex + 1); - } - - /** - * Extract the filename from the given Java resource path, - * e.g. {@code "mypath/myfile.txt" -> "myfile.txt"}. - * - * @param path the file path (may be {@code null}) - * @return the extracted filename, or {@code null} if none - */ - public static String getFilename(String path) { - if (path == null) { - return null; - } - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); - } - - public static byte[] getUtf8Bytes(String string) { - return string.getBytes(StandardCharsets.UTF_8); - } - - public static byte[] getBytes(String string) { - return string.getBytes(StandardCharsets.ISO_8859_1); - } - - /** - * Convert String to an integer. Parses up to the first non-numeric - * character. If no number is found an IllegalArgumentException is thrown - * - * @param string A String containing an integer. - * @param from The index to start parsing from - * @return an int - */ - public static int toInt(String string, int from) { - int val = 0; - boolean started = false; - boolean minus = false; - - for (int i = from; i < string.length(); i++) { - char b = string.charAt(i); - if (b <= ' ') { - if (started) - break; - } else if (b >= '0' && b <= '9') { - val = val * 10 + (b - '0'); - started = true; - } else if (b == '-' && !started) { - minus = true; - } else - break; - } - - if (started) - return minus ? (-val) : val; - throw new NumberFormatException(string); - } - - /** - * append hex digit - * - * @param buf the buffer to append to - * @param b the byte to append - * @param base the base of the hex output (almost always 16). - */ - public static void append(StringBuilder buf, byte b, int base) { - int bi = 0xff & b; - int c = '0' + (bi / base) % base; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - c = '0' + bi % base; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - } - - /** - * Append 2 digits (zero padded) to the StringBuilder - * - * @param buf the buffer to append to - * @param i the value to append - */ - public static void append2digits(StringBuilder buf, int i) { - if (i < 100) { - buf.append((char) (i / 10 + '0')); - buf.append((char) (i % 10 + '0')); - } - } - - /** - * fast lower case conversion. Only works on ascii (not unicode) - * - * @param s the string to convert - * @return a lower case version of s - */ - public static String asciiToLowerCase(String s) { - char[] c = null; - int i = s.length(); - - // look for first conversion - while (i-- > 0) { - char c1 = s.charAt(i); - if (c1 <= 127) { - char c2 = LOWER_CASE[c1]; - if (c1 != c2) { - c = s.toCharArray(); - c[i] = c2; - break; - } - } - } - - while (i-- > 0) { - if (c != null && c[i] <= 127) - c[i] = LOWER_CASE[c[i]]; - } - return c == null ? s : new String(c); - } - - /** - * Convert alternate charset names (eg utf8) to normalized name (eg UTF-8). - * - * @param s the charset to normalize - * @return the normalized charset (or null if normalized version not found) - */ - public static String normalizeCharset(String s) { - String n = CHARSETS.get(s); - return (n == null) ? s : n; - } - - /** - * Convert alternate charset names (eg utf8) to normalized name (eg UTF-8). - * - * @param s the charset to normalize - * @param offset the offset in the charset - * @param length the length of the charset in the input param - * @return the normalized charset (or null if not found) - */ - public static String normalizeCharset(String s, int offset, int length) { - String n = CHARSETS.get(s, offset, length); - return (n == null) ? s.substring(offset, offset + length) : n; - } - - public static boolean isHex(String str, int offset, int length) { - if (offset + length > str.length()) { - return false; - } - - for (int i = offset; i < (offset + length); i++) { - char c = str.charAt(i); - if (!(((c >= 'a') && (c <= 'f')) || - ((c >= 'A') && (c <= 'F')) || - ((c >= '0') && (c <= '9')))) { - return false; - } - } - return true; - } - - /** - * Truncate a string to a max size. - * - * @param str the string to possibly truncate - * @param maxSize the maximum size of the string - * @return the truncated string. if str param is null, then the returned string will also be null. - */ - public static String truncate(String str, int maxSize) { - if (str == null) { - return null; - } - if (str.length() <= maxSize) { - return str; - } - return str.substring(0, maxSize); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/string/Utf8Appendable.java b/firefly-common/src/main/java/com/fireflysource/common/string/Utf8Appendable.java deleted file mode 100644 index f1b7bb8f0..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/string/Utf8Appendable.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.fireflysource.common.string; - -import com.fireflysource.common.object.TypeUtils; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Utf8 Appendable abstract base class - *

    - * This abstract class wraps a standard {@link Appendable} and - * provides methods to append UTF-8 encoded bytes, that are converted into - * characters. - *

    - * This class is stateful and up to 4 calls to {@link #append(byte)} may be - * needed before state a character is appended to the string buffer. - *

    - * The UTF-8 decoding is done by this class and no additional buffers or Readers - * are used. The UTF-8 code was inspired by - * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - *

    - * License information for Bjoern Hoehrmann's code: - *

    - * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

    - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

    - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - **/ -public abstract class Utf8Appendable { - public static final char REPLACEMENT = '\ufffd'; - public static final byte[] REPLACEMENT_UTF8 = new byte[]{(byte) 0xEF, (byte) 0xBF, (byte) 0xBD}; - private static final int UTF8_ACCEPT = 0; - private static final int UTF8_REJECT = 12; - private static final byte[] BYTE_TABLE = - { - // The first part of the table maps bytes to character classes that - // to reduce the size of the transition table and create bitmasks. - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 - }; - private static final byte[] TRANS_TABLE = - { - // The second part is a transition table that maps a combination - // of a state of the automaton and a character class to a state. - 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, - 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 - }; - protected final Appendable appendable; - protected int _state = UTF8_ACCEPT; - private int codep; - - public Utf8Appendable(Appendable appendable) { - this.appendable = appendable; - } - - public abstract int length(); - - protected void reset() { - _state = UTF8_ACCEPT; - } - - private void checkCharAppend() throws IOException { - if (_state != UTF8_ACCEPT) { - appendable.append(REPLACEMENT); - int state = _state; - _state = UTF8_ACCEPT; - throw new NotUtf8Exception("char appended in state " + state); - } - } - - public void append(char c) { - try { - checkCharAppend(); - appendable.append(c); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void append(String s) { - try { - checkCharAppend(); - appendable.append(s); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void append(String s, int offset, int length) { - try { - checkCharAppend(); - appendable.append(s, offset, offset + length); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void append(byte b) { - try { - appendByte(b); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void append(ByteBuffer buf) { - try { - while (buf.remaining() > 0) { - appendByte(buf.get()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void append(byte[] b, int offset, int length) { - try { - int end = offset + length; - for (int i = offset; i < end; i++) - appendByte(b[i]); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public boolean append(byte[] b, int offset, int length, int maxChars) { - try { - int end = offset + length; - for (int i = offset; i < end; i++) { - if (length() > maxChars) - return false; - appendByte(b[i]); - } - return true; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - protected void appendByte(byte b) throws IOException { - - if (b > 0 && _state == UTF8_ACCEPT) { - appendable.append((char) (b & 0xFF)); - } else { - int i = b & 0xFF; - int type = BYTE_TABLE[i]; - codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (codep << 6); - int next = TRANS_TABLE[_state + type]; - - switch (next) { - case UTF8_ACCEPT: - _state = next; - if (codep < Character.MIN_HIGH_SURROGATE) { - appendable.append((char) codep); - } else { - for (char c : Character.toChars(codep)) - appendable.append(c); - } - break; - - case UTF8_REJECT: - String reason = "byte " + TypeUtils.toHexString(b) + " in state " + (_state / 12); - codep = 0; - _state = UTF8_ACCEPT; - appendable.append(REPLACEMENT); - throw new NotUtf8Exception(reason); - - default: - _state = next; - - } - } - } - - public boolean isUtf8SequenceComplete() { - return _state == UTF8_ACCEPT; - } - - protected void checkState() { - if (!isUtf8SequenceComplete()) { - codep = 0; - _state = UTF8_ACCEPT; - try { - appendable.append(REPLACEMENT); - } catch (IOException e) { - throw new RuntimeException(e); - } - throw new NotUtf8Exception("incomplete UTF8 sequence"); - } - } - - public String toReplacedString() { - if (!isUtf8SequenceComplete()) { - codep = 0; - _state = UTF8_ACCEPT; - try { - appendable.append(REPLACEMENT); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return appendable.toString(); - } - - @SuppressWarnings("serial") - public static class NotUtf8Exception extends IllegalArgumentException { - public NotUtf8Exception(String reason) { - super("Not valid UTF8! " + reason); - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/string/Utf8StringBuilder.java b/firefly-common/src/main/java/com/fireflysource/common/string/Utf8StringBuilder.java deleted file mode 100644 index be2d1d962..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/string/Utf8StringBuilder.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fireflysource.common.string; - -/** - * UTF-8 StringBuilder. - *

    - * This class wraps a standard {@link StringBuilder} and provides - * methods to append UTF-8 encoded bytes, that are converted into characters. - *

    - * This class is stateful and up to 4 calls to {@link #append(byte)} may be - * needed before state a character is appended to the string buffer. - *

    - * The UTF-8 decoding is done by this class and no additional buffers or Readers - * are used. The UTF-8 code was inspired by - * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - */ -public class Utf8StringBuilder extends Utf8Appendable { - final StringBuilder buffer; - - public Utf8StringBuilder() { - super(new StringBuilder()); - buffer = (StringBuilder) appendable; - } - - public Utf8StringBuilder(int capacity) { - super(new StringBuilder(capacity)); - buffer = (StringBuilder) appendable; - } - - @Override - public int length() { - return buffer.length(); - } - - @Override - public void reset() { - super.reset(); - buffer.setLength(0); - } - - public StringBuilder getStringBuilder() { - checkState(); - return buffer; - } - - @Override - public String toString() { - checkState(); - return buffer.toString(); - } - -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/sys/JavaVersion.java b/firefly-common/src/main/java/com/fireflysource/common/sys/JavaVersion.java deleted file mode 100644 index 604ee401f..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/sys/JavaVersion.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.fireflysource.common.sys; - -/** - * Java Version Utility class. - *

    Parses java versions to extract a consistent set of version parts

    - */ -public class JavaVersion { - - public static final JavaVersion VERSION = parse(System.getProperty("java.version")); - - public static JavaVersion parse(String v) { - // $VNUM is a dot-separated list of integers of arbitrary length - String[] split = v.split("[^0-9]"); - int len = Math.min(split.length, 3); - int[] version = new int[len]; - for (int i = 0; i < len; i++) { - try { - version[i] = Integer.parseInt(split[i]); - } catch (Throwable e) { - len = i - 1; - break; - } - } - - return new JavaVersion( - v, - (version[0] >= 9 || len == 1) ? version[0] : version[1], - version[0], - len > 1 ? version[1] : 0, - len > 2 ? version[2] : 0); - } - - private final String version; - private final int platform; - private final int major; - private final int minor; - private final int micro; - - private JavaVersion(String version, int platform, int major, int minor, int micro) { - this.version = version; - this.platform = platform; - this.major = major; - this.minor = minor; - this.micro = micro; - } - - /** - * @return the string from which this JavaVersion was created - */ - public String getVersion() { - return version; - } - - /** - *

    Returns the Java Platform version, such as {@code 8} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.

    - * - * @return the Java Platform version - */ - public int getPlatform() { - return platform; - } - - /** - *

    Returns the major number version, such as {@code 1} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.

    - * - * @return the major number version - */ - public int getMajor() { - return major; - } - - /** - *

    Returns the minor number version, such as {@code 8} for JDK 1.8.0_92 and {@code 2} for JDK 9.2.4.

    - * - * @return the minor number version - */ - public int getMinor() { - return minor; - } - - /** - *

    Returns the micro number version (aka security number), such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.

    - * - * @return the micro number version - */ - public int getMicro() { - return micro; - } - - /** - *

    Returns the update number version, such as {@code 92} for JDK 1.8.0_92 and {@code 0} for JDK 9.2.4.

    - * - * @return the update number version - */ - @Deprecated - public int getUpdate() { - return 0; - } - - /** - *

    Returns the remaining string after the version numbers, such as {@code -internal} for - * JDK 1.8.0_92-internal and {@code -ea} for JDK 9-ea, or {@code +13} for JDK 9.2.4+13.

    - * - * @return the remaining string after the version numbers - */ - @Deprecated - public String getSuffix() { - return null; - } - - @Override - public String toString() { - return version; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/sys/ProjectVersion.java b/firefly-common/src/main/java/com/fireflysource/common/sys/ProjectVersion.java deleted file mode 100644 index f1f081c0b..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/sys/ProjectVersion.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fireflysource.common.sys; - -import java.io.InputStream; -import java.util.Properties; - -import static java.lang.System.lineSeparator; - -/** - * @author Pengtao Qiu - */ -public class ProjectVersion { - - private String value; - private String githubUrl; - - private ProjectVersion() { - try (InputStream is = ProjectVersion.class.getResourceAsStream("/firefly_version.properties")) { - Properties properties = new Properties(); - properties.load(is); - value = properties.getProperty("firefly.version"); - githubUrl = properties.getProperty("github.url"); - } catch (Exception ignored) { - } - } - - public static String getValue() { - return Holder.instance.value; - } - - public static String getGithubUrl() { - return Holder.instance.githubUrl; - } - - public static String getAsciiArt() { - return "\033[31;0m\n" + - "______ _ __ _ \n" + - "| ___(_) / _| | \n" + - "| |_ _ _ __ ___| |_| |_ _ \n" + - "| _| | | '__/ _ \\ _| | | | |\n" + - "| | | | | | __/ | | | |_| |\n" + - "\\_| |_|_| \\___|_| |_|\\__, |\n" + - " __/ |\n" + - " |___/ \n\033[0m"; - } - - public static String logo() { - return lineSeparator() + "Github: " + getGithubUrl() + lineSeparator() + - "Version: " + getValue() + lineSeparator() + - getAsciiArt() + lineSeparator(); - } - - private static class Holder { - private static final ProjectVersion instance = new ProjectVersion(); - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/sys/Result.java b/firefly-common/src/main/java/com/fireflysource/common/sys/Result.java deleted file mode 100644 index de49b45e7..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/sys/Result.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.fireflysource.common.sys; - -import com.fireflysource.common.string.StringUtils; - -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -import static com.fireflysource.common.func.FunctionInterfaceUtils.createEmptyConsumer; - -/** - * @author Pengtao Qiu - */ -public class Result { - - public static final CompletableFuture DONE = doneFuture(); - public static final Result SUCCESS = new Result<>(true, null, null); - @SuppressWarnings("rawtypes") - private static final Consumer EMPTY = createEmptyConsumer(); - - private final boolean success; - private final T value; - private final Throwable throwable; - - public Result(boolean success, T value, Throwable throwable) { - this.success = success; - this.value = value; - this.throwable = throwable; - } - - public static void done(CompletableFuture future) { - future.complete(null); - } - - public static CompletableFuture doneFuture() { - return CompletableFuture.completedFuture(null); - } - - @SuppressWarnings("unchecked") - public static Consumer emptyConsumer() { - return EMPTY; - } - - public static Result createFailedResult(T value, Throwable throwable) { - return new Result<>(false, value, throwable); - } - - public static Result createFailedResult(Throwable throwable) { - return new Result<>(false, null, throwable); - } - - public static Result createSuccessResult() { - return new Result<>(true, null, null); - } - - @SuppressWarnings("unchecked") - public static Consumer> discard() { - return EMPTY; - } - - public static Consumer> futureToConsumer(CompletableFuture future) { - return result -> { - if (result.isSuccess()) { - future.complete(result.getValue()); - } else { - future.completeExceptionally(result.getThrowable()); - } - }; - } - - public static Result runCaching(Callable callable) { - try { - T t = callable.call(); - return new Result<>(true, t, null); - } catch (Throwable t) { - return new Result<>(false, null, t); - } - } - - public boolean isSuccess() { - return success; - } - - public T getValue() { - return value; - } - - public Throwable getThrowable() { - return throwable; - } - - @Override - public String toString() { - return "Result{" + - "success=" + success + - ", value=" + value + - ", throwable=" + (throwable != null && StringUtils.hasText(throwable.getMessage()) ? throwable.getMessage() : "null") + - '}'; - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/common/sys/SystemLogger.java b/firefly-common/src/main/java/com/fireflysource/common/sys/SystemLogger.java deleted file mode 100644 index cd8b94d35..000000000 --- a/firefly-common/src/main/java/com/fireflysource/common/sys/SystemLogger.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.fireflysource.common.sys; - -import com.fireflysource.common.bytecode.JavassistClassProxyFactory; -import com.fireflysource.common.lifecycle.ShutdownTasks; -import com.fireflysource.common.slf4j.LazyLogger; -import org.slf4j.ILoggerFactory; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; - -import java.io.Closeable; -import java.io.IOException; - -/** - * @author Pengtao Qiu - */ -public class SystemLogger { - - private static final LazyLogger system = LazyLogger.create("firefly-system"); - - static { - ShutdownTasks.register(SystemLogger::stop); - } - - public static LazyLogger create(Class clazz) { - try { - String className = clazz.getSimpleName(); - return JavassistClassProxyFactory.INSTANCE.createProxy(system, ((handler, originalInstance, args) -> { - try (MDC.MDCCloseable ignored = MDC.putCloseable("class", className)) { - return handler.invoke(originalInstance, args); - } - }), null); - } catch (Throwable e) { - system.error("create system logger exception", e); - throw new IllegalStateException(e); - } - } - - public static void stop() { - ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); - if (iLoggerFactory instanceof Closeable) { - try { - ((Closeable) iLoggerFactory).close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} diff --git a/firefly-common/src/main/java/com/fireflysource/doc/FeignedCommonDoc.java b/firefly-common/src/main/java/com/fireflysource/doc/FeignedCommonDoc.java deleted file mode 100644 index 5b232ab04..000000000 --- a/firefly-common/src/main/java/com/fireflysource/doc/FeignedCommonDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedCommonDoc { -} diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/annotation/CompilerPluginAnnotation.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/annotation/CompilerPluginAnnotation.kt deleted file mode 100644 index 9255c4a37..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/annotation/CompilerPluginAnnotation.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.common.annotation - -/** - * @author Pengtao Qiu - */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class NoArg \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/concurrent/CompletableFutureExtension.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/concurrent/CompletableFutureExtension.kt deleted file mode 100644 index 160717ee4..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/concurrent/CompletableFutureExtension.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.fireflysource.common.concurrent - -import com.fireflysource.common.sys.Result -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionStage - -inline fun CompletionStage.exceptionallyCompose(crossinline block: (Throwable) -> CompletionStage): CompletableFuture { - return CompletableFutures.exceptionallyCompose(this) { block(it) }.toCompletableFuture() -} - -inline fun CompletionStage.exceptionallyAccept(crossinline block: (Throwable) -> Unit): CompletableFuture { - return this.exceptionally { - block(it) - null - }.thenCompose { Result.DONE }.toCompletableFuture() -} - -inline fun CompletionStage.doFinally(crossinline block: (T?, Throwable?) -> CompletableFuture): CompletableFuture { - return CompletableFutures.doFinally(this) { value, throwable -> block(value, throwable) } -} \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/ChannelExtension.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/ChannelExtension.kt deleted file mode 100644 index 15430c867..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/ChannelExtension.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.common.coroutine - -import kotlinx.coroutines.channels.Channel - -inline fun Channel.consumeAll(crossinline block: (T) -> Unit) { - try { - while (true) { - val result = this.tryReceive() - if (result.isFailure) { - break - } - val message = result.getOrNull() ?: break - block(message) - } - } catch (ignore: Exception) { - } -} - -fun Channel.clear() { - this.consumeAll { } -} \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CommonCoroutinePool.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CommonCoroutinePool.kt deleted file mode 100644 index 55512cfc8..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CommonCoroutinePool.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.fireflysource.common.coroutine - -import com.fireflysource.common.concurrent.ExecutorServiceUtils.shutdownAndAwaitTermination -import com.fireflysource.common.coroutine.CoroutineDispatchers.awaitTerminationTimeout -import com.fireflysource.common.ref.Cleaner -import kotlinx.coroutines.* -import java.util.concurrent.* -import java.util.concurrent.atomic.AtomicInteger - -/** - * @author Pengtao Qiu - */ -object CoroutineDispatchers { - - val availableProcessors = Runtime.getRuntime().availableProcessors() - val awaitTerminationTimeout = - Integer.getInteger("com.fireflysource.common.coroutine.awaitTerminationTimeout", 5).toLong() - - val defaultPoolSize: Int = - Integer.getInteger("com.fireflysource.common.coroutine.defaultPoolSize", availableProcessors) - - val ioBlockingPoolSize: Int = - Integer.getInteger("com.fireflysource.common.coroutine.ioBlockingPoolSize", availableProcessors * 8) - val ioBlockingPoolKeepAliveTime: Long = - Integer.getInteger("com.fireflysource.common.coroutine.ioBlockingPoolKeepAliveTime", 30).toLong() - val ioBlockingPoolQueueSize: Int = - Integer.getInteger("com.fireflysource.common.coroutine.ioBlockingPoolQueueSize", 10000) - - - val ioBlockingThreadPool: ExecutorService by lazy { - val threadId = AtomicInteger() - ThreadPoolExecutor( - availableProcessors * 2, ioBlockingPoolSize, - ioBlockingPoolKeepAliveTime, TimeUnit.SECONDS, - LinkedBlockingQueue(ioBlockingPoolQueueSize) - ) { runnable -> Thread(runnable, "firefly-io-blocking-pool-" + threadId.getAndIncrement()) } - } - - val singleThreadPool: ExecutorService by lazy { - ThreadPoolExecutor( - 1, 1, 0, TimeUnit.MILLISECONDS, - LinkedTransferQueue() - ) { runnable -> Thread(runnable, "firefly-single-thread-pool") } - } - - val computationThreadPool: ExecutorService by lazy { - ForkJoinPool(defaultPoolSize, { pool -> - val worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool) - worker.name = "firefly-computation-pool-" + worker.poolIndex - worker - }, null, true) - } - - val computation: CoroutineDispatcher by lazy { computationThreadPool.asCoroutineDispatcher() } - val ioBlocking: CoroutineDispatcher by lazy { ioBlockingThreadPool.asCoroutineDispatcher() } - val singleThread: CoroutineDispatcher by lazy { singleThreadPool.asCoroutineDispatcher() } - - val scheduler: ScheduledExecutorService by lazy { - Executors.newScheduledThreadPool(defaultPoolSize) { - Thread(it, "firefly-scheduler-thread") - } - } - - fun newSingleThreadExecutor(name: String): ExecutorService { - val executor = ThreadPoolExecutor( - 1, 1, 0, TimeUnit.MILLISECONDS, - LinkedTransferQueue() - ) { runnable -> Thread(runnable, name) } - return FinalizableExecutorService(executor) - } - - fun newComputationThreadExecutor(name: String, asyncMode: Boolean = true): ExecutorService { - val executor = ForkJoinPool(defaultPoolSize, { pool -> - val worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool) - worker.name = name + "-" + worker.poolIndex - worker - }, null, asyncMode) - return FinalizableExecutorService(executor) - } - - fun newSingleThreadDispatcher(name: String): CoroutineDispatcher { - return newSingleThreadExecutor(name).asCoroutineDispatcher() - } - - fun newComputationThreadDispatcher(name: String, asyncMode: Boolean = true): CoroutineDispatcher { - return newComputationThreadExecutor(name, asyncMode).asCoroutineDispatcher() - } - - fun stopAll() { - shutdownAndAwaitTermination(computationThreadPool, awaitTerminationTimeout) - shutdownAndAwaitTermination(singleThreadPool, awaitTerminationTimeout) - shutdownAndAwaitTermination(ioBlockingThreadPool, awaitTerminationTimeout) - shutdownAndAwaitTermination(scheduler, awaitTerminationTimeout) - } -} - -val applicationCleaner: Cleaner = Cleaner.create() - -class ExecutorCleanTask(private val executor: ExecutorService) : Runnable { - override fun run() { - if (!executor.isShutdown) { - shutdownAndAwaitTermination(executor, awaitTerminationTimeout) - } - } -} - -class FinalizableExecutorService(private val executor: ExecutorService) : ExecutorService by executor { - - init { - applicationCleaner.register(this, ExecutorCleanTask(executor)) - } -} - -class FinalizableScheduledExecutorService(private val executor: ScheduledExecutorService) : - ScheduledExecutorService by executor { - - init { - applicationCleaner.register(this, ExecutorCleanTask(executor)) - } -} - -val applicationScope = CoroutineScope(CoroutineName("Firefly-Application")) - -inline fun compute(crossinline block: suspend CoroutineScope.() -> Unit): Job = - applicationScope.launch(CoroutineDispatchers.computation) { block(this) } - -inline fun computeAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred = - applicationScope.async(CoroutineDispatchers.computation) { block(this) } - -inline fun blocking(crossinline block: suspend CoroutineScope.() -> Unit): Job = - applicationScope.launch(CoroutineDispatchers.ioBlocking) { block(this) } - -inline fun blockingAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred = - applicationScope.async(CoroutineDispatchers.ioBlocking) { block(this) } - -inline fun event(crossinline block: suspend CoroutineScope.() -> Unit): Job = - applicationScope.launch(CoroutineDispatchers.singleThread) { block(this) } - -inline fun eventAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred = - applicationScope.async(CoroutineDispatchers.singleThread) { block(this) } - -inline fun CoroutineScope.blocking(crossinline block: suspend CoroutineScope.() -> Unit): Job = - this.launch(CoroutineDispatchers.ioBlocking) { block(this) } - -inline fun CoroutineScope.blockingAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred = - this.async(CoroutineDispatchers.ioBlocking) { block(this) } \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CoroutineExtension.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CoroutineExtension.kt deleted file mode 100644 index a89317a88..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CoroutineExtension.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.common.coroutine - -import com.fireflysource.common.sys.Result -import kotlinx.coroutines.Job -import kotlinx.coroutines.future.asCompletableFuture -import java.util.concurrent.CompletableFuture - -fun Job.asVoidFuture(): CompletableFuture { - return this.asCompletableFuture().thenCompose { Result.DONE } -} \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CoroutineLocal.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CoroutineLocal.kt deleted file mode 100644 index 7dacb8a4e..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/coroutine/CoroutineLocal.kt +++ /dev/null @@ -1,160 +0,0 @@ -package com.fireflysource.common.coroutine - -import kotlinx.coroutines.* -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -/** - * Retain a value in the coroutine scope. - * - * @author Pengtao Qiu - */ -class CoroutineLocal { - - private val threadLocal = ThreadLocal() - - /** - * Convert a value to the coroutine context element. - * - * @param value A value runs through in the coroutine scope. - * @return The coroutine context element. - */ - fun asElement(value: T) = threadLocal.asContextElement(value) - - /** - * Get the value in the coroutine scope. - * - * @return The value in the coroutine scope. - */ - fun get(): T? = threadLocal.get() - - /** - * Set the value in the coroutine scope. - * - * @param value The value in the coroutine scope. - */ - fun set(value: T?) = threadLocal.set(value) -} - -/** - * Retain the attributes in the coroutine scope. - * - * @author Pengtao Qiu - */ -object CoroutineLocalContext { - - private val ctx: CoroutineLocal> by lazy { CoroutineLocal() } - - /** - * Convert the attributes to the coroutine context element. - * - * @param attributes The attributes run through in the coroutine scope. - * @return The coroutine context element. - */ - fun asElement(attributes: MutableMap): ThreadContextElement> = - ctx.asElement(HashMap(attributes)) - - /** - * Inherit parent coroutine context element, and merge it into current attributes. - * - * @param attributes The attributes run through in the coroutine scope. - * @return The coroutine context element. - */ - fun inheritParentElement(attributes: MutableMap? = null): ThreadContextElement> { - val newAttributes = HashMap() - val parentAttributes = getAttributes() - if (parentAttributes != null) { - newAttributes.putAll(parentAttributes) - } - if (attributes != null) { - newAttributes.putAll(attributes) - } - return ctx.asElement(newAttributes) - } - - /** - * Get the current attributes. - * - * @return The current attributes. - */ - fun getAttributes(): MutableMap? = ctx.get() - - /** - * Get an attribute in the current coroutine scope. - * - * @param name The name of attribute. - * @return An attribute in the current coroutine scope. - */ - inline fun getAttr(name: String): T? { - return getAttributes()?.get(name) as T? - } - - /** - * Get an attribute in the current coroutine scope, if the value is null return the default value. - * - * @param name The name of attribute. - * @param func Get the default value lazily. - * @return An attribute in the current coroutine scope, or the default value. - */ - inline fun getAttrOrDefault(name: String, crossinline func: (String) -> T): T { - return getAttributes()?.get(name) as T? ?: func(name) - } - - /** - * Set an attribute in the current coroutine scope. - * - * @param name The attribute's name. - * @param value The attribute's value. - * @return The old value in the current coroutine scope. - */ - inline fun setAttr(name: String, value: T): T? { - return getAttributes()?.put(name, value) as T? - } - - /** - * If the specified attribute name does not already associate with a value (or is mapped - * to null), attempts to compute its value using the given mapping - * function and enters it into this map unless null. - * - * @param name The attribute's name. - * @param func The mapping function. - * @return The value in the current coroutine scope. - */ - inline fun computeIfAbsent(name: String, crossinline func: (String) -> T): T? { - return getAttributes()?.computeIfAbsent(name) { func(it) } as T? - } -} - -inline fun CoroutineScope.inheritableAsync( - context: CoroutineContext = EmptyCoroutineContext, - attributes: MutableMap? = null, - start: CoroutineStart = CoroutineStart.DEFAULT, - crossinline block: suspend CoroutineScope.() -> T -): Deferred { - return this.async(context + CoroutineLocalContext.inheritParentElement(attributes), start) { block(this) } -} - -inline fun CoroutineScope.inheritableLaunch( - context: CoroutineContext = EmptyCoroutineContext, - attributes: MutableMap? = null, - start: CoroutineStart = CoroutineStart.DEFAULT, - crossinline block: suspend CoroutineScope.() -> Unit -): Job { - return this.launch(context + CoroutineLocalContext.inheritParentElement(attributes), start) { block(this) } -} - -/** - * Starts an asynchronous task waiting the result and inherits parent coroutine local attributes. - * - * @param context Additional to [CoroutineScope.coroutineContext] context of the coroutine. - * @param attributes The attributes merge into the parent coroutine context element. - * @param block The coroutine code block. - * @return The result. - */ -suspend inline fun withContextInheritable( - context: CoroutineContext = EmptyCoroutineContext, - attributes: MutableMap? = null, - crossinline block: suspend CoroutineScope.() -> T -): T { - return withContext(context + CoroutineLocalContext.inheritParentElement(attributes)) { block(this) } -} \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/exception/UnsupportedOperationException.java b/firefly-common/src/main/kotlin/com/fireflysource/common/exception/UnsupportedOperationException.java deleted file mode 100644 index 30c04d4f9..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/exception/UnsupportedOperationException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.common.exception; - -/** - * @author Pengtao Qiu - */ -public class UnsupportedOperationException extends RuntimeException { - - public UnsupportedOperationException() { - } - - public UnsupportedOperationException(String message) { - super(message); - } -} diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/io/BufferExtension.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/io/BufferExtension.kt deleted file mode 100644 index e9c2c233d..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/io/BufferExtension.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.fireflysource.common.io - -import java.nio.ByteBuffer -import java.nio.charset.Charset - -fun ByteBuffer.append(buffer: ByteBuffer): ByteBuffer { - BufferUtils.append(this, buffer) - return this -} - -fun ByteBuffer.addCapacity(capacity: Int): ByteBuffer { - return BufferUtils.addCapacity(this, capacity) -} - -fun ByteBuffer.flipToFill(): Int = BufferUtils.flipToFill(this) - -fun ByteBuffer.flipToFlush(position: Int): ByteBuffer { - BufferUtils.flipToFlush(this, position) - return this -} - -fun ByteBuffer.copy(): ByteBuffer { - return if (this.hasRemaining()) BufferUtils.allocate(this.remaining()).append(this) - else BufferUtils.EMPTY_BUFFER -} - -fun String.toBuffer(): ByteBuffer { - return BufferUtils.toBuffer(this) -} - -fun String.toBuffer(charset: Charset): ByteBuffer { - return BufferUtils.toBuffer(this, charset) -} \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/io/Nio.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/io/Nio.kt deleted file mode 100644 index d1a8d6072..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/io/Nio.kt +++ /dev/null @@ -1,273 +0,0 @@ -@file:Suppress("BlockingMethodInNonBlockingContext", "KDocUnresolvedReference") - -package com.fireflysource.common.io - -import com.fireflysource.common.coroutine.CoroutineDispatchers.ioBlockingThreadPool -import com.fireflysource.common.coroutine.blocking -import com.fireflysource.common.coroutine.blockingAsync -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.future.await -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import java.io.Closeable -import java.net.SocketAddress -import java.nio.ByteBuffer -import java.nio.channels.* -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.LinkOption -import java.nio.file.OpenOption -import java.nio.file.Path -import java.nio.file.attribute.BasicFileAttributes -import java.util.concurrent.TimeUnit - -/** - * Performs [AsynchronousFileChannel.lock] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousFileChannel.lockAwait() = suspendCancellableCoroutine { cont -> - lock(cont, asyncIOHandler()) - closeOnCancel(cont) -} - -/** - * Performs [AsynchronousFileChannel.lock] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousFileChannel.lockAwait( - position: Long, - size: Long, - shared: Boolean -) = suspendCancellableCoroutine { cont -> - lock(position, size, shared, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -suspend fun T.useAwait(block: suspend (T) -> R): R { - try { - return block(this) - } catch (e: Throwable) { - throw e - } finally { - withContext(NonCancellable) { - this@useAwait?.closeAsync()?.join() - } - } -} - -suspend fun T.useAwait(block: suspend (T) -> R): R { - try { - return block(this) - } catch (e: Throwable) { - throw e - } finally { - withContext(NonCancellable) { - this@useAwait?.closeAsync()?.await() - } - } -} - -/** - * Close in the I/O blocking coroutine dispatcher - */ -fun Closeable.closeAsync() = blocking { - close() -} - -fun openFileChannelAsync(file: Path, vararg options: OpenOption) = blockingAsync { - AsynchronousFileChannel.open(file, setOf(*options), ioBlockingThreadPool) -} - -fun openFileChannelAsync(file: Path, options: Set) = blockingAsync { - AsynchronousFileChannel.open(file, options, ioBlockingThreadPool) -} - -fun listFilesAsync(dir: Path) = blockingAsync { - Files.list(dir) -} - -fun readFileLinesAsync(file: Path, charset: Charset = StandardCharsets.UTF_8) = blockingAsync { - Files.readAllLines(file, charset) -} - -fun readFileBytesAsync(file: Path) = blockingAsync { - Files.readAllBytes(file) -} - -fun deleteIfExistsAsync(file: Path) = blockingAsync { - Files.deleteIfExists(file) -} - -fun existsAsync(file: Path, vararg options: LinkOption) = blockingAsync { - Files.exists(file, *options) -} - -fun readAttributesAsync(file: Path, vararg options: LinkOption) = blockingAsync { - Files.readAttributes(file, BasicFileAttributes::class.java, *options) -} - -fun writeFileLinesAsync(file: Path, iterable: Iterable, vararg options: OpenOption) = blockingAsync { - Files.write(file, iterable, *options) -} - -fun writeFileBytesAsync(file: Path, byteArray: ByteArray, vararg options: OpenOption) = blockingAsync { - Files.write(file, byteArray, *options) -} - - - -/** - * Performs [AsynchronousFileChannel.read] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousFileChannel.readAwait( - buf: ByteBuffer, - position: Long -) = suspendCancellableCoroutine { cont -> - read(buf, position, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -/** - * Performs [AsynchronousFileChannel.write] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousFileChannel.writeAwait( - buf: ByteBuffer, - position: Long -) = suspendCancellableCoroutine { cont -> - write(buf, position, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -/** - * Performs [AsynchronousServerSocketChannel.accept] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousServerSocketChannel.acceptAwait() = - suspendCancellableCoroutine { cont -> - accept(cont, asyncIOHandler()) - closeOnCancel(cont) - } - -/** - * Performs [AsynchronousSocketChannel.connect] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousSocketChannel.connectAwait( - socketAddress: SocketAddress -) = suspendCancellableCoroutine { cont -> - connect(socketAddress, cont, AsyncVoidIOHandler) - closeOnCancel(cont) -} - -/** - * Performs [AsynchronousSocketChannel.read] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousSocketChannel.readAwait( - buf: ByteBuffer, - timeout: Long = 0L, - timeUnit: TimeUnit = TimeUnit.MILLISECONDS -) = suspendCancellableCoroutine { cont -> - read(buf, timeout, timeUnit, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -suspend fun AsynchronousSocketChannel.readAwait( - buffers: Array, - offset: Int, - length: Int, - timeout: Long = 0L, - timeUnit: TimeUnit = TimeUnit.MILLISECONDS -) = suspendCancellableCoroutine { cont -> - read(buffers, offset, length, timeout, timeUnit, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -/** - * Performs [AsynchronousSocketChannel.write] without blocking a thread and resumes when asynchronous operation completes. - * This suspending function is cancellable. - * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function - * *closes the underlying channel* and immediately resumes with [CancellationException]. - */ -suspend fun AsynchronousSocketChannel.writeAwait( - buf: ByteBuffer, - timeout: Long = 0L, - timeUnit: TimeUnit = TimeUnit.MILLISECONDS -) = suspendCancellableCoroutine { cont -> - write(buf, timeout, timeUnit, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -suspend fun AsynchronousSocketChannel.writeAwait( - buffers: Array, - offset: Int, - length: Int, - timeout: Long = 0L, - timeUnit: TimeUnit = TimeUnit.MILLISECONDS -) = suspendCancellableCoroutine { cont -> - write(buffers, offset, length, timeout, timeUnit, cont, asyncIOHandler()) - closeOnCancel(cont) -} - -// ---------------- private details ---------------- - -private fun Channel.closeOnCancel(cont: CancellableContinuation<*>) { - cont.invokeOnCancellation { - try { - close() - } catch (ex: Throwable) { - // Specification says that it is Ok to call it any time, but reality is different, - // so we have just to ignore exception - } - } -} - -@Suppress("UNCHECKED_CAST") -private fun asyncIOHandler(): CompletionHandler> = - AsyncIOHandlerAny as CompletionHandler> - -private object AsyncIOHandlerAny : CompletionHandler> { - override fun completed(result: Any, cont: CancellableContinuation) { - cont.resumeWith(Result.success(result)) - } - - override fun failed(ex: Throwable, cont: CancellableContinuation) { - // just return if already cancelled and got an expected exception for that case - if (ex is AsynchronousCloseException && cont.isCancelled) { - return - } - cont.resumeWith(Result.failure(ex)) - } -} - -private object AsyncVoidIOHandler : CompletionHandler> { - override fun completed(result: Void?, cont: CancellableContinuation) { - cont.resumeWith(Result.success(Unit)) - } - - override fun failed(ex: Throwable, cont: CancellableContinuation) { - // just return if already cancelled and got an expected exception for that case - if (ex is AsynchronousCloseException && cont.isCancelled) { - return - } - cont.resumeWith(Result.failure(ex)) - } -} diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/reflect/KotlinNameResolver.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/reflect/KotlinNameResolver.kt deleted file mode 100644 index 1bcd93f72..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/reflect/KotlinNameResolver.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.fireflysource.common.reflect - -import java.lang.reflect.Modifier - -/** - * Resolves name of java classes. - * - * @author Pengtao Qiu - */ -object KotlinNameResolver { - /** - * Get class name for function by the package of the function. - * - * @param func Get class name for the function. - */ - fun name(func: () -> Unit): String { - val name = func.javaClass.name - return when { - name.contains("Kt$") -> name.substringBefore("Kt$") - name.contains("$") -> name.substringBefore("$") - else -> name - } - } - - /** - * Get class name for java class (that usually represents kotlin class) - * - * @param forClass Get the name for the class. - */ - fun name(forClass: Class): String = unwrapCompanionClass(forClass).name - - - /** - * unwrap companion class to enclosing class given a Java Class - */ - private fun unwrapCompanionClass(clazz: Class): Class<*> { - if (clazz.enclosingClass != null) { - try { - val field = clazz.enclosingClass.getField(clazz.simpleName) - if (Modifier.isStatic(field.modifiers) && field.type == clazz) { - // && field.get(null) === obj - // the above might be safer but problematic with initialization order - return clazz.enclosingClass - } - } catch (e: Exception) { - //ok, it is not a companion object - } - } - return clazz - } -} \ No newline at end of file diff --git a/firefly-common/src/main/kotlin/com/fireflysource/common/slf4j/Slf4jExtension.kt b/firefly-common/src/main/kotlin/com/fireflysource/common/slf4j/Slf4jExtension.kt deleted file mode 100644 index c166dc54a..000000000 --- a/firefly-common/src/main/kotlin/com/fireflysource/common/slf4j/Slf4jExtension.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.fireflysource.common.slf4j - -import com.fireflysource.common.reflect.KotlinNameResolver -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -/** - * @author Pengtao Qiu - */ -object LazyLoggerKt { - fun getLogger(func: () -> Unit): Logger = LoggerFactory.getLogger(KotlinNameResolver.name(func)) - - fun getLogger(name: String): Logger = LoggerFactory.getLogger(name) - - fun getLogger(clazz: Class<*>): Logger = LoggerFactory.getLogger(clazz) -} - -/** - * Lazy add a log message if isTraceEnabled is true - */ -inline fun Logger.trace(crossinline msg: () -> Any?) { - if (isTraceEnabled) trace(toStringSafe(msg)) -} - -/** - * Lazy add a log message if isDebugEnabled is true - */ -inline fun Logger.debug(crossinline msg: () -> Any?) { - if (isDebugEnabled) debug(toStringSafe(msg)) -} - -/** - * Lazy add a log message if isInfoEnabled is true - */ -inline fun Logger.info(crossinline msg: () -> Any?) { - if (isInfoEnabled) info(toStringSafe(msg)) -} - -/** - * Lazy add a log message if isWarnEnabled is true - */ -inline fun Logger.warn(crossinline msg: () -> Any?) { - if (isWarnEnabled) warn(toStringSafe(msg)) -} - -/** - * Lazy add a log message if isErrorEnabled is true - */ -inline fun Logger.error(crossinline msg: () -> Any?) { - if (isErrorEnabled) error(toStringSafe(msg)) -} - -/** - * Lazy add a log message with throwable payload if isTraceEnabled is true - */ -inline fun Logger.trace(throwable: Throwable, crossinline msg: () -> Any?) { - if (isTraceEnabled) trace(toStringSafe(msg), throwable) -} - -/** - * Lazy add a log message with throwable payload if isDebugEnabled is true - */ -inline fun Logger.debug(throwable: Throwable, crossinline msg: () -> Any?) { - if (isDebugEnabled) debug(toStringSafe(msg), throwable) -} - -/** - * Lazy add a log message with throwable payload if isInfoEnabled is true - */ -inline fun Logger.info(throwable: Throwable, crossinline msg: () -> Any?) { - if (isInfoEnabled) info(toStringSafe(msg), throwable) -} - -/** - * Lazy add a log message with throwable payload if isWarnEnabled is true - */ -inline fun Logger.warn(throwable: Throwable, crossinline msg: () -> Any?) { - if (isWarnEnabled) warn(toStringSafe(msg), throwable) -} - -/** - * Lazy add a log message with throwable payload if isErrorEnabled is true - */ -inline fun Logger.error(throwable: Throwable, crossinline msg: () -> Any?) { - if (isErrorEnabled) error(toStringSafe(msg), throwable) -} - - -inline fun toStringSafe(crossinline msg: () -> Any?): String { - return try { - msg.invoke().toString() - } catch (e: Exception) { - "KtLogger: get message exception: $e" - } -} \ No newline at end of file diff --git a/firefly-common/src/main/resources/firefly_version.properties b/firefly-common/src/main/resources/firefly_version.properties deleted file mode 100644 index f54db2138..000000000 --- a/firefly-common/src/main/resources/firefly_version.properties +++ /dev/null @@ -1,2 +0,0 @@ -firefly.version=${firefly.version} -github.url=${github.url} \ No newline at end of file diff --git a/firefly-common/src/test/java/com/fireflysource/common/actor/TestActor.java b/firefly-common/src/test/java/com/fireflysource/common/actor/TestActor.java deleted file mode 100644 index ede25fdac..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/actor/TestActor.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.fireflysource.common.actor; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class TestActor { - - @Test - @DisplayName("should purchase products successfully") - void test() throws Exception { - StoreActor store = new StoreActor(); - stock(store, "iPhone", 5200, 5); - stock(store, "h301x", 310, 20); - - List> results = new LinkedList<>(); - results.addAll(purchase(store, "h301x", 400, 10)); - results.addAll(purchase(store, "iPhone", 6500, 9)); - - StoreActor.CloseMessage closeMessage = new StoreActor.CloseMessage(); - results.add(closeMessage.todayAmount.thenApply(amount -> { - log("Today sales amount: " + amount); - return null; - })); - store.offer(closeMessage); - - CompletableFuture.allOf(results.stream().map(r -> r.handle((ignore, throwable) -> { - Optional.ofNullable(throwable).map(Throwable::getMessage).ifPresent(System.out::println); - return ignore; - })).toArray(CompletableFuture[]::new)).join(); - assertEquals(36500L, closeMessage.todayAmount.get()); - } - - private void stock(StoreActor store, String name, long price, int count) { - IntStream.range(0, count).parallel() - .forEach(i -> store.offer(new StoreActor.StockMessage(new StoreActor.Product(name, price)))); - } - - private List> purchase(StoreActor store, String name, long price, int count) { - return IntStream.range(0, count).parallel().boxed().map(i -> { - StoreActor.PurchaseMessage purchaseMessage = new StoreActor.PurchaseMessage(new StoreActor.Product(name, price)); - store.offer(purchaseMessage); - return purchaseMessage.result.thenAccept(ignore -> log("purchase " + name + " success.")); - }).collect(Collectors.toList()); - } - - public static class StoreActor extends AbstractAsyncActor { - - public enum MessageType { - PURCHASE, STOCK, CLOSE - } - - public interface Message { - MessageType getType(); - } - - public static class Product { - public final String name; - public final long price; - - public Product(String name, long price) { - this.name = name; - this.price = price; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Product product = (Product) o; - return name.equals(product.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - } - - public static class PurchaseMessage implements Message { - - public final Product product; - public final CompletableFuture result = new CompletableFuture<>(); - - public PurchaseMessage(Product product) { - this.product = product; - } - - @Override - public MessageType getType() { - return MessageType.PURCHASE; - } - } - - public static class StockMessage implements Message { - public final Product product; - - public StockMessage(Product product) { - this.product = product; - } - - @Override - public MessageType getType() { - return MessageType.STOCK; - } - } - - public static class CloseMessage implements Message { - public final CompletableFuture todayAmount = new CompletableFuture<>(); - - @Override - public MessageType getType() { - return MessageType.CLOSE; - } - } - - private final Map> products = new HashMap<>(); - private long amount; - - @Override - public CompletableFuture onReceiveAsync(Message message) { - switch (message.getType()) { - case STOCK: - StockMessage stockMessage = (StockMessage) message; - return stock(stockMessage.product); - case PURCHASE: - PurchaseMessage purchaseMessage = (PurchaseMessage) message; - return purchase(purchaseMessage); - case CLOSE: - shutdown(); - CloseMessage closeMessage = (CloseMessage) message; - closeMessage.todayAmount.complete(amount); - return CompletableFuture.completedFuture(null); - default: - return CompletableFuture.completedFuture(null); - } - } - - private CompletableFuture stock(Product product) { - return CompletableFuture.runAsync(() -> { - BlockingTask.sleep(100); - products.computeIfAbsent(product.name, k -> new LinkedList<>()).offer(product); - log("stock " + product.name + " success."); - }); - } - - private CompletableFuture purchase(PurchaseMessage purchaseMessage) { - return CompletableFuture.runAsync(() -> { - BlockingTask.sleep(200); - Optional orderProduct = Optional.ofNullable(products.get(purchaseMessage.product.name)).map(Queue::poll); - if (orderProduct.isPresent()) { - amount += purchaseMessage.product.price; - purchaseMessage.result.complete(null); - } else { - purchaseMessage.result.completeExceptionally(new IllegalStateException("The product sells out")); - } - }); - } - - @Override - public void onDiscard(Message message) { - log("discard message: " + message.getType()); - if (message.getType() == MessageType.PURCHASE) { - PurchaseMessage purchaseMessage = (PurchaseMessage) message; - purchaseMessage.result.completeExceptionally(new IllegalStateException("The store is close")); - } - } - } - - public static void log(String text) { - System.out.println(LocalDateTime.now().format(ISO_LOCAL_DATE_TIME) + " " + Thread.currentThread().getName() + " -- " + text); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/bytecode/TestClassProxy.java b/firefly-common/src/test/java/com/fireflysource/common/bytecode/TestClassProxy.java deleted file mode 100644 index 82cec1381..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/bytecode/TestClassProxy.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.fireflysource.common.bytecode; - - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.lang.reflect.InvocationTargetException; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -class TestClassProxy { - - static Stream parametersProvider() { - return Stream.of(arguments(JavassistClassProxyFactory.INSTANCE)); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - void test(ClassProxyFactory classProxyFactory) throws Throwable { - Fee origin = new Fee(); - - Fee fee = classProxyFactory.createProxy(origin, - (handler, originalInstance, args) -> { - System.out.println("intercept method 1: " + handler.method().getName() + "|" + originalInstance.getClass().getCanonicalName()); - if (handler.method().getName().equals("testInt")) { - args[0] = 1; - } - Object ret = handler.invoke(originalInstance, args); - System.out.println("intercept method 1 end..."); - if (handler.method().getName().equals("hello")) { - ret = ret + " intercept 1"; - } - return ret; - }, null); - System.out.println(fee.getClass().getCanonicalName()); - assertEquals("hello fee intercept 1", fee.hello()); - assertEquals(1, fee.testInt(25)); - - Fee fee2 = classProxyFactory.createProxy(fee, - (handler, originalInstance, args) -> { - System.out.println("intercept method 2: " + handler.method().getName() + "|" + originalInstance.getClass().getCanonicalName()); - if (handler.method().getName().equals("testInt")) { - args[0] = 2; - } - Object ret = handler.invoke(originalInstance, args); - System.out.println("intercept method 2 end..."); - - if (handler.method().getName().equals("hello")) { - ret = ret + " intercept 2"; - } - return ret; - }, null); - System.out.println(fee2.getClass().getCanonicalName()); - assertEquals("hello fee intercept 1 intercept 2", fee2.hello()); - assertEquals(1, fee.testInt(25)); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - void testFilter(ClassProxyFactory classProxyFactory) throws Throwable { - Fee origin = new Fee(); - - Fee fee = classProxyFactory.createProxy(origin, - (handler, originalInstance, args) -> { - System.out.println("filter method 1: " + handler.method().getName() + "|" + originalInstance.getClass().getCanonicalName()); - if (handler.method().getName().equals("testInt")) { - args[0] = 1; - } - Object ret = handler.invoke(originalInstance, args); - System.out.println("filter method 1 end..."); - if (handler.method().getName().equals("hello")) { - ret = ret + " filter 1"; - } - return ret; - }, method -> !method.getName().equals("testInt")); - System.out.println(fee.getClass().getCanonicalName()); - assertEquals("hello fee filter 1", fee.hello()); - assertEquals(25, fee.testInt(25)); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - void testNonJavaBean(ClassProxyFactory classProxyFactory) { - assertThrows(InvocationTargetException.class, () -> { - NonJavaBean x = new NonJavaBean("test"); - NonJavaBean y = classProxyFactory.createProxy(x, - (handler, originalInstance, args) -> "no java bean", - method -> method.getName().equals("getHello")); - System.out.println(y.getHello()); - }); - } - - public static class Fee { - protected void testProtected() { - } - - public void testVoid(String str, Long l) { - } - - public int testInt(int i) { - return i; - } - - public Void testParameters(String str, int i, Long l) { - return null; - } - - public String hello() { - return "hello fee"; - } - } - - public static class NonJavaBean { - String hello; - - public NonJavaBean(String hello) { - this.hello = hello; - } - - public String getHello() { - return hello; - } - - public void setHello(String hello) { - this.hello = hello; - } - } - -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/bytecode/TestProxyFactory.java b/firefly-common/src/test/java/com/fireflysource/common/bytecode/TestProxyFactory.java deleted file mode 100644 index ea026367a..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/bytecode/TestProxyFactory.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.fireflysource.common.bytecode; - - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.lang.reflect.Field; -import java.util.stream.Stream; - -import static com.fireflysource.common.reflection.ReflectionUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.params.provider.Arguments.arguments; - - -/** - * @author Pengtao Qiu - */ -public class TestProxyFactory { - - static Stream parametersProvider() { - return Stream.of( - arguments(JavaReflectionProxyFactory.INSTANCE), - arguments(JavassistReflectionProxyFactory.INSTANCE) - ); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - void testProxyMethod(ProxyFactory proxyFactory) throws Throwable { - Foo foo = new Foo(); - MethodProxy proxy = proxyFactory.getMethodProxy(Foo.class.getMethod("setProperty", String.class, boolean.class)); - assertNull(proxy.invoke(foo, "proxy foo", true)); - assertEquals("proxy foo", foo.getName()); - assertTrue(foo.isFailure()); - - proxy = proxyFactory.getMethodProxy(getGetterMethod(Foo.class, "name")); - assertEquals("proxy foo", proxy.invoke(foo)); - - proxy = proxyFactory.getMethodProxy(getGetterMethod(Foo.class, "failure")); - assertTrue((Boolean) proxy.invoke(foo)); - - proxy = proxyFactory.getMethodProxy(getSetterMethod(Foo.class, "price")); - assertNull(proxy.invoke(foo, 35.5)); - assertEquals(35.5, foo.getPrice()); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - void testArray(ProxyFactory proxyFactory) { - int[] intArr = new int[5]; - Integer[] intArr2 = new Integer[10]; - - ArrayProxy intArrProxy = proxyFactory.getArrayProxy(intArr.getClass()); - ArrayProxy intArr2Proxy = proxyFactory.getArrayProxy(intArr2.getClass()); - - assertEquals(5, intArrProxy.size(intArr)); - assertEquals(10, intArr2Proxy.size(intArr2)); - - intArrProxy.set(intArr, 0, 33); - assertEquals(33, intArrProxy.get(intArr, 0)); - - intArr2Proxy.set(intArr2, intArr2.length - 1, 55); - assertEquals(55, intArr2Proxy.get(intArr2, 9)); - - intArrProxy.set(intArr, 1, 23); - assertEquals(23, intArrProxy.get(intArr, 1)); - - intArr2Proxy.set(intArr2, intArr2.length - 1, 65); - assertEquals(65, intArr2Proxy.get(intArr2, 9)); - } - - @ParameterizedTest - @MethodSource("parametersProvider") - void testProxyField(ProxyFactory proxyFactory) throws Throwable { - Foo foo = new Foo(); - Field num2 = Foo.class.getField("num2"); - Field info = Foo.class.getField("info"); - - FieldProxy proxyNum2 = proxyFactory.getFieldProxy(num2); - proxyNum2.set(foo, 30); - assertEquals(30, proxyNum2.get(foo)); - - FieldProxy proxyInfo = proxyFactory.getFieldProxy(info); - proxyInfo.set(foo, "test info 0"); - assertEquals("test info 0", proxyInfo.get(foo)); - - setProperty(foo, "name", "hello"); - assertEquals("hello", getProperty(foo, "name")); - - - Foo foo2 = new Foo(); - - proxyNum2 = proxyFactory.getFieldProxy(num2); - proxyNum2.set(foo2, 303); - assertEquals(303, proxyNum2.get(foo2)); - - proxyInfo = proxyFactory.getFieldProxy(info); - proxyInfo.set(foo2, "test info 03"); - assertEquals("test info 03", proxyInfo.get(foo2)); - } - - public static class Foo { - public String name; - public int num2; - public String info; - private boolean failure; - private int number; - private double price; - private String iPhone; - private boolean iPad; - - public String getiPhone() { - return iPhone; - } - - public void setiPhone(String iPhone) { - this.iPhone = iPhone; - } - - public boolean isiPad() { - return iPad; - } - - public void setiPad(boolean iPad) { - this.iPad = iPad; - } - - public int getNumber() { - return number; - } - - public void setNumber(int number) { - this.number = number; - } - - public double getPrice() { - return price; - } - - public void setPrice(double price) { - this.price = price; - } - - public boolean isFailure() { - return failure; - } - - public void setFailure(boolean failure) { - this.failure = failure; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public void setProperty(String name, boolean failure) { - this.name = name; - this.failure = failure; - } - - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/codec/TestBase64.java b/firefly-common/src/test/java/com/fireflysource/common/codec/TestBase64.java deleted file mode 100644 index 9528b656e..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/codec/TestBase64.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.common.codec; - -import com.fireflysource.common.codec.base64.Base64Utils; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class TestBase64 { - - @Test - void test() { - byte[] hello = "hello world".getBytes(StandardCharsets.UTF_8); - String base64 = Base64Utils.encodeToString(hello); - String src = new String(Base64Utils.decodeFromString(base64), StandardCharsets.UTF_8); - assertEquals("hello world", src); - } - - @Test - void testSafeUrl() { - byte[] safeUrl = "http://www.fireflysource.com/base64/test?id=测试".getBytes(StandardCharsets.UTF_8); - String base64 = Base64Utils.encodeToUrlSafeString(safeUrl); - System.out.println(base64); - String src = new String(Base64Utils.decodeFromUrlSafeString(base64), StandardCharsets.UTF_8); - assertEquals("http://www.fireflysource.com/base64/test?id=测试", src); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/collection/TestCollectionUtils.java b/firefly-common/src/test/java/com/fireflysource/common/collection/TestCollectionUtils.java deleted file mode 100644 index cb13813f7..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/collection/TestCollectionUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.fireflysource.common.collection; - -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Set; - -import static com.fireflysource.common.collection.CollectionUtils.newHashSet; -import static org.junit.jupiter.api.Assertions.*; - -class TestCollectionUtils { - - @Test - void testIsEmpty() { - assertTrue(CollectionUtils.isEmpty(Collections.emptyMap())); - assertTrue(CollectionUtils.isEmpty(Collections.emptyList())); - assertTrue(CollectionUtils.isEmpty(new HashMap<>())); - Set map = null; - assertTrue(CollectionUtils.isEmpty(map)); - } - - @Test - void testIntersection() { - Set a = newHashSet(2, 3, 4); - Set b = newHashSet(3, 4, 5, 6); - Set result = CollectionUtils.intersect(a, b); - - assertEquals(3, a.size()); - assertEquals(4, b.size()); - assertEquals(newHashSet(3, 4), result); - } - - @Test - void testIntersectionEmpty() { - Set a = newHashSet(2, 3, 4); - Set b = newHashSet(5, 6); - Set result = CollectionUtils.intersect(a, b); - assertTrue(result.isEmpty()); - } - - @Test - void testHasIntersection() { - Set a = newHashSet(2, 3, 4); - Set b = newHashSet(3, 4, 5, 6); - assertTrue(CollectionUtils.hasIntersection(a, b)); - } - - @Test - void testNoIntersection() { - Set a = newHashSet(1, 2, 3, 4); - Set b = newHashSet(5, 6); - assertFalse(CollectionUtils.hasIntersection(a, b)); - assertFalse(CollectionUtils.hasIntersection(a, newHashSet())); - assertFalse(CollectionUtils.hasIntersection(newHashSet(), b)); - assertFalse(CollectionUtils.hasIntersection(newHashSet(), newHashSet())); - } - - @Test - void testUnion() { - Set a = newHashSet(2, 3, 4); - Set b = newHashSet(3, 4, 5, 6); - Set result = CollectionUtils.union(a, b); - assertEquals(newHashSet(2, 3, 4, 5, 6), result); - - result = CollectionUtils.union(a, newHashSet()); - assertEquals(a, result); - } - -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/collection/list/TestLazyList.java b/firefly-common/src/test/java/com/fireflysource/common/collection/list/TestLazyList.java deleted file mode 100644 index 3fed43392..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/collection/list/TestLazyList.java +++ /dev/null @@ -1,1968 +0,0 @@ -package com.fireflysource.common.collection.list; - -import com.fireflysource.common.collection.array.ArrayUtils; -import org.junit.jupiter.api.Test; - -import java.net.URI; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - - -/** - * Tests for LazyList utility class. - */ -class TestLazyList { - private static final boolean STRICT = false; - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_NullInput_NullItem() { - Object list = LazyList.add(null, null); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - } - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_NullInput_NonListItem() { - String item = "a"; - Object list = LazyList.add(null, item); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - } - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_NullInput_LazyListItem() { - Object item = LazyList.add(null, "x"); - item = LazyList.add(item, "y"); - item = LazyList.add(item, "z"); - - Object list = LazyList.add(null, item); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - - Object val = LazyList.get(list, 0); - assertTrue(val instanceof List); - } - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_NonListInput() { - String input = "a"; - - Object list = LazyList.add(input, "b"); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(2, LazyList.size(list)); - } - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_LazyListInput() { - Object input = LazyList.add(null, "a"); - - Object list = LazyList.add(input, "b"); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - - list = LazyList.add(list, "c"); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_GenericListInput() { - List input = new ArrayList<>(); - input.add("a"); - - Object list = LazyList.add(input, "b"); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - - list = LazyList.add(list, "c"); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#add(Object, Object)} - */ - @Test - void testAddObjectObject_AddNull() { - Object list = null; - list = LazyList.add(list, null); - assertEquals(1, LazyList.size(list)); - assertNull(LazyList.get(list, 0)); - - list = "a"; - list = LazyList.add(list, null); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertNull(LazyList.get(list, 1)); - - list = LazyList.add(list, null); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertNull(LazyList.get(list, 1)); - assertNull(LazyList.get(list, 2)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NullInput_NullItem() { - Object list = LazyList.add(null, 0, null); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NullInput_NonListItem() { - String item = "a"; - Object list = LazyList.add(null, 0, item); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NullInput_NonListItem2() { - assumeTrue(STRICT); // Only run in STRICT mode. - - String item = "a"; - // Test branch of logic "index>0" - Object list = LazyList.add(null, 1, item); // Always throws exception? - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NullInput_LazyListItem() { - Object item = LazyList.add(null, "x"); - item = LazyList.add(item, "y"); - item = LazyList.add(item, "z"); - - Object list = LazyList.add(null, 0, item); - assertNotNull(list); - assertEquals(1, LazyList.size(list)); - - Object val = LazyList.get(list, 0); - assertTrue(val instanceof List); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NullInput_GenericListItem() { - List item = new ArrayList<>(); - item.add("a"); - - Object list = LazyList.add(null, 0, item); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NonListInput_NullItem() { - String input = "a"; - - Object list = LazyList.add(input, 0, null); - assertNotNull(list); - assertEquals(2, LazyList.size(list)); - assertNull(LazyList.get(list, 0)); - assertEquals("a", LazyList.get(list, 1)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_NonListInput_NonListItem() { - String input = "a"; - String item = "b"; - - Object list = LazyList.add(input, 0, item); - assertNotNull(list); - assertEquals(2, LazyList.size(list)); - assertEquals("b", LazyList.get(list, 0)); - assertEquals("a", LazyList.get(list, 1)); - } - - /** - * Test for {@link LazyList#add(Object, int, Object)} - */ - @Test - void testAddObjectIntObject_LazyListInput() { - Object list = LazyList.add(null, "c"); // [c] - list = LazyList.add(list, 0, "a"); // [a, c] - list = LazyList.add(list, 1, "b"); // [a, b, c] - list = LazyList.add(list, 3, "d"); // [a, b, c, d] - - assertEquals(4, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - assertEquals("d", LazyList.get(list, 3)); - } - - /** - * Test for {@link LazyList#addCollection(Object, Collection)} - */ - @Test - void testAddCollection_NullInput() { - Collection coll = Arrays.asList("a", "b", "c"); - - Object list = LazyList.addCollection(null, coll); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Test for {@link LazyList#addCollection(Object, Collection)} - */ - @Test - void testAddCollection_NonListInput() { - Collection coll = Arrays.asList("a", "b", "c"); - String input = "z"; - - Object list = LazyList.addCollection(input, coll); - assertTrue(list instanceof List); - assertEquals(4, LazyList.size(list)); - assertEquals("z", LazyList.get(list, 0)); - assertEquals("a", LazyList.get(list, 1)); - assertEquals("b", LazyList.get(list, 2)); - assertEquals("c", LazyList.get(list, 3)); - } - - /** - * Test for {@link LazyList#addCollection(Object, Collection)} - */ - @Test - void testAddCollection_LazyListInput() { - Collection coll = Arrays.asList("a", "b", "c"); - - Object input = LazyList.add(null, "x"); - input = LazyList.add(input, "y"); - input = LazyList.add(input, "z"); - - Object list = LazyList.addCollection(input, coll); - assertTrue(list instanceof List); - assertEquals(6, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - assertEquals("a", LazyList.get(list, 3)); - assertEquals("b", LazyList.get(list, 4)); - assertEquals("c", LazyList.get(list, 5)); - } - - /** - * Test for {@link LazyList#addCollection(Object, Collection)} - */ - @Test - void testAddCollection_GenricListInput() { - Collection coll = Arrays.asList("a", "b", "c"); - - List input = new ArrayList(); - input.add("x"); - input.add("y"); - input.add("z"); - - Object list = LazyList.addCollection(input, coll); - assertTrue(list instanceof List); - assertEquals(6, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - assertEquals("a", LazyList.get(list, 3)); - assertEquals("b", LazyList.get(list, 4)); - assertEquals("c", LazyList.get(list, 5)); - } - - /** - * Test for {@link LazyList#addCollection(Object, Collection)} - */ - @Test - void testAddCollection_Sequential() { - Collection coll = Arrays.asList("a", "b"); - - Object list = null; - list = LazyList.addCollection(list, coll); - list = LazyList.addCollection(list, coll); - - assertEquals(4, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("a", LazyList.get(list, 2)); - assertEquals("b", LazyList.get(list, 3)); - } - - /** - * Test for {@link LazyList#addCollection(Object, Collection)} - */ - @Test - void testAddCollection_GenericListInput() { - List l = new ArrayList<>(); - l.add("a"); - l.add("b"); - - Object list = null; - list = LazyList.addCollection(list, l); - list = LazyList.addCollection(list, l); - - assertEquals(4, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("a", LazyList.get(list, 2)); - assertEquals("b", LazyList.get(list, 3)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NullInput_NullArray() { - String[] arr = null; - Object list = LazyList.addArray(null, arr); - assertNull(list); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NullInput_EmptyArray() { - String[] arr = new String[0]; - Object list = LazyList.addArray(null, arr); - if (STRICT) { - assertNotNull(list); - assertTrue(list instanceof List); - } - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NullInput_SingleArray() { - String[] arr = new String[]{"a"}; - Object list = LazyList.addArray(null, arr); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NullInput_Array() { - String[] arr = new String[]{"a", "b", "c"}; - Object list = LazyList.addArray(null, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NonListInput_NullArray() { - String input = "z"; - String[] arr = null; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - assertEquals("z", LazyList.get(list, 0)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NonListInput_EmptyArray() { - String input = "z"; - String[] arr = new String[0]; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - assertEquals("z", LazyList.get(list, 0)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NonListInput_SingleArray() { - String input = "z"; - String[] arr = new String[]{"a"}; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(2, LazyList.size(list)); - assertEquals("z", LazyList.get(list, 0)); - assertEquals("a", LazyList.get(list, 1)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_NonListInput_Array() { - String input = "z"; - String[] arr = new String[]{"a", "b", "c"}; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(4, LazyList.size(list)); - assertEquals("z", LazyList.get(list, 0)); - assertEquals("a", LazyList.get(list, 1)); - assertEquals("b", LazyList.get(list, 2)); - assertEquals("c", LazyList.get(list, 3)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_LazyListInput_NullArray() { - Object input = LazyList.add(null, "x"); - input = LazyList.add(input, "y"); - input = LazyList.add(input, "z"); - - String[] arr = null; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_LazyListInput_EmptyArray() { - Object input = LazyList.add(null, "x"); - input = LazyList.add(input, "y"); - input = LazyList.add(input, "z"); - - String[] arr = new String[0]; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_LazyListInput_SingleArray() { - Object input = LazyList.add(null, "x"); - input = LazyList.add(input, "y"); - input = LazyList.add(input, "z"); - - String[] arr = new String[]{"a"}; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(4, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - assertEquals("a", LazyList.get(list, 3)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_LazyListInput_Array() { - Object input = LazyList.add(null, "x"); - input = LazyList.add(input, "y"); - input = LazyList.add(input, "z"); - - String[] arr = new String[]{"a", "b", "c"}; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(6, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - assertEquals("a", LazyList.get(list, 3)); - assertEquals("b", LazyList.get(list, 4)); - assertEquals("c", LazyList.get(list, 5)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_GenericListInput_NullArray() { - List input = new ArrayList(); - input.add("x"); - input.add("y"); - input.add("z"); - - String[] arr = null; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_GenericListInput_EmptyArray() { - List input = new ArrayList<>(); - input.add("x"); - input.add("y"); - input.add("z"); - - String[] arr = new String[0]; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_GenericListInput_SingleArray() { - List input = new ArrayList(); - input.add("x"); - input.add("y"); - input.add("z"); - - String[] arr = new String[]{"a"}; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(4, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - assertEquals("a", LazyList.get(list, 3)); - } - - /** - * Tests for {@link LazyList#addArray(Object, Object[])} - */ - @Test - void testAddArray_GenericListInput_Array() { - List input = new ArrayList<>(); - input.add("x"); - input.add("y"); - input.add("z"); - - String[] arr = new String[]{"a", "b", "c"}; - Object list = LazyList.addArray(input, arr); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(6, LazyList.size(list)); - assertEquals("x", LazyList.get(list, 0)); - assertEquals("y", LazyList.get(list, 1)); - assertEquals("z", LazyList.get(list, 2)); - assertEquals("a", LazyList.get(list, 3)); - assertEquals("b", LazyList.get(list, 4)); - assertEquals("c", LazyList.get(list, 5)); - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_NullInput() { - Object list = LazyList.ensureSize(null, 10); - assertNotNull(list); - assertTrue(list instanceof List); - // Not possible to test for List capacity value. - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_NonListInput() { - String input = "a"; - Object list = LazyList.ensureSize(input, 10); - assertNotNull(list); - assertTrue(list instanceof List); - // Not possible to test for List capacity value. - assertEquals(1, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - - Object list = LazyList.ensureSize(input, 10); - assertNotNull(list); - assertTrue(list instanceof List); - // Not possible to test for List capacity value. - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_GenericListInput() { - List input = new ArrayList<>(); - input.add("a"); - input.add("b"); - - Object list = LazyList.ensureSize(input, 10); - assertNotNull(list); - assertTrue(list instanceof List); - // Not possible to test for List capacity value. - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_GenericListInput_LinkedList() { - assumeTrue(STRICT); // Only run in STRICT mode. - - // Using LinkedList concrete type as LazyList internal - // implementation does not look for this specifically. - List input = new LinkedList(); - input.add("a"); - input.add("b"); - - Object list = LazyList.ensureSize(input, 10); - assertNotNull(list); - assertTrue(list instanceof List); - // Not possible to test for List capacity value. - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_Growth() { - List l = new ArrayList<>(); - l.add("a"); - l.add("b"); - l.add("c"); - - // NOTE: Testing for object equality might be viewed as - // fragile by most developers, however, for this - // specific implementation, we don't want the - // provided list to change if the size requirements - // have been met. - - // Trigger growth - Object ret = LazyList.ensureSize(l, 10); - assertNotSame(ret, l); - - // Growth not neeed. - ret = LazyList.ensureSize(l, 1); - assertSame(ret, l); - } - - /** - * Tests for {@link LazyList#ensureSize(Object, int)} - */ - @Test - void testEnsureSize_Growth_LinkedList() { - assumeTrue(STRICT); // Only run in STRICT mode. - - // Using LinkedList concrete type as LazyList internal - // implementation has not historically looked for this - // specifically. - List l = new LinkedList<>(); - l.add("a"); - l.add("b"); - l.add("c"); - - // NOTE: Testing for object equality might be viewed as - // fragile by most developers, however, for this - // specific implementation, we don't want the - // provided list to change if the size requirements - // have been met. - - // Trigger growth - Object ret = LazyList.ensureSize(l, 10); - assertTrue(ret != l); - - // Growth not neeed. - ret = LazyList.ensureSize(l, 1); - assertTrue(ret == l); - } - - /** - * Test for {@link LazyList#remove(Object, Object)} - */ - @Test - void testRemoveObjectObject_NullInput() { - Object input = null; - - assertNull(LazyList.remove(input, null)); - assertNull(LazyList.remove(input, "a")); - assertNull(LazyList.remove(input, new ArrayList<>())); - assertNull(LazyList.remove(input, Integer.valueOf(42))); - } - - /** - * Test for {@link LazyList#remove(Object, Object)} - */ - @Test - void testRemoveObjectObject_NonListInput() { - String input = "a"; - - // Remove null item - Object list = LazyList.remove(input, null); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - - // Remove item that doesn't exist - list = LazyList.remove(input, "b"); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - - // Remove item that exists - list = LazyList.remove(input, "a"); - // should this be null? or an empty list? - assertNull(list); // nothing left in list - assertEquals(0, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#remove(Object, Object)} - */ - @Test - void testRemoveObjectObject_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - // Remove null item - Object list = LazyList.remove(input, null); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - - // Attempt to remove something that doesn't exist - list = LazyList.remove(input, "z"); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - - // Remove something that exists in input - list = LazyList.remove(input, "b"); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("c", LazyList.get(list, 1)); - } - - /** - * Test for {@link LazyList#remove(Object, Object)} - */ - @Test - void testRemoveObjectObject_GenericListInput() { - List input = new ArrayList<>(); - input.add("a"); - input.add("b"); - input.add("c"); - - // Remove null item - Object list = LazyList.remove(input, null); - assertNotNull(list); - assertTrue(list instanceof List); - assertTrue(input == list); - assertEquals(3, LazyList.size(list)); - - // Attempt to remove something that doesn't exist - list = LazyList.remove(input, "z"); - assertNotNull(list); - assertTrue(list instanceof List); - assertTrue(input == list); - assertEquals(3, LazyList.size(list)); - - // Remove something that exists in input - list = LazyList.remove(input, "b"); - assertNotNull(list); - assertTrue(list instanceof List); - assertTrue(input == list); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("c", LazyList.get(list, 1)); - - // Try to remove the rest. - list = LazyList.remove(list, "a"); - list = LazyList.remove(list, "c"); - assertNull(list); - } - - /** - * Test for {@link LazyList#remove(Object, Object)} - */ - @Test - void testRemoveObjectObject_LinkedListInput() { - // Should be able to use any collection object. - List input = new LinkedList<>(); - input.add("a"); - input.add("b"); - input.add("c"); - - // Remove null item - Object list = LazyList.remove(input, null); - assertNotNull(list); - assertTrue(list instanceof List); - assertTrue(input == list); - assertEquals(3, LazyList.size(list)); - - // Attempt to remove something that doesn't exist - list = LazyList.remove(input, "z"); - assertNotNull(list); - assertTrue(list instanceof List); - assertTrue(input == list); - assertEquals(3, LazyList.size(list)); - - // Remove something that exists in input - list = LazyList.remove(input, "b"); - assertNotNull(list); - assertTrue(list instanceof List); - assertTrue(input == list); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("c", LazyList.get(list, 1)); - } - - /** - * Tests for {@link LazyList#remove(Object, int)} - */ - @Test - void testRemoveObjectInt_NullInput() { - Object input = null; - - assertNull(LazyList.remove(input, 0)); - assertNull(LazyList.remove(input, 2)); - assertNull(LazyList.remove(input, -2)); - } - - /** - * Tests for {@link LazyList#remove(Object, int)} - */ - @Test - void testRemoveObjectInt_NonListInput() { - String input = "a"; - - // Invalid index - Object list = LazyList.remove(input, 1); - assertNotNull(list); - if (STRICT) { - assertTrue(list instanceof List); - } - assertEquals(1, LazyList.size(list)); - - // Valid index - list = LazyList.remove(input, 0); - // should this be null? or an empty list? - assertNull(list); // nothing left in list - assertEquals(0, LazyList.size(list)); - } - - /** - * Tests for {@link LazyList#remove(Object, int)} - */ - @Test - void testRemoveObjectInt_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - Object list = null; - - if (STRICT) { - // Invalid index - // Shouldn't cause a IndexOutOfBoundsException as this is not the - // same behavior you experience in testRemoveObjectInt_NonListInput - // and - // testRemoveObjectInt_NullInput when using invalid indexes. - list = LazyList.remove(input, 5); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - } - - // Valid index - list = LazyList.remove(input, 1); // remove the 'b' - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("c", LazyList.get(list, 1)); - } - - /** - * Tests for {@link LazyList#remove(Object, int)} - */ - @Test - void testRemoveObjectInt_GenericListInput() { - List input = new ArrayList(); - input.add("a"); - input.add("b"); - input.add("c"); - - Object list = null; - - if (STRICT) { - // Invalid index - // Shouldn't cause a IndexOutOfBoundsException as this is not the - // same behavior you experience in testRemoveObjectInt_NonListInput - // and - // testRemoveObjectInt_NullInput when using invalid indexes. - list = LazyList.remove(input, 5); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - } - - // Valid index - list = LazyList.remove(input, 1); // remove the 'b' - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(2, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("c", LazyList.get(list, 1)); - - // Remove the rest - list = LazyList.remove(list, 0); // the 'a' - list = LazyList.remove(list, 0); // the 'c' - assertNull(list); - } - - /** - * Test for {@link LazyList#getList(Object)} - */ - @Test - void testGetListObject_NullInput() { - Object input = null; - - Object list = LazyList.getList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(0, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#getList(Object)} - */ - @Test - void testGetListObject_NonListInput() { - String input = "a"; - - Object list = LazyList.getList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - } - - /** - * Test for {@link LazyList#getList(Object)} - */ - @Test - void testGetListObject_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - Object list = LazyList.getList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Test for {@link LazyList#getList(Object)} - */ - @Test - void testGetListObject_GenericListInput() { - List input = new ArrayList(); - input.add("a"); - input.add("b"); - input.add("c"); - - Object list = LazyList.getList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Test for {@link LazyList#getList(Object)} - */ - @Test - void testGetListObject_LinkedListInput() { - List input = new LinkedList(); - input.add("a"); - input.add("b"); - input.add("c"); - - Object list = LazyList.getList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Test for {@link LazyList#getList(Object)} - */ - @Test - void testGetListObject_NullForEmpty() { - assertNull(LazyList.getList(null, true)); - assertNotNull(LazyList.getList(null, false)); - } - - /** - * Tests for {@link LazyList#toStringArray(Object)} - */ - @Test - @SuppressWarnings("unchecked") - void testToStringArray() { - assertEquals(0, LazyList.toStringArray(null).length); - - assertEquals(1, LazyList.toStringArray("a").length); - assertEquals("a", LazyList.toStringArray("a")[0]); - - @SuppressWarnings("rawtypes") - ArrayList l = new ArrayList(); - l.add("a"); - l.add(null); - l.add(new Integer(2)); - String[] a = LazyList.toStringArray(l); - - assertEquals(3, a.length); - assertEquals("a", a[0]); - assertEquals(null, a[1]); - assertEquals("2", a[2]); - - } - - /** - * Tests for {@link LazyList#toArray(Object, Class)} - */ - @Test - void testToArray_NullInput_Object() { - Object input = null; - - Object arr = LazyList.toArray(input, Object.class); - assertNotNull(arr); - assertTrue(arr.getClass().isArray()); - } - - /** - * Tests for {@link LazyList#toArray(Object, Class)} - */ - @Test - void testToArray_NullInput_String() { - String input = null; - - Object arr = LazyList.toArray(input, String.class); - assertNotNull(arr); - assertTrue(arr.getClass().isArray()); - assertTrue(arr instanceof String[]); - } - - /** - * Tests for {@link LazyList#toArray(Object, Class)} - */ - @Test - void testToArray_NonListInput() { - String input = "a"; - - Object arr = LazyList.toArray(input, String.class); - assertNotNull(arr); - assertTrue(arr.getClass().isArray()); - assertTrue(arr instanceof String[]); - - String[] strs = (String[]) arr; - assertEquals(1, strs.length); - assertEquals("a", strs[0]); - } - - /** - * Tests for {@link LazyList#toArray(Object, Class)} - */ - @Test - void testToArray_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - Object arr = LazyList.toArray(input, String.class); - assertNotNull(arr); - assertTrue(arr.getClass().isArray()); - assertTrue(arr instanceof String[]); - - String[] strs = (String[]) arr; - assertEquals(3, strs.length); - assertEquals("a", strs[0]); - assertEquals("b", strs[1]); - assertEquals("c", strs[2]); - } - - /** - * Tests for {@link LazyList#toArray(Object, Class)} - */ - @Test - void testToArray_LazyListInput_Primitives() { - Object input = LazyList.add(null, 22); - input = LazyList.add(input, 333); - input = LazyList.add(input, 4444); - input = LazyList.add(input, 55555); - - Object arr = LazyList.toArray(input, int.class); - assertNotNull(arr); - assertTrue(arr.getClass().isArray()); - assertTrue(arr instanceof int[]); - - int[] nums = (int[]) arr; - assertEquals(4, nums.length); - assertEquals(22, nums[0]); - assertEquals(333, nums[1]); - assertEquals(4444, nums[2]); - assertEquals(55555, nums[3]); - } - - /** - * Tests for {@link LazyList#toArray(Object, Class)} - */ - @Test - void testToArray_GenericListInput() { - List input = new ArrayList(); - input.add("a"); - input.add("b"); - input.add("c"); - - Object arr = LazyList.toArray(input, String.class); - assertNotNull(arr); - assertTrue(arr.getClass().isArray()); - assertTrue(arr instanceof String[]); - - String[] strs = (String[]) arr; - assertEquals(3, strs.length); - assertEquals("a", strs[0]); - assertEquals("b", strs[1]); - assertEquals("c", strs[2]); - } - - /** - * Tests for {@link LazyList#size(Object)} - */ - @Test - void testSize_NullInput() { - assertEquals(0, LazyList.size(null)); - } - - /** - * Tests for {@link LazyList#size(Object)} - */ - @Test - void testSize_NonListInput() { - String input = "a"; - assertEquals(1, LazyList.size(input)); - } - - /** - * Tests for {@link LazyList#size(Object)} - */ - @Test - void testSize_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - - assertEquals(2, LazyList.size(input)); - - input = LazyList.add(input, "c"); - - assertEquals(3, LazyList.size(input)); - } - - /** - * Tests for {@link LazyList#size(Object)} - */ - @Test - void testSize_GenericListInput() { - List input = new ArrayList(); - - assertEquals(0, LazyList.size(input)); - - input.add("a"); - input.add("b"); - - assertEquals(2, LazyList.size(input)); - - input.add("c"); - - assertEquals(3, LazyList.size(input)); - } - - /** - * Tests for bad input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_OutOfBounds_NullInput() { - assertThrows(IndexOutOfBoundsException.class, () -> { - LazyList.get(null, 0); // Should Fail due to null input - }); - } - - /** - * Tests for bad input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_OutOfBounds_NonListInput() { - assertThrows(IndexOutOfBoundsException.class, () -> { - String input = "a"; - LazyList.get(input, 1); // Should Fail - }); - } - - /** - * Tests for bad input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_OutOfBounds_LazyListInput() { - assertThrows(IndexOutOfBoundsException.class, () -> { - Object input = LazyList.add(null, "a"); - LazyList.get(input, 1); // Should Fail - }); - } - - /** - * Tests for bad input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_OutOfBounds_GenericListInput() { - assertThrows(IndexOutOfBoundsException.class, () -> { - List input = new ArrayList<>(); - input.add("a"); - LazyList.get(input, 1); // Should Fail - }); - } - - /** - * Tests for non-list input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_NonListInput() { - String input = "a"; - assertEquals("a", LazyList.get(input, 0)); - } - - /** - * Tests for list input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_LazyListInput() { - Object input = LazyList.add(null, "a"); - assertEquals("a", LazyList.get(input, 0)); - } - - /** - * Tests for list input on {@link LazyList#get(Object, int)} - */ - @Test - void testGet_GenericListInput() { - List input = new ArrayList<>(); - input.add("a"); - assertEquals("a", LazyList.get(input, 0)); - - List uris = new ArrayList<>(); - uris.add(URI.create("http://www.abc.org/")); - uris.add(URI.create("http://www.cde.ort/firefly/")); - uris.add(URI.create("http://www.fgh.com/firefly/")); - uris.add(URI.create("http://www.fireflysource.com/")); - - // Make sure that Generics pass through the 'get' routine safely. - // We should be able to call this without casting the result to URI - URI eclipseUri = LazyList.get(uris, 3); - assertEquals("http://www.fireflysource.com/", eclipseUri.toASCIIString()); - } - - /** - * Tests for {@link LazyList#contains(Object, Object)} - */ - @Test - void testContains_NullInput() { - assertFalse(LazyList.contains(null, "z")); - } - - /** - * Tests for {@link LazyList#contains(Object, Object)} - */ - @Test - void testContains_NonListInput() { - String input = "a"; - assertFalse(LazyList.contains(input, "z")); - assertTrue(LazyList.contains(input, "a")); - } - - /** - * Tests for {@link LazyList#contains(Object, Object)} - */ - @Test - void testContains_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - assertFalse(LazyList.contains(input, "z")); - assertTrue(LazyList.contains(input, "a")); - assertTrue(LazyList.contains(input, "b")); - } - - /** - * Tests for {@link LazyList#contains(Object, Object)} - */ - @Test - void testContains_GenericListInput() { - List input = new ArrayList<>(); - input.add("a"); - input.add("b"); - input.add("c"); - - assertFalse(LazyList.contains(input, "z")); - assertTrue(LazyList.contains(input, "a")); - assertTrue(LazyList.contains(input, "b")); - } - - /** - * Tests for {@link LazyList#clone(Object)} - */ - @Test - void testClone_NullInput() { - Object input = null; - - Object list = LazyList.clone(input); - assertNull(list); - } - - /** - * Tests for {@link LazyList#clone(Object)} - */ - @Test - void testClone_NonListInput() { - String input = "a"; - - Object list = LazyList.clone(input); - assertNotNull(list); - assertSame(input, list); - } - - /** - * Tests for {@link LazyList#clone(Object)} - */ - @Test - void testClone_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - Object list = LazyList.clone(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertNotSame(input, list); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#clone(Object)} - */ - @Test - void testClone_GenericListInput() { - List input = new ArrayList<>(); - input.add("a"); - input.add("b"); - input.add("c"); - - // decorate the .clone(Object) method to return - // the same generic object element type - Object list = LazyList.clone(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertNotSame(input, list); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link LazyList#toString(Object)} - */ - @Test - void testToString_NullInput() { - Object input = null; - assertEquals("[]", LazyList.toString(input)); - } - - /** - * Tests for {@link LazyList#toString(Object)} - */ - @Test - void testToString_NonListInput() { - String input = "a"; - assertEquals("[a]", LazyList.toString(input)); - } - - /** - * Tests for {@link LazyList#toString(Object)} - */ - @Test - void testToString_LazyListInput() { - Object input = LazyList.add(null, "a"); - - assertEquals("[a]", LazyList.toString(input)); - - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - assertEquals("[a, b, c]", LazyList.toString(input)); - } - - /** - * Tests for {@link LazyList#toString(Object)} - */ - @Test - void testToString_GenericListInput() { - List input = new ArrayList(); - input.add("a"); - - assertEquals("[a]", LazyList.toString(input)); - - input.add("b"); - input.add("c"); - - assertEquals("[a, b, c]", LazyList.toString(input)); - } - - /** - * Tests for {@link LazyList#iterator(Object)} - */ - @Test - void testIterator_NullInput() { - Iterator iter = LazyList.iterator(null); - assertNotNull(iter); - assertFalse(iter.hasNext()); - } - - /** - * Tests for {@link LazyList#iterator(Object)} - */ - @Test - void testIterator_NonListInput() { - String input = "a"; - - Iterator iter = LazyList.iterator(input); - assertNotNull(iter); - assertTrue(iter.hasNext()); - assertEquals("a", iter.next()); - assertFalse(iter.hasNext()); - } - - /** - * Tests for {@link LazyList#iterator(Object)} - */ - @Test - void testIterator_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - Iterator iter = LazyList.iterator(input); - assertNotNull(iter); - assertTrue(iter.hasNext()); - assertEquals("a", iter.next()); - assertEquals("b", iter.next()); - assertEquals("c", iter.next()); - assertFalse(iter.hasNext()); - } - - /** - * Tests for {@link LazyList#iterator(Object)} - */ - @Test - void testIterator_GenericListInput() { - List input = new ArrayList(); - input.add("a"); - input.add("b"); - input.add("c"); - - Iterator iter = LazyList.iterator(input); - assertNotNull(iter); - assertTrue(iter.hasNext()); - assertEquals("a", iter.next()); - assertEquals("b", iter.next()); - assertEquals("c", iter.next()); - assertFalse(iter.hasNext()); - } - - /** - * Tests for {@link LazyList#listIterator(Object)} - */ - @Test - void testListIterator_NullInput() { - ListIterator iter = LazyList.listIterator(null); - assertNotNull(iter); - assertFalse(iter.hasNext()); - assertFalse(iter.hasPrevious()); - } - - /** - * Tests for {@link LazyList#listIterator(Object)} - */ - @Test - void testListIterator_NonListInput() { - String input = "a"; - - ListIterator iter = LazyList.listIterator(input); - assertNotNull(iter); - assertTrue(iter.hasNext()); - assertFalse(iter.hasPrevious()); - assertEquals("a", iter.next()); - assertFalse(iter.hasNext()); - assertTrue(iter.hasPrevious()); - } - - /** - * Tests for {@link LazyList#listIterator(Object)} - */ - @Test - void testListIterator_LazyListInput() { - Object input = LazyList.add(null, "a"); - input = LazyList.add(input, "b"); - input = LazyList.add(input, "c"); - - ListIterator iter = LazyList.listIterator(input); - assertNotNull(iter); - assertTrue(iter.hasNext()); - assertFalse(iter.hasPrevious()); - assertEquals("a", iter.next()); - assertEquals("b", iter.next()); - assertEquals("c", iter.next()); - assertFalse(iter.hasNext()); - assertTrue(iter.hasPrevious()); - assertEquals("c", iter.previous()); - assertEquals("b", iter.previous()); - assertEquals("a", iter.previous()); - assertFalse(iter.hasPrevious()); - } - - /** - * Tests for {@link LazyList#listIterator(Object)} - */ - @Test - void testListIterator_GenericListInput() { - List input = new ArrayList(); - input.add("a"); - input.add("b"); - input.add("c"); - - ListIterator iter = LazyList.listIterator(input); - assertNotNull(iter); - assertTrue(iter.hasNext()); - assertFalse(iter.hasPrevious()); - assertEquals("a", iter.next()); - assertEquals("b", iter.next()); - assertEquals("c", iter.next()); - assertFalse(iter.hasNext()); - assertTrue(iter.hasPrevious()); - assertEquals("c", iter.previous()); - assertEquals("b", iter.previous()); - assertEquals("a", iter.previous()); - assertFalse(iter.hasPrevious()); - } - - /** - * Tests for {@link ArrayUtils#asMutableList(Object[])} - */ - @Test - void testArray2List_NullInput() { - Object[] input = null; - Object list = ArrayUtils.asMutableList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(0, LazyList.size(list)); - } - - /** - * Tests for {@link ArrayUtils#asMutableList(Object[])} - */ - @Test - void testArray2List_EmptyInput() { - String[] input = new String[0]; - - Object list = ArrayUtils.asMutableList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(0, LazyList.size(list)); - } - - /** - * Tests for {@link ArrayUtils#asMutableList(Object[])} - */ - @Test - void testArray2List_SingleInput() { - String[] input = new String[]{"a"}; - - Object list = ArrayUtils.asMutableList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(1, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - } - - /** - * Tests for {@link ArrayUtils#asMutableList(Object[])} - */ - @Test - void testArray2List_MultiInput() { - String[] input = new String[]{"a", "b", "c"}; - - Object list = ArrayUtils.asMutableList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link ArrayUtils#asMutableList(Object[])} - */ - @Test - void testArray2List_GenericsInput() { - String[] input = new String[]{"a", "b", "c"}; - - // Test the Generics definitions for array2List - List list = ArrayUtils.asMutableList(input); - assertNotNull(list); - assertTrue(list instanceof List); - assertEquals(3, LazyList.size(list)); - assertEquals("a", LazyList.get(list, 0)); - assertEquals("b", LazyList.get(list, 1)); - assertEquals("c", LazyList.get(list, 2)); - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_NullInput_NullItem() { - Object[] input = null; - - Object[] arr = ArrayUtils.addToArray(input, null, Object.class); - assertNotNull(arr); - if (STRICT) { - // Adding null item to array should result in nothing added? - assertEquals(0, arr.length); - } else { - assertEquals(1, arr.length); - } - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_NullNullNull() { - // NPE if item && type are both null. - assumeTrue(STRICT); - - // Harsh test case. - Object[] input = null; - - Object[] arr = ArrayUtils.addToArray(input, null, null); - assertNotNull(arr); - if (STRICT) { - // Adding null item to array should result in nothing added? - assertEquals(0, arr.length); - } else { - assertEquals(1, arr.length); - } - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_NullInput_SimpleItem() { - Object[] input = null; - - Object[] arr = ArrayUtils.addToArray(input, "a", String.class); - assertNotNull(arr); - assertEquals(1, arr.length); - assertEquals("a", arr[0]); - - // Same test, but with an undefined type - arr = ArrayUtils.addToArray(input, "b", null); - assertNotNull(arr); - assertEquals(1, arr.length); - assertEquals("b", arr[0]); - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_EmptyInput_NullItem() { - String[] input = new String[0]; - - String[] arr = ArrayUtils.addToArray(input, null, Object.class); - assertNotNull(arr); - if (STRICT) { - // Adding null item to array should result in nothing added? - assertEquals(0, arr.length); - } else { - assertEquals(1, arr.length); - } - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_EmptyInput_SimpleItem() { - String[] input = new String[0]; - - String[] arr = ArrayUtils.addToArray(input, "a", String.class); - assertNotNull(arr); - assertEquals(1, arr.length); - assertEquals("a", arr[0]); - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_SingleInput_NullItem() { - String[] input = new String[]{"z"}; - - String[] arr = ArrayUtils.addToArray(input, null, Object.class); - assertNotNull(arr); - if (STRICT) { - // Should a null item be added to an array? - assertEquals(1, arr.length); - } else { - assertEquals(2, arr.length); - assertEquals("z", arr[0]); - assertEquals(null, arr[1]); - } - } - - /** - * Tests for {@link ArrayUtils#addToArray(Object[], Object, Class)} - */ - @Test - void testAddToArray_SingleInput_SimpleItem() { - String[] input = new String[]{"z"}; - - String[] arr = ArrayUtils.addToArray(input, "a", String.class); - assertNotNull(arr); - assertEquals(2, arr.length); - assertEquals("z", arr[0]); - assertEquals("a", arr[1]); - } - - /** - * Tests for {@link ArrayUtils#removeFromArray(Object[], Object)} - */ - @Test - void testRemoveFromArray_NullInput_NullItem() { - Object[] input = null; - - Object[] arr = ArrayUtils.removeFromArray(input, null); - assertNull(arr); - } - - /** - * Tests for {@link ArrayUtils#removeFromArray(Object[], Object)} - */ - @Test - void testRemoveFromArray_NullInput_SimpleItem() { - Object[] input = null; - - Object[] arr = ArrayUtils.removeFromArray(input, "a"); - assertNull(arr); - } - - /** - * Tests for {@link ArrayUtils#removeFromArray(Object[], Object)} - */ - @Test - void testRemoveFromArray_EmptyInput_NullItem() { - String[] input = new String[0]; - - String[] arr = ArrayUtils.removeFromArray(input, null); - assertNotNull(arr); - assertEquals(0, arr.length); - } - - /** - * Tests for {@link ArrayUtils#removeFromArray(Object[], Object)} - */ - @Test - void testRemoveFromArray_EmptyInput_SimpleItem() { - String[] input = new String[0]; - - String[] arr = ArrayUtils.removeFromArray(input, "a"); - assertNotNull(arr); - assertEquals(0, arr.length); - } - - /** - * Tests for {@link ArrayUtils#removeFromArray(Object[], Object)} - */ - @Test - void testRemoveFromArray_SingleInput() { - String[] input = new String[]{"a"}; - - String[] arr = ArrayUtils.removeFromArray(input, null); - assertNotNull(arr); - assertEquals(1, arr.length); - assertEquals("a", arr[0]); - - // Remove actual item - arr = ArrayUtils.removeFromArray(input, "a"); - assertNotNull(arr); - assertEquals(0, arr.length); - } - - /** - * Tests for {@link ArrayUtils#removeFromArray(Object[], Object)} - */ - @Test - void testRemoveFromArray_MultiInput() { - String[] input = new String[]{"a", "b", "c"}; - - String[] arr = ArrayUtils.removeFromArray(input, null); - assertNotNull(arr); - assertEquals(3, arr.length); - assertEquals("a", arr[0]); - assertEquals("b", arr[1]); - assertEquals("c", arr[2]); - - // Remove an actual item - arr = ArrayUtils.removeFromArray(input, "b"); - assertNotNull(arr); - assertEquals(2, arr.length); - assertEquals("a", arr[0]); - assertEquals("c", arr[1]); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/collection/map/MultiMapTest.java b/firefly-common/src/test/java/com/fireflysource/common/collection/map/MultiMapTest.java deleted file mode 100644 index be81b0a34..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/collection/map/MultiMapTest.java +++ /dev/null @@ -1,467 +0,0 @@ -package com.fireflysource.common.collection.map; - - -import com.fireflysource.common.collection.list.LazyList; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -class MultiMapTest { - - @Test - void testPut() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - } - - @Test - void testPutNullString() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - String val = null; - - mm.put(key, val); - assertMapSize(mm, 1); - assertNullValues(mm, key); - } - - @Test - void testPutNullList() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - List vals = null; - - mm.put(key, vals); - assertMapSize(mm, 1); - assertNullValues(mm, key); - } - - @Test - void testPutReplace() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - Object ret; - - ret = mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - assertNull(ret); - Object orig = mm.get(key); - - // Now replace it - ret = mm.put(key, "jar"); - assertMapSize(mm, 1); - assertValues(mm, key, "jar"); - assertEquals(orig, ret); - } - - @Test - void testPutValuesList() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - List input = new ArrayList<>(); - input.add("gzip"); - input.add("jar"); - input.add("pack200"); - - mm.putValues(key, input); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - } - - @Test - void testPutValuesStringArray() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - String[] input = {"gzip", "jar", "pack200"}; - mm.putValues(key, input); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - } - - @Test - void testPutValuesVarArgs() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - mm.putValues(key, "gzip", "jar", "pack200"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - } - - @Test - void testAdd() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - - // Add to the key - mm.add(key, "jar"); - mm.add(key, "pack200"); - - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - } - - @Test - void testAddValuesList() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - - // Add to the key - List extras = new ArrayList<>(); - extras.add("jar"); - extras.add("pack200"); - extras.add("zip"); - mm.addValues(key, extras); - - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200", "zip"); - } - - @Test - void testAddValuesListEmpty() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - - // Add to the key - List extras = new ArrayList<>(); - mm.addValues(key, extras); - - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - } - - @Test - void testAddValuesStringArray() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - - // Add to the key - String[] extras = {"jar", "pack200", "zip"}; - mm.addValues(key, extras); - - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200", "zip"); - } - - @Test - void testAddValuesStringArrayEmpty() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.put(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - - // Add to the key - String[] extras = new String[0]; - mm.addValues(key, extras); - - assertMapSize(mm, 1); - assertValues(mm, key, "gzip"); - } - - @Test - void testRemoveValue() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.putValues(key, "gzip", "jar", "pack200"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - - // Remove a value - mm.removeValue(key, "jar"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "pack200"); - - } - - @Test - void testRemoveValueInvalidItem() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.putValues(key, "gzip", "jar", "pack200"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - - // Remove a value that isn't there - mm.removeValue(key, "msi"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - } - - @Test - void testRemoveValueAllItems() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.putValues(key, "gzip", "jar", "pack200"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "jar", "pack200"); - - // Remove a value - mm.removeValue(key, "jar"); - assertMapSize(mm, 1); - assertValues(mm, key, "gzip", "pack200"); - - // Remove another value - mm.removeValue(key, "gzip"); - assertMapSize(mm, 1); - assertValues(mm, key, "pack200"); - - // Remove last value - mm.removeValue(key, "pack200"); - assertMapSize(mm, 0); // should be empty now - } - - @Test - void testRemoveValueFromEmpty() { - MultiMap mm = new MultiMap<>(); - - String key = "formats"; - - // Setup the key - mm.putValues(key, new String[0]); - assertMapSize(mm, 1); - assertEmptyValues(mm, key); - - // Remove a value that isn't in the underlying values - mm.removeValue(key, "jar"); - assertMapSize(mm, 1); - assertEmptyValues(mm, key); - } - - @Test - void testPutAllMap() { - MultiMap mm = new MultiMap<>(); - - assertMapSize(mm, 0); // Shouldn't have anything yet. - - Map input = new HashMap<>(); - input.put("food", "apple"); - input.put("color", "red"); - input.put("amount", "bushel"); - - mm.putAllValues(input); - - assertMapSize(mm, 3); - assertValues(mm, "food", "apple"); - assertValues(mm, "color", "red"); - assertValues(mm, "amount", "bushel"); - } - - @Test - void testPutAllMultiMapSimple() { - MultiMap mm = new MultiMap<>(); - - assertMapSize(mm, 0); // Shouldn't have anything yet. - - MultiMap input = new MultiMap<>(); - input.put("food", "apple"); - input.put("color", "red"); - input.put("amount", "bushel"); - - mm.putAll(input); - - assertMapSize(mm, 3); - assertValues(mm, "food", "apple"); - assertValues(mm, "color", "red"); - assertValues(mm, "amount", "bushel"); - } - - @Test - void testPutAllMultiMapComplex() { - MultiMap mm = new MultiMap<>(); - - assertMapSize(mm, 0); // Shouldn't have anything yet. - - MultiMap input = new MultiMap<>(); - input.putValues("food", "apple", "cherry", "raspberry"); - input.put("color", "red"); - input.putValues("amount", "bushel", "pint"); - - mm.putAll(input); - - assertMapSize(mm, 3); - assertValues(mm, "food", "apple", "cherry", "raspberry"); - assertValues(mm, "color", "red"); - assertValues(mm, "amount", "bushel", "pint"); - } - - @Test - void testToStringArrayMap() { - MultiMap mm = new MultiMap<>(); - mm.putValues("food", "apple", "cherry", "raspberry"); - mm.put("color", "red"); - mm.putValues("amount", "bushel", "pint"); - - assertMapSize(mm, 3); - - Map sam = mm.toStringArrayMap(); - assertEquals(3, sam.size()); - - assertArray("toStringArrayMap(food)", sam.get("food"), "apple", "cherry", "raspberry"); - assertArray("toStringArrayMap(color)", sam.get("color"), "red"); - assertArray("toStringArrayMap(amount)", sam.get("amount"), "bushel", "pint"); - } - - @Test - void testToString() { - MultiMap mm = new MultiMap<>(); - mm.put("color", "red"); - assertEquals("{color=red}", mm.toString()); - - mm.putValues("food", "apple", "cherry", "raspberry"); - assertEquals("{color=red, food=[apple, cherry, raspberry]}", mm.toString()); - } - @Test - void testClear() { - MultiMap mm = new MultiMap<>(); - mm.putValues("food", "apple", "cherry", "raspberry"); - mm.put("color", "red"); - mm.putValues("amount", "bushel", "pint"); - - assertMapSize(mm, 3); - - mm.clear(); - - assertMapSize(mm, 0); - } - - @Test - void testContainsKey() { - MultiMap mm = new MultiMap<>(); - mm.putValues("food", "apple", "cherry", "raspberry"); - mm.put("color", "red"); - mm.putValues("amount", "bushel", "pint"); - - assertTrue(mm.containsKey("color")); - assertFalse(mm.containsKey("nutrition")); - } - - @Test - void testContainsSimpleValue() { - MultiMap mm = new MultiMap<>(); - mm.putValues("food", "apple", "cherry", "raspberry"); - mm.put("color", "red"); - mm.putValues("amount", "bushel", "pint"); - - assertTrue(mm.containsSimpleValue("red")); - assertFalse(mm.containsValue("nutrition")); - } - - @Test - void testContainsValue() { - MultiMap mm = new MultiMap<>(); - mm.putValues("food", "apple", "cherry", "raspberry"); - mm.put("color", "red"); - mm.putValues("amount", "bushel", "pint"); - - List acr = new ArrayList<>(); - acr.add("apple"); - acr.add("cherry"); - acr.add("raspberry"); - assertTrue(mm.containsValue(acr)); - assertFalse(mm.containsValue("nutrition")); - } - - @Test - void testContainsValue_LazyList() { - MultiMap mm = new MultiMap<>(); - mm.putValues("food", "apple", "cherry", "raspberry"); - mm.put("color", "red"); - mm.putValues("amount", "bushel", "pint"); - - Object list = LazyList.add(null, "bushel"); - list = LazyList.add(list, "pint"); - - assertTrue(mm.containsValue(list)); - } - - private void assertArray(String prefix, Object[] actualValues, Object... expectedValues) { - assertEquals(expectedValues.length, actualValues.length); - int len = actualValues.length; - for (int i = 0; i < len; i++) { - assertEquals(expectedValues[i], actualValues[i]); - } - } - - private void assertValues(MultiMap mm, String key, Object... expectedValues) { - List values = mm.getValues(key); - assertEquals(expectedValues.length, values.size()); - int len = expectedValues.length; - for (int i = 0; i < len; i++) { - if (expectedValues[i] == null) { - assertNull(values.get(i)); - } else { - assertEquals(expectedValues[i], values.get(i)); - } - } - } - - private void assertNullValues(MultiMap mm, String key) { - List values = mm.getValues(key); - assertNull(values); - } - - private void assertEmptyValues(MultiMap mm, String key) { - List values = mm.getValues(key); - assertEquals(0, LazyList.size(values)); - } - - private void assertMapSize(MultiMap mm, int expectedSize) { - assertEquals(expectedSize, mm.size()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/collection/trie/TestTrie.java b/firefly-common/src/test/java/com/fireflysource/common/collection/trie/TestTrie.java deleted file mode 100644 index ff80f693a..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/collection/trie/TestTrie.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.common.collection.trie; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class TestTrie { - - @ParameterizedTest - @ValueSource(strings = {"TreeTrie", "ArrayTernaryTrie", "ArrayTrie"}) - void test(String type) { - Trie trie; - switch (type) { - case "TreeTrie": - trie = new TreeTrie<>(); - break; - case "ArrayTernaryTrie": - trie = new ArrayTernaryTrie<>(500); - break; - case "ArrayTrie": - trie = new ArrayTrie<>(500); - break; - default: - trie = new TreeTrie<>(); - } - - trie.put("com.firefly.foo.bar"); - trie.put("com.firefly.foo"); - - assertEquals(2, trie.keySet().size()); - assertEquals("com.firefly.foo", trie.getBest("com.firefly.foo.Test")); - assertEquals("com.firefly.foo.bar", trie.getBest("com.firefly.foo.bar.Hello")); - } - -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/AtomicBiIntegerTest.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/AtomicBiIntegerTest.java deleted file mode 100644 index 572173f1a..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/AtomicBiIntegerTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class AtomicBiIntegerTest { - - @Test - void testBitOperations() { - long encoded; - - encoded = AtomicBiInteger.encode(0, 0); - assertEquals(0, AtomicBiInteger.getHi(encoded)); - assertEquals(0, AtomicBiInteger.getLo(encoded)); - - encoded = AtomicBiInteger.encode(1, 2); - assertEquals(1, AtomicBiInteger.getHi(encoded)); - assertEquals(2, AtomicBiInteger.getLo(encoded)); - - encoded = AtomicBiInteger.encode(Integer.MAX_VALUE, -1); - assertEquals(Integer.MAX_VALUE, AtomicBiInteger.getHi(encoded)); - assertEquals(-1, AtomicBiInteger.getLo(encoded)); - encoded = AtomicBiInteger.encodeLo(encoded, 42); - assertEquals(Integer.MAX_VALUE, AtomicBiInteger.getHi(encoded)); - assertEquals(42, AtomicBiInteger.getLo(encoded)); - - encoded = AtomicBiInteger.encode(-1, Integer.MAX_VALUE); - assertEquals(-1, AtomicBiInteger.getHi(encoded)); - assertEquals(Integer.MAX_VALUE, AtomicBiInteger.getLo(encoded)); - encoded = AtomicBiInteger.encodeHi(encoded, 42); - assertEquals(42, AtomicBiInteger.getHi(encoded)); - assertEquals(Integer.MAX_VALUE, AtomicBiInteger.getLo(encoded)); - - encoded = AtomicBiInteger.encode(Integer.MIN_VALUE, 1); - assertEquals(Integer.MIN_VALUE, AtomicBiInteger.getHi(encoded)); - assertEquals(1, AtomicBiInteger.getLo(encoded)); - encoded = AtomicBiInteger.encodeLo(encoded, Integer.MAX_VALUE); - assertEquals(Integer.MIN_VALUE, AtomicBiInteger.getHi(encoded)); - assertEquals(Integer.MAX_VALUE, AtomicBiInteger.getLo(encoded)); - - encoded = AtomicBiInteger.encode(1, Integer.MIN_VALUE); - assertEquals(1, AtomicBiInteger.getHi(encoded)); - assertEquals(Integer.MIN_VALUE, AtomicBiInteger.getLo(encoded)); - encoded = AtomicBiInteger.encodeHi(encoded, Integer.MAX_VALUE); - assertEquals(Integer.MAX_VALUE, AtomicBiInteger.getHi(encoded)); - assertEquals(Integer.MIN_VALUE, AtomicBiInteger.getLo(encoded)); - } - - @Test - void testSet() { - AtomicBiInteger abi = new AtomicBiInteger(); - assertEquals(0, abi.getHi()); - assertEquals(0, abi.getLo()); - - abi.getAndSetHi(Integer.MAX_VALUE); - assertEquals(Integer.MAX_VALUE, abi.getHi()); - assertEquals(0, abi.getLo()); - - abi.getAndSetLo(Integer.MIN_VALUE); - assertEquals(Integer.MAX_VALUE, abi.getHi()); - assertEquals(Integer.MIN_VALUE, abi.getLo()); - } - - @Test - void testCompareAndSet() { - AtomicBiInteger abi = new AtomicBiInteger(); - assertEquals(0, abi.getHi()); - assertEquals(0, abi.getLo()); - - assertFalse(abi.compareAndSetHi(1, 42)); - assertTrue(abi.compareAndSetHi(0, 42)); - assertEquals(42, abi.getHi()); - assertEquals(0, abi.getLo()); - - assertFalse(abi.compareAndSetLo(1, -42)); - assertTrue(abi.compareAndSetLo(0, -42)); - assertEquals(42, abi.getHi()); - assertEquals(-42, abi.getLo()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/AutoLockTest.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/AutoLockTest.java deleted file mode 100644 index 4a18a29e5..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/AutoLockTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class AutoLockTest { - - @Test - public void testLocked() { - AutoLock lock = new AutoLock(); - assertFalse(lock.isLocked()); - - try (AutoLock ignored = lock.lock()) { - assertTrue(lock.isLocked()); - } finally { - assertFalse(lock.isLocked()); - } - - assertFalse(lock.isLocked()); - } - - @Test - public void testLockedException() { - AutoLock lock = new AutoLock(); - assertFalse(lock.isLocked()); - - try (AutoLock ignored = lock.lock()) { - assertTrue(lock.isLocked()); - throw new Exception(); - } catch (Exception e) { - assertFalse(lock.isLocked()); - } finally { - assertFalse(lock.isLocked()); - } - - assertFalse(lock.isLocked()); - } - - @Test - public void testContend() throws Exception { - AutoLock lock = new AutoLock(); - - final CountDownLatch held0 = new CountDownLatch(1); - final CountDownLatch hold0 = new CountDownLatch(1); - - Thread thread0 = new Thread(() -> - { - try (AutoLock ignored = lock.lock()) { - held0.countDown(); - hold0.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - thread0.start(); - held0.await(); - - assertTrue(lock.isLocked()); - - final CountDownLatch held1 = new CountDownLatch(1); - final CountDownLatch hold1 = new CountDownLatch(1); - Thread thread1 = new Thread(() -> - { - try (AutoLock ignored = lock.lock()) { - held1.countDown(); - hold1.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - thread1.start(); - // thread1 will be spinning here - assertFalse(held1.await(100, TimeUnit.MILLISECONDS)); - - // Let thread0 complete - hold0.countDown(); - thread0.join(); - - // thread1 can progress - held1.await(); - - // let thread1 complete - hold1.countDown(); - thread1.join(); - - assertFalse(lock.isLocked()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/IteratingCallbackTest.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/IteratingCallbackTest.java deleted file mode 100644 index a11743c75..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/IteratingCallbackTest.java +++ /dev/null @@ -1,250 +0,0 @@ -package com.fireflysource.common.concurrent; - -import com.fireflysource.common.sys.Result; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.*; - -public class IteratingCallbackTest { - private ScheduledExecutorService scheduler; - - @BeforeEach - public void prepare() { - scheduler = Executors.newScheduledThreadPool(4); - } - - @AfterEach - public void dispose() { - ExecutorServiceUtils.shutdownAndAwaitTermination(scheduler, 1, TimeUnit.SECONDS); - } - - @Test - public void testNonWaitingProcess() throws Exception { - TestCB cb = new TestCB() { - int i = 10; - - @Override - protected Action process() { - processed++; - if (i-- > 1) { - accept(Result.SUCCESS); // fake a completed IO operation - return Action.SCHEDULED; - } - return Action.SUCCEEDED; - } - }; - - cb.iterate(); - assertTrue(cb.waitForComplete()); - assertEquals(10, cb.processed); - } - - @Test - public void testWaitingProcess() throws Exception { - TestCB cb = new TestCB() { - int i = 4; - - @Override - protected Action process() { - processed++; - if (i-- > 1) { - scheduler.schedule(successTask, 50, TimeUnit.MILLISECONDS); - return Action.SCHEDULED; - } - return Action.SUCCEEDED; - } - }; - - cb.iterate(); - - assertTrue(cb.waitForComplete()); - - assertEquals(4, cb.processed); - } - - @Test - public void testWaitingProcessSpuriousIterate() throws Exception { - final TestCB cb = new TestCB() { - int i = 4; - - @Override - protected Action process() { - processed++; - if (i-- > 1) { - scheduler.schedule(successTask, 50, TimeUnit.MILLISECONDS); - return Action.SCHEDULED; - } - return Action.SUCCEEDED; - } - }; - - cb.iterate(); - scheduler.schedule(new Runnable() { - @Override - public void run() { - cb.iterate(); - if (!cb.isSucceeded()) - scheduler.schedule(this, 50, TimeUnit.MILLISECONDS); - } - }, 49, TimeUnit.MILLISECONDS); - - assertTrue(cb.waitForComplete()); - - assertEquals(4, cb.processed); - } - - @Test - public void testNonWaitingProcessFailure() throws Exception { - TestCB cb = new TestCB() { - int i = 10; - - @Override - protected Action process() { - processed++; - if (i-- > 1) { - if (i > 5) - accept(Result.SUCCESS); // fake a completed IO operation - else - accept(Result.createFailedResult(new Exception("testing"))); - return Action.SCHEDULED; - } - return Action.SUCCEEDED; - } - }; - - cb.iterate(); - assertFalse(cb.waitForComplete()); - assertEquals(5, cb.processed); - } - - @Test - public void testWaitingProcessFailure() throws Exception { - TestCB cb = new TestCB() { - int i = 4; - - @Override - protected Action process() { - processed++; - if (i-- > 1) { - scheduler.schedule(i > 2 ? successTask : failTask, 50, TimeUnit.MILLISECONDS); - return Action.SCHEDULED; - } - return Action.SUCCEEDED; - } - }; - - cb.iterate(); - - assertFalse(cb.waitForComplete()); - assertEquals(2, cb.processed); - } - - @Test - public void testIdleWaiting() throws Exception { - final CountDownLatch idle = new CountDownLatch(1); - - TestCB cb = new TestCB() { - int i = 5; - - @Override - protected Action process() { - processed++; - - switch (i--) { - case 5: - accept(Result.SUCCESS); - return Action.SCHEDULED; - - case 4: - scheduler.schedule(successTask, 5, TimeUnit.MILLISECONDS); - return Action.SCHEDULED; - - case 3: - scheduler.schedule(idle::countDown, 5, TimeUnit.MILLISECONDS); - return Action.IDLE; - - case 2: - accept(Result.SUCCESS); - return Action.SCHEDULED; - - case 1: - scheduler.schedule(successTask, 5, TimeUnit.MILLISECONDS); - return Action.SCHEDULED; - - case 0: - return Action.SUCCEEDED; - - default: - throw new IllegalStateException(); - } - } - }; - - cb.iterate(); - idle.await(10, TimeUnit.SECONDS); - assertTrue(cb.isIdle()); - - cb.iterate(); - assertTrue(cb.waitForComplete()); - assertEquals(6, cb.processed); - } - - @Test - public void testCloseDuringProcessingReturningScheduled() throws Exception { - testCloseDuringProcessing(IteratingCallback.Action.SCHEDULED); - } - - @Test - public void testCloseDuringProcessingReturningSucceeded() throws Exception { - testCloseDuringProcessing(IteratingCallback.Action.SUCCEEDED); - } - - private void testCloseDuringProcessing(final IteratingCallback.Action action) throws Exception { - final CountDownLatch failureLatch = new CountDownLatch(1); - IteratingCallback callback = new IteratingCallback() { - @Override - protected Action process() throws Exception { - close(); - return action; - } - - @Override - protected void onCompleteFailure(Throwable cause) { - failureLatch.countDown(); - } - }; - - callback.iterate(); - - assertTrue(failureLatch.await(5, TimeUnit.SECONDS)); - } - - private abstract static class TestCB extends IteratingCallback { - protected Runnable successTask = () -> accept(Result.SUCCESS); - protected Runnable failTask = () -> accept(Result.createFailedResult(new Exception("testing failure"))); - protected CountDownLatch completed = new CountDownLatch(1); - protected int processed = 0; - - @Override - protected void onCompleteSuccess() { - completed.countDown(); - } - - @Override - public void onCompleteFailure(Throwable x) { - completed.countDown(); - } - - boolean waitForComplete() throws InterruptedException { - completed.await(10, TimeUnit.SECONDS); - return isSucceeded(); - } - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestAtomics.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestAtomics.java deleted file mode 100644 index 7cb0f1583..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestAtomics.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -/** - * @author Pengtao Qiu - */ -class TestAtomics { - - @Test - void testGetAndDecrement() { - int init = 10; - int min = 5; - AtomicInteger integer = new AtomicInteger(init); - - for (int i = init; i > 0; i--) { - Atomics.getAndDecrement(integer, min); - } - assertEquals(min, integer.get()); - } - - @Test - void testGetAndIncrement() { - int max = 10; - AtomicInteger integer = new AtomicInteger(0); - for (int i = 0; i < 20; i++) { - Atomics.getAndIncrement(integer, max); - } - assertEquals(max, integer.get()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestCompletableFutures.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestCompletableFutures.java deleted file mode 100644 index b8009a767..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestCompletableFutures.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @author Pengtao Qiu - */ -public class TestCompletableFutures { - - @Test - @DisplayName("should retry operation successfully") - void testRetrySuccessfully() throws Exception { - AtomicInteger i = new AtomicInteger(4); - CompletableFuture future = CompletableFutures.retry(3, () -> { - System.out.println("execute count: " + i.get()); - if (i.decrementAndGet() > 0) { - return CompletableFutures.failedFuture(new IllegalStateException("error")); - } else { - return CompletableFuture.completedFuture("ok"); - } - }, (e, c) -> System.out.println("start to retry: " + c)); - String str = future.get(); - assertEquals("ok", str); - assertEquals(0, i.get()); - } - - @Test - @DisplayName("should retry operation failure") - void testRetryFailure() { - AtomicInteger i = new AtomicInteger(4); - CompletableFuture future = CompletableFutures.retry(2, () -> { - System.out.println("execute count: " + i.get()); - if (i.decrementAndGet() > 0) { - return CompletableFutures.failedFuture(new IllegalStateException("error")); - } else { - return CompletableFuture.completedFuture("ok"); - } - }, (e, c) -> System.out.println("start to retry: " + c)); - - assertThrows(ExecutionException.class, future::get); - } - - @Test - @DisplayName("should do finally always") - void testDoFinally() throws Exception { - AtomicReference ref = new AtomicReference<>(); - CompletableFuture future = CompletableFuture.supplyAsync(() -> "OK"); - CompletableFuture result = CompletableFutures.doFinally(future, (value, ex) -> CompletableFuture.runAsync(() -> { - String msg = "do finally. " + value; - System.out.println(msg); - ref.set(msg); - })); - assertEquals("OK", result.get()); - assertEquals("do finally. OK", ref.get()); - - CompletableFuture failure = CompletableFuture.supplyAsync(() -> { - throw new IllegalStateException("Failure"); - }); - CompletableFuture failureResult = CompletableFutures.doFinally(failure, (value, ex) -> CompletableFuture.runAsync(() -> { - String msg = "do finally. " + ex.getMessage(); - System.out.println(msg); - ref.set(msg); - })); - assertThrows(ExecutionException.class, failureResult::get); - assertEquals("do finally. java.lang.IllegalStateException: Failure", ref.get()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestExecutorServiceUtils.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestExecutorServiceUtils.java deleted file mode 100644 index 020212f23..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestExecutorServiceUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.fireflysource.common.concurrent.ExecutorServiceUtils.shutdownAndAwaitTermination; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author Pengtao Qiu - */ -class TestExecutorServiceUtils { - - @Test - void testShutdownAndAwaitTermination() { - int threadNum = 2; - int count = 10; - long taskTime = 500L; - long maxTime = taskTime * count / threadNum; - - ExecutorService pool = Executors.newFixedThreadPool(threadNum); - AtomicInteger maxTask = new AtomicInteger(count); - for (int i = 0; i < count; i++) { - pool.submit(() -> { - try { - Thread.sleep(taskTime); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println("task complete. " + maxTask.getAndDecrement()); - }); - } - shutdownAndAwaitTermination(pool, maxTime + 100, TimeUnit.MILLISECONDS); - assertEquals(0, maxTask.get()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestSingleThreadExecutorService.java b/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestSingleThreadExecutorService.java deleted file mode 100644 index 0e25e5223..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/concurrent/TestSingleThreadExecutorService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.common.concurrent; - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class TestSingleThreadExecutorService { - - @Test - void test() { - ExecutorService executorService = new SingleThreadExecutorService(1024 * 16); - executorService.execute(() -> { - try { - System.out.println("start to execute."); - Thread.sleep(1000L); - System.out.println("execute success."); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - ExecutorServiceUtils.shutdownAndAwaitTermination(executorService, 3, TimeUnit.SECONDS); - assertTrue(executorService.isShutdown()); - assertTrue(executorService.isTerminated()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/io/TestBufferUtils.java b/firefly-common/src/test/java/com/fireflysource/common/io/TestBufferUtils.java deleted file mode 100644 index 771ea64c5..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/io/TestBufferUtils.java +++ /dev/null @@ -1,422 +0,0 @@ -package com.fireflysource.common.io; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * @author Pengtao Qiu - */ -class TestBufferUtils { - - @Test - void testToArray() { - ByteBuffer buffer = ByteBuffer.allocate(4); - buffer.putInt(6); - buffer.flip(); - - byte[] bytes = BufferUtils.toArray(buffer); - assertNotSame(bytes, buffer.array()); - assertEquals(6, ByteBuffer.wrap(bytes).getInt()); - - buffer = ByteBuffer.allocateDirect(4); - buffer.putInt(3); - buffer.flip(); - bytes = BufferUtils.toArray(buffer); - assertEquals(3, ByteBuffer.wrap(bytes).getInt()); - } - - @Test - void testCollectionToArray() { - int count = 10; - List list = new LinkedList<>(); - for (int i = 0; i < count; i++) { - ByteBuffer buffer = ByteBuffer.allocate(4); - buffer.putInt(i); - buffer.flip(); - list.add(buffer); - } - - byte[] bytes = BufferUtils.toArray(list); - assertEquals(count * 4, bytes.length); - - ByteBuffer buffer = ByteBuffer.wrap(bytes); - for (int i = 0; i < count; i++) { - int x = buffer.getInt(); - assertEquals(i, x); - } - } - - @Test - void testToInt() { - ByteBuffer[] buf = { - BufferUtils.toBuffer("0"), - BufferUtils.toBuffer(" 42 "), - BufferUtils.toBuffer(" 43abc"), - BufferUtils.toBuffer("-44"), - BufferUtils.toBuffer(" - 45;"), - BufferUtils.toBuffer("-2147483648"), - BufferUtils.toBuffer("2147483647"), - }; - - int[] val = { - 0, 42, 43, -44, -45, -2147483648, 2147483647 - }; - - for (int i = 0; i < buf.length; i++) - assertEquals(val[i], BufferUtils.toInt(buf[i]), "t" + i); - } - - @Test - void testPutInt() { - int[] val = { - 0, 42, 43, -44, -45, Integer.MIN_VALUE, Integer.MAX_VALUE - }; - - String[] str = { - "0", "42", "43", "-44", "-45", "" + Integer.MIN_VALUE, "" + Integer.MAX_VALUE - }; - - ByteBuffer buffer = ByteBuffer.allocate(24); - - for (int i = 0; i < val.length; i++) { - BufferUtils.clearToFill(buffer); - BufferUtils.putDecInt(buffer, val[i]); - BufferUtils.flipToFlush(buffer, 0); - assertEquals(str[i], BufferUtils.toString(buffer), "t" + i); - } - } - - @Test - void testPutLong() { - long[] val = { - 0L, 42L, 43L, -44L, -45L, Long.MIN_VALUE, Long.MAX_VALUE - }; - - String[] str = { - "0", "42", "43", "-44", "-45", "" + Long.MIN_VALUE, "" + Long.MAX_VALUE - }; - - ByteBuffer buffer = ByteBuffer.allocate(50); - - for (int i = 0; i < val.length; i++) { - BufferUtils.clearToFill(buffer); - BufferUtils.putDecLong(buffer, val[i]); - BufferUtils.flipToFlush(buffer, 0); - assertEquals(str[i], BufferUtils.toString(buffer), "t" + i); - } - } - - @Test - void testPutHexInt() { - int[] val = { - 0, 42, 43, -44, -45, -2147483648, 2147483647 - }; - - String[] str = { - "0", "2A", "2B", "-2C", "-2D", "-80000000", "7FFFFFFF" - }; - - ByteBuffer buffer = ByteBuffer.allocate(50); - - for (int i = 0; i < val.length; i++) { - BufferUtils.clearToFill(buffer); - BufferUtils.putHexInt(buffer, val[i]); - BufferUtils.flipToFlush(buffer, 0); - assertEquals(str[i], BufferUtils.toString(buffer), "t" + i); - } - } - - @Test - void testPut() { - ByteBuffer to = BufferUtils.allocate(10); - ByteBuffer from = BufferUtils.toBuffer("12345"); - - BufferUtils.clear(to); - assertEquals(5, BufferUtils.append(to, from)); - assertTrue(BufferUtils.isEmpty(from)); - assertEquals("12345", BufferUtils.toString(to)); - - from = BufferUtils.toBuffer("XX67890ZZ"); - from.position(2); - - assertEquals(5, BufferUtils.append(to, from)); - assertEquals(2, from.remaining()); - assertEquals("1234567890", BufferUtils.toString(to)); - - from = BufferUtils.toBuffer("1234"); - to = BufferUtils.allocate(from.remaining() * 2); - assertEquals(0, to.position()); - assertEquals(0, to.limit()); - assertEquals(0, from.position()); - assertEquals(4, from.limit()); - - BufferUtils.append(to, from); - assertEquals(0, to.position()); - assertEquals(4, to.limit()); - assertEquals(4, from.position()); - assertEquals(4, from.limit()); - - to.get(); - from = BufferUtils.toBuffer("1234"); - BufferUtils.append(to, from); - assertEquals(1, to.position()); - assertEquals(8, to.limit()); - } - - @Test - void testPutBuffer() { - ByteBuffer from = BufferUtils.toBuffer("hello"); - ByteBuffer to = BufferUtils.allocate(20); - int pos = BufferUtils.flipToFill(to); - System.out.println("from: " + from.remaining()); - System.out.println("to: " + to.remaining()); - assertEquals(5, from.remaining()); - assertEquals(20, to.remaining()); - - int len = BufferUtils.put(from, to); - System.out.println("len: " + len); - System.out.println("from: " + from.remaining()); - System.out.println("to: " + to.remaining()); - assertEquals(0, from.remaining()); - assertEquals(15, to.remaining()); - assertEquals(5, len); - - BufferUtils.flipToFlush(to, pos); - String str = BufferUtils.toString(to); - assertEquals("hello", str); - } - - - @Test - void testAppend() { - ByteBuffer to = BufferUtils.allocate(8); - ByteBuffer from = BufferUtils.toBuffer("12345"); - - BufferUtils.append(to, from.array(), 0, 3); - assertEquals("123", BufferUtils.toString(to)); - BufferUtils.append(to, from.array(), 3, 2); - assertEquals("12345", BufferUtils.toString(to)); - - assertThrows(BufferOverflowException.class, () -> BufferUtils.append(to, from.array(), 0, 5)); - } - - - @Test - void testPutDirect() { - ByteBuffer to = BufferUtils.allocateDirect(10); - ByteBuffer from = BufferUtils.toBuffer("12345"); - - BufferUtils.clear(to); - assertEquals(5, BufferUtils.append(to, from)); - assertTrue(BufferUtils.isEmpty(from)); - assertEquals("12345", BufferUtils.toString(to)); - - from = BufferUtils.toBuffer("XX67890ZZ"); - from.position(2); - - assertEquals(5, BufferUtils.append(to, from)); - assertEquals(2, from.remaining()); - assertEquals("1234567890", BufferUtils.toString(to)); - } - - @Test - void testToBuffer_Array() { - byte[] arr = new byte[128]; - Arrays.fill(arr, (byte) 0x44); - ByteBuffer buf = BufferUtils.toBuffer(arr); - - int count = 0; - while (buf.remaining() > 0) { - byte b = buf.get(); - assertEquals(b, 0x44); - count++; - } - - assertEquals(arr.length, count, "Count of bytes"); - } - - @Test - void testToBuffer_ArrayOffsetLength() { - byte[] arr = new byte[128]; - Arrays.fill(arr, (byte) 0xFF); // fill whole thing with FF - int offset = 10; - int length = 100; - Arrays.fill(arr, offset, offset + length, (byte) 0x77); // fill partial with 0x77 - ByteBuffer buf = BufferUtils.toBuffer(arr, offset, length); - - int count = 0; - while (buf.remaining() > 0) { - byte b = buf.get(); - assertEquals(b, 0x77); - count++; - } - - assertEquals(length, count, "Count of bytes"); - } - - @Test - void testWriteToWithBufferThatDoesNotExposeArrayAndSmallContent() throws IOException { - int capacity = BufferUtils.TEMP_BUFFER_SIZE / 4; - testWriteToWithBufferThatDoesNotExposeArray(capacity); - } - - @Test - void testWriteToWithBufferThatDoesNotExposeArrayAndContentLengthMatchingTempBufferSize() throws IOException { - int capacity = BufferUtils.TEMP_BUFFER_SIZE; - testWriteToWithBufferThatDoesNotExposeArray(capacity); - } - - @Test - void testWriteToWithBufferThatDoesNotExposeArrayAndContentSlightlyBiggerThanTwoTimesTempBufferSize() - throws - IOException { - int capacity = BufferUtils.TEMP_BUFFER_SIZE * 2 + 1024; - testWriteToWithBufferThatDoesNotExposeArray(capacity); - } - - - @Test - void testEnsureCapacity() { - ByteBuffer b = BufferUtils.toBuffer("Goodbye Cruel World"); - assertSame(b, BufferUtils.ensureCapacity(b, 0)); - assertSame(b, BufferUtils.ensureCapacity(b, 10)); - assertSame(b, BufferUtils.ensureCapacity(b, b.capacity())); - - - ByteBuffer b1 = BufferUtils.ensureCapacity(b, 64); - assertNotSame(b, b1); - assertEquals(64, b1.capacity()); - assertEquals("Goodbye Cruel World", BufferUtils.toString(b1)); - - b1.position(8); - b1.limit(13); - assertEquals("Cruel", BufferUtils.toString(b1)); - ByteBuffer b2 = b1.slice(); - assertEquals("Cruel", BufferUtils.toString(b2)); - System.err.println(BufferUtils.toDetailString(b2)); - assertEquals(8, b2.arrayOffset()); - assertEquals(5, b2.capacity()); - - assertSame(b2, BufferUtils.ensureCapacity(b2, 5)); - - ByteBuffer b3 = BufferUtils.ensureCapacity(b2, 64); - assertNotSame(b2, b3); - assertEquals(64, b3.capacity()); - assertEquals("Cruel", BufferUtils.toString(b3)); - assertEquals(0, b3.arrayOffset()); - - assertEquals(19, b.remaining()); - b.position(19); - ByteBuffer b4 = BufferUtils.ensureCapacity(b, b.position() + 20); - BufferUtils.flipToFill(b4); - b4.position(b.position()); - - assertEquals(20, b4.remaining()); - assertEquals(19, b4.position()); - assertEquals(39, b4.capacity()); - - BufferUtils.flipToFill(b); - ByteBuffer b5 = BufferUtils.allocateDirect(19); - assertEquals(0, b5.remaining()); - assertEquals(19, b.remaining()); - - BufferUtils.append(b5, b); - assertEquals(0, b.remaining()); - assertEquals(19, b5.remaining()); - BufferUtils.flipToFill(b); - assertEquals(19, b.remaining()); - - ByteBuffer b6 = BufferUtils.ensureCapacity(b5, 20); - assertEquals(19, b6.remaining()); - BufferUtils.flipToFill(b6); - assertEquals(1, b6.remaining()); - } - - @Test - void testAddCapacity() { - Arrays.asList(BufferUtils.allocateDirect(19), BufferUtils.allocate(19)).forEach(b -> { - BufferUtils.flipToFill(b); - b.put(BufferUtils.toBuffer("Goodbye Cruel World")); - - assertEquals(19, b.position()); - assertEquals(0, b.remaining()); - - ByteBuffer b1 = BufferUtils.addCapacity(b, 20); - assertEquals(20, b1.remaining()); - assertEquals(19, b1.position()); - assertEquals(39, b1.capacity()); - assertEquals(39, b1.limit()); - BufferUtils.flipToFlush(b1, 0); - assertEquals("Goodbye Cruel World", BufferUtils.toString(b1).trim()); - }); - - } - - @Test - void testToDetail_WithDEL() { - ByteBuffer b = ByteBuffer.allocate(40); - b.putChar('a').putChar('b').putChar('c'); - b.put((byte) 0x7F); - b.putChar('x').putChar('y').putChar('z'); - b.flip(); - String result = BufferUtils.toDetailString(b); - assertTrue(result.contains("\\x7f")); - } - - @Test - @DisplayName("should merge buffers successfully") - void testMergeBuffers() { - List buffers = new LinkedList<>(); - for (int i = 0; i < 10; i++) { - ByteBuffer buf = BufferUtils.allocate(16); - int pos = BufferUtils.flipToFill(buf); - for (int j = 0; j < 4; j++) { - int e = i * 4 + j; - buf.putInt(e); - } - BufferUtils.flipToFlush(buf, pos); - buffers.add(buf); - } - - ByteBuffer newBuffer = BufferUtils.merge(buffers); - buffers.forEach(buf -> assertEquals(0, buf.remaining())); - assertEquals(160, newBuffer.remaining()); - for (int i = 0; i < 40; i++) { - assertEquals(i, newBuffer.getInt()); - } - } - - @Test - @DisplayName("should convert buffers to string successfully") - void testBuffersToString() { - List buffers = Arrays.asList( - BufferUtils.toBuffer("hello ", StandardCharsets.UTF_8), - BufferUtils.toBuffer("测试 ", StandardCharsets.UTF_8), - BufferUtils.toBuffer("ok ", StandardCharsets.UTF_8)); - String str = BufferUtils.toString(buffers, StandardCharsets.UTF_8); - assertEquals("hello 测试 ok ", str); - } - - - private void testWriteToWithBufferThatDoesNotExposeArray(int capacity) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] bytes = new byte[capacity]; - ThreadLocalRandom.current().nextBytes(bytes); - ByteBuffer buffer = BufferUtils.allocate(capacity); - BufferUtils.append(buffer, bytes, 0, capacity); - BufferUtils.writeTo(buffer.asReadOnlyBuffer(), out); - assertArrayEquals(bytes, out.toByteArray()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/object/TestTypeUtils.java b/firefly-common/src/test/java/com/fireflysource/common/object/TestTypeUtils.java deleted file mode 100644 index 7961914b8..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/object/TestTypeUtils.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.fireflysource.common.object; - - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class TestTypeUtils { - @Test - void convertHexDigitTest() { - assertEquals((byte) 0, TypeUtils.convertHexDigit((byte) '0')); - assertEquals((byte) 9, TypeUtils.convertHexDigit((byte) '9')); - assertEquals((byte) 10, TypeUtils.convertHexDigit((byte) 'a')); - assertEquals((byte) 10, TypeUtils.convertHexDigit((byte) 'A')); - assertEquals((byte) 15, TypeUtils.convertHexDigit((byte) 'f')); - assertEquals((byte) 15, TypeUtils.convertHexDigit((byte) 'F')); - - assertEquals(0, TypeUtils.convertHexDigit((int) '0')); - assertEquals(9, TypeUtils.convertHexDigit((int) '9')); - assertEquals(10, TypeUtils.convertHexDigit((int) 'a')); - assertEquals(10, TypeUtils.convertHexDigit((int) 'A')); - assertEquals(15, TypeUtils.convertHexDigit((int) 'f')); - assertEquals(15, TypeUtils.convertHexDigit((int) 'F')); - } - - @Test - void testToHexInt() throws Exception { - StringBuilder b = new StringBuilder(); - - b.setLength(0); - TypeUtils.toHex(0, b); - assertEquals("00000000", b.toString()); - - b.setLength(0); - TypeUtils.toHex(Integer.MAX_VALUE, b); - assertEquals("7FFFFFFF", b.toString()); - - b.setLength(0); - TypeUtils.toHex(Integer.MIN_VALUE, b); - assertEquals("80000000", b.toString()); - - b.setLength(0); - TypeUtils.toHex(0x12345678, b); - assertEquals("12345678", b.toString()); - - b.setLength(0); - TypeUtils.toHex(0x9abcdef0, b); - assertEquals("9ABCDEF0", b.toString()); - } - - @Test - void testToHexLong() throws Exception { - StringBuilder b = new StringBuilder(); - - b.setLength(0); - TypeUtils.toHex((long) 0, b); - assertEquals("0000000000000000", b.toString()); - - b.setLength(0); - TypeUtils.toHex(Long.MAX_VALUE, b); - assertEquals("7FFFFFFFFFFFFFFF", b.toString()); - - b.setLength(0); - TypeUtils.toHex(Long.MIN_VALUE, b); - assertEquals("8000000000000000", b.toString()); - - b.setLength(0); - TypeUtils.toHex(0x123456789abcdef0L, b); - assertEquals("123456789ABCDEF0", b.toString()); - } - - @Test - void testIsTrue() { - assertTrue(TypeUtils.isTrue(Boolean.TRUE)); - assertTrue(TypeUtils.isTrue(true)); - assertTrue(TypeUtils.isTrue("true")); - assertTrue(TypeUtils.isTrue(new Object() { - @Override - public String toString() { - return "true"; - } - })); - - assertFalse(TypeUtils.isTrue(Boolean.FALSE)); - assertFalse(TypeUtils.isTrue(false)); - assertFalse(TypeUtils.isTrue("false")); - assertFalse(TypeUtils.isTrue("blargle")); - assertFalse(TypeUtils.isTrue(new Object() { - @Override - public String toString() { - return "false"; - } - })); - } - - @Test - void testIsFalse() { - assertTrue(TypeUtils.isFalse(Boolean.FALSE)); - assertTrue(TypeUtils.isFalse(false)); - assertTrue(TypeUtils.isFalse("false")); - assertTrue(TypeUtils.isFalse(new Object() { - @Override - public String toString() { - return "false"; - } - })); - - assertFalse(TypeUtils.isFalse(Boolean.TRUE)); - assertFalse(TypeUtils.isFalse(true)); - assertFalse(TypeUtils.isFalse("true")); - assertFalse(TypeUtils.isFalse("blargle")); - assertFalse(TypeUtils.isFalse(new Object() { - @Override - public String toString() { - return "true"; - } - })); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/ref/TestCleaner.java b/firefly-common/src/test/java/com/fireflysource/common/ref/TestCleaner.java deleted file mode 100644 index 933bbfb62..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/ref/TestCleaner.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fireflysource.common.ref; - -import com.fireflysource.common.lifecycle.ShutdownTasks; -import org.junit.jupiter.api.Test; - -public class TestCleaner { - - private Cleaner cleaner = Cleaner.create(); - - public static class AutoCloseableResource { - private final AutoCloseable closeable; - - public AutoCloseableResource(Cleaner cleaner, AutoCloseable closeable) { - this.closeable = closeable; - cleaner.register(this, () -> { - try { - closeable.close(); - } catch (Exception e) { - e.printStackTrace(); - } - }); - } - } - - public static class Foo implements AutoCloseable { - - private final String text; - - public Foo(String text) { - this.text = text; - } - - public String getText() { - return text; - } - - @Override - public void close() throws Exception { - System.out.println("Clean resource: " + text); - } - } - - @Test - void test() throws InterruptedException { - ShutdownTasks.register(() -> System.out.println("exit process")); - - Foo foo = new Foo("Foo fu"); - AutoCloseableResource resource = new AutoCloseableResource(cleaner, foo); - System.out.println(foo.getText()); - resource = null; - - System.gc(); - Thread.sleep(3000); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/reflection/TestReflectUtils.java b/firefly-common/src/test/java/com/fireflysource/common/reflection/TestReflectUtils.java deleted file mode 100644 index 624440e19..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/reflection/TestReflectUtils.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.fireflysource.common.reflection; - - -import org.junit.jupiter.api.Test; - -import static com.fireflysource.common.reflection.ReflectionUtils.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class TestReflectUtils { - - @Test - void testGetterAndSetterMethod() { - assertEquals("getName", getGetterMethod(Foo.class, "name").getName()); - assertEquals("isFailure", getGetterMethod(Foo.class, "failure").getName()); - - assertEquals("setName", getSetterMethod(Foo.class, "name").getName()); - assertEquals("setFailure", getSetterMethod(Foo.class, "failure").getName()); - - assertEquals("setiPad", getSetterMethod(Foo.class, "iPad").getName()); - assertEquals("setiPhone", getSetterMethod(Foo.class, "iPhone").getName()); - - assertEquals("isiPad", getGetterMethod(Foo.class, "iPad").getName()); - assertEquals("getiPhone", getGetterMethod(Foo.class, "iPhone").getName()); - } - - @Test - void testGetAndSet() throws Throwable { - Foo foo = new Foo(); - set(foo, "price", 4.44); - set(foo, "failure", true); - set(foo, "name", "foo hello"); - - assertEquals(4.44, get(foo, "price")); - assertTrue((Boolean) get(foo, "failure")); - assertEquals("foo hello", get(foo, "name")); - } - - @Test - void testCopy() { - Foo foo = new Foo(); - foo.setName("hello foo"); - foo.setPrice(3.3); - foo.setNumber(40); - - Foo2 foo2 = new Foo2(); - foo2.setName("hello foo2"); - - copy(foo2, foo); - assertEquals("hello foo2", foo.getName()); - assertEquals(40, foo.getNumber()); - } - - public static class Foo2 { - private String name; - private Integer number; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getNumber() { - return number; - } - - public void setNumber(Integer number) { - this.number = number; - } - - } - - public static class Foo { - public String name; - public int num2; - public String info; - private boolean failure; - private int number; - private double price; - private String iPhone; - private boolean iPad; - - public String getiPhone() { - return iPhone; - } - - public void setiPhone(String iPhone) { - this.iPhone = iPhone; - } - - public boolean isiPad() { - return iPad; - } - - public void setiPad(boolean iPad) { - this.iPad = iPad; - } - - public int getNumber() { - return number; - } - - public void setNumber(int number) { - this.number = number; - } - - public double getPrice() { - return price; - } - - public void setPrice(double price) { - this.price = price; - } - - public boolean isFailure() { - return failure; - } - - public void setFailure(boolean failure) { - this.failure = failure; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public void setProperty(String name, boolean failure) { - this.name = name; - this.failure = failure; - } - - } - -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/string/SearchPatternTest.java b/firefly-common/src/test/java/com/fireflysource/common/string/SearchPatternTest.java deleted file mode 100644 index faf424e60..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/string/SearchPatternTest.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.fireflysource.common.string; - -import com.fireflysource.common.io.BufferUtils; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.ThreadLocalRandom; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class SearchPatternTest { - - @Test - public void testBasicSearch() { - byte[] p1 = "truth".getBytes(StandardCharsets.US_ASCII); - byte[] p2 = "evident".getBytes(StandardCharsets.US_ASCII); - byte[] p3 = "we".getBytes(StandardCharsets.US_ASCII); - byte[] d = "we hold these truths to be self evident".getBytes(StandardCharsets.US_ASCII); - - // Testing Compiled Pattern p1 "truth" - SearchPattern sp1 = SearchPattern.compile(p1); - assertEquals(14, sp1.match(d, 0, d.length)); - assertEquals(14, sp1.match(d, 14, p1.length)); - assertEquals(14, sp1.match(d, 14, p1.length + 1)); - assertEquals(-1, sp1.match(d, 14, p1.length - 1)); - assertEquals(-1, sp1.match(d, 15, d.length - 15)); - - // Testing Compiled Pattern p2 "evident" - SearchPattern sp2 = SearchPattern.compile(p2); - assertEquals(32, sp2.match(d, 0, d.length)); - assertEquals(32, sp2.match(d, 32, p2.length)); - assertEquals(32, sp2.match(d, 32, p2.length)); - assertEquals(-1, sp2.match(d, 32, p2.length - 1)); - assertEquals(-1, sp2.match(d, 33, d.length - 33)); - - // Testing Compiled Pattern p3 "evident" - SearchPattern sp3 = SearchPattern.compile(p3); - assertEquals(0, sp3.match(d, 0, d.length)); - assertEquals(0, sp3.match(d, 0, p3.length)); - assertEquals(0, sp3.match(d, 0, p3.length + 1)); - assertEquals(-1, sp3.match(d, 0, p3.length - 1)); - assertEquals(-1, sp3.match(d, 1, d.length - 1)); - } - - @Test - public void testDoubleMatch() { - byte[] p = "violent".getBytes(StandardCharsets.US_ASCII); - byte[] d = "These violent delights have violent ends.".getBytes(StandardCharsets.US_ASCII); - SearchPattern sp = SearchPattern.compile(p); - assertEquals(6, sp.match(d, 0, d.length)); - assertEquals(-1, sp.match(d, 6, p.length - 1)); - assertEquals(28, sp.match(d, 7, d.length - 7)); - assertEquals(28, sp.match(d, 28, d.length - 28)); - assertEquals(-1, sp.match(d, 29, d.length - 29)); - } - - @Test - public void testSearchInBinary() { - byte[] random = new byte[8192]; - ThreadLocalRandom.current().nextBytes(random); - // Arrays.fill(random,(byte)-67); - String preamble = "Blah blah blah"; - String epilogue = "The End! Blah Blah Blah"; - - ByteBuffer data = BufferUtils.allocate(preamble.length() + random.length + epilogue.length()); - BufferUtils.append(data, BufferUtils.toBuffer(preamble)); - BufferUtils.append(data, ByteBuffer.wrap(random)); - BufferUtils.append(data, BufferUtils.toBuffer(epilogue)); - - SearchPattern sp = SearchPattern.compile("The End!"); - - assertEquals(preamble.length() + random.length, sp.match(data.array(), data.arrayOffset() + data.position(), data.remaining())); - } - - @Test - public void testSearchBinaryKey() { - byte[] random = new byte[8192]; - ThreadLocalRandom.current().nextBytes(random); - byte[] key = new byte[64]; - ThreadLocalRandom.current().nextBytes(key); - - ByteBuffer data = BufferUtils.allocate(random.length + key.length); - BufferUtils.append(data, ByteBuffer.wrap(random)); - BufferUtils.append(data, ByteBuffer.wrap(key)); - SearchPattern sp = SearchPattern.compile(key); - - assertEquals(random.length, sp.match(data.array(), data.arrayOffset() + data.position(), data.remaining())); - } - - @Test - public void testAlmostMatch() { - byte[] p = "violent".getBytes(StandardCharsets.US_ASCII); - byte[] d = "vio lent violen v iolent violin vioviolenlent viiolent".getBytes(StandardCharsets.US_ASCII); - SearchPattern sp = SearchPattern.compile(p); - assertEquals(-1, sp.match(d, 0, d.length)); - } - - @Test - public void testOddSizedPatterns() { - // Test Large Pattern - byte[] p = "pneumonoultramicroscopicsilicovolcanoconiosis".getBytes(StandardCharsets.US_ASCII); - byte[] d = "pneumon".getBytes(StandardCharsets.US_ASCII); - SearchPattern sp = SearchPattern.compile(p); - assertEquals(-1, sp.match(d, 0, d.length)); - - // Test Single Character Pattern - p = "s".getBytes(StandardCharsets.US_ASCII); - d = "the cake is a lie".getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(10, sp.match(d, 0, d.length)); - } - - @Test - public void testEndsWith() { - byte[] p = "pneumonoultramicroscopicsilicovolcanoconiosis".getBytes(StandardCharsets.US_ASCII); - byte[] d = "pneumonoultrami".getBytes(StandardCharsets.US_ASCII); - SearchPattern sp = SearchPattern.compile(p); - assertEquals(15, sp.endsWith(d, 0, d.length)); - - p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - d = "abcdefghijklmnopqrstuvwxyzabcdefghijklmno".getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(0, sp.match(d, 0, d.length)); - assertEquals(-1, sp.match(d, 1, d.length - 1)); - assertEquals(15, sp.endsWith(d, 0, d.length)); - - p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - d = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(0, sp.match(d, 0, d.length)); - assertEquals(26, sp.match(d, 1, d.length - 1)); - assertEquals(26, sp.endsWith(d, 0, d.length)); - - //test no match - p = "hello world".getBytes(StandardCharsets.US_ASCII); - d = "there is definitely no match in here".getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(0, sp.endsWith(d, 0, d.length)); - } - - @Test - public void testStartsWithNoOffset() { - testStartsWith(""); - } - - @Test - public void testStartsWithOffset() { - testStartsWith("abcdef"); - } - - private void testStartsWith(String offset) { - byte[] p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - byte[] d = (offset + "ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - SearchPattern sp = SearchPattern.compile(p); - assertEquals(18 + offset.length(), sp.match(d, offset.length(), d.length - offset.length())); - assertEquals(-1, sp.match(d, offset.length() + 19, d.length - 19 - offset.length())); - assertEquals(26, sp.startsWith(d, offset.length(), d.length - offset.length(), 8)); - - p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - d = (offset + "ijklmnopqrstuvwxyNOMATCH").getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(0, sp.startsWith(d, offset.length(), d.length - offset.length(), 8)); - - p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - d = (offset + "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(26, sp.startsWith(d, offset.length(), d.length - offset.length(), 0)); - - //test no match - p = "hello world".getBytes(StandardCharsets.US_ASCII); - d = (offset + "there is definitely no match in here").getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(0, sp.startsWith(d, offset.length(), d.length - offset.length(), 0)); - - //test large pattern small buffer - p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - d = (offset + "mnopqrs").getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(19, sp.startsWith(d, offset.length(), d.length - offset.length(), 12)); - - //partial pattern - p = "abcdef".getBytes(StandardCharsets.US_ASCII); - d = (offset + "cde").getBytes(StandardCharsets.US_ASCII); - sp = SearchPattern.compile(p); - assertEquals(5, sp.startsWith(d, offset.length(), d.length - offset.length(), 2)); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/string/TestPattern.java b/firefly-common/src/test/java/com/fireflysource/common/string/TestPattern.java deleted file mode 100644 index 3cc6aa3af..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/string/TestPattern.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.fireflysource.common.string; - - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class TestPattern { - - @Test - @DisplayName("should match pattern successfully.") - void testPattern() { - Pattern p = Pattern.compile("?ddaaad?", "?"); - assertEquals("", p.match("ddaaad")[1]); - assertEquals("xwww", p.match("ddaaadxwww")[1]); - assertEquals("xwww", p.match("addaaadxwww")[1]); - assertEquals("a", p.match("addaaadxwww")[0]); - assertEquals("a", p.match("addaaad")[0]); - assertNull(p.match("orange")); - - p = Pattern.compile("?", "?"); - assertEquals("orange", p.match("orange")[0]); - - p = Pattern.compile("??????", "?"); - assertEquals("orange", p.match("orange")[0]); - assertEquals(1, p.match("orange").length); - - p = Pattern.compile("org", "?"); - assertNull(p.match("orange")); - assertEquals(0, p.match("org").length); - - p = Pattern.compile("?org", "?"); - assertEquals("", p.match("org")[0]); - assertEquals("aass", p.match("aassorg")[0]); - assertEquals(1, p.match("ssorg").length); - - p = Pattern.compile("org?", "?"); - assertEquals("", p.match("org")[0]); - assertEquals("aaa", p.match("orgaaa")[0]); - assertEquals(1, p.match("orgaaa").length); - - p = Pattern.compile("www.?.com?", "?"); - assertEquals("fireflysource", p.match("www.fireflysource.com")[0]); - assertEquals("", p.match("www.fireflysource.com")[1]); - assertEquals("/cn/", p.match("www.fireflysource.com/cn/")[1]); - assertEquals(2, p.match("www.fireflysource.com/cn/").length); - assertNull(p.match("orange")); - - p = Pattern.compile("www.?.com/?/app", "?"); - assertNull(p.match("orange")); - assertEquals(2, p.match("www.fireflysource.com/cn/app").length); - assertEquals("fireflysource", p.match("www.fireflysource.com/cn/app")[0]); - assertEquals("cn", p.match("www.fireflysource.com/cn/app")[1]); - - p = Pattern.compile("?www.?.com/?/app", "?"); - assertNull(p.match("orange")); - assertEquals(3, p.match("www.fireflysource.com/cn/app").length); - assertEquals("", p.match("www.fireflysource.com/cn/app")[0]); - assertEquals("fireflysource", p.match("www.fireflysource.com/cn/app")[1]); - assertEquals("cn", p.match("www.fireflysource.com/cn/app")[2]); - assertEquals("http://", p.match("http://www.fireflysource.com/cn/app")[0]); - - p = Pattern.compile("?www.?.com/?/app?", "?"); - assertNull(p.match("orange")); - assertEquals(4, p.match("www.fireflysource.com/cn/app").length); - assertEquals("", p.match("www.fireflysource.com/cn/app")[0]); - assertEquals("fireflysource", p.match("www.fireflysource.com/cn/app")[1]); - assertEquals("cn", p.match("www.fireflysource.com/cn/app")[2]); - assertEquals("http://", p.match("http://www.fireflysource.com/cn/app")[0]); - assertEquals("", p.match("http://www.fireflysource.com/cn/app")[3]); - assertEquals("/1334", p.match("http://www.fireflysource.com/cn/app/1334")[3]); - - p = Pattern.compile("abc*abc", "*"); - assertEquals("", p.match("abcabcabc")[0]); - - p = Pattern.compile("aa*aa", "*"); - assertEquals("", p.match("aaaaa")[0]); - - p = Pattern.compile("*.mustache", "*"); - assertNull(p.match("IO.class")); - } - -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/string/TestQuotedStringTokenizer.java b/firefly-common/src/test/java/com/fireflysource/common/string/TestQuotedStringTokenizer.java deleted file mode 100644 index 8ca1f1590..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/string/TestQuotedStringTokenizer.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.fireflysource.common.string; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - - -class TestQuotedStringTokenizer { - /* - * Test for String nextToken() - */ - @Test - void testTokenizer0() { - QuotedStringTokenizer tok = new QuotedStringTokenizer("abc\n\"d\\\"'\"\n'p\\',y'\nz"); - checkTok(tok, false, false); - } - - /* - * Test for String nextToken() - */ - @Test - void testTokenizer1() { - QuotedStringTokenizer tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,"); - checkTok(tok, false, false); - } - - /* - * Test for String nextToken() - */ - @Test - void testTokenizer2() { - QuotedStringTokenizer tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", false); - checkTok(tok, false, false); - - tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", true); - checkTok(tok, true, false); - } - - /* - * Test for String nextToken() - */ - @Test - void testTokenizer3() { - QuotedStringTokenizer tok; - - tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", false, false); - checkTok(tok, false, false); - - tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", false, true); - checkTok(tok, false, true); - - tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", true, false); - checkTok(tok, true, false); - - tok = new QuotedStringTokenizer("abc, \"d\\\"'\",'p\\',y' z", " ,", true, true); - checkTok(tok, true, true); - } - - @Test - void testQuote() { - StringBuilder buf = new StringBuilder(); - - buf.setLength(0); - QuotedStringTokenizer.quote(buf, "abc \n efg"); - assertEquals("\"abc \\n efg\"", buf.toString()); - - buf.setLength(0); - QuotedStringTokenizer.quote(buf, "abcefg"); - assertEquals("\"abcefg\"", buf.toString()); - - buf.setLength(0); - QuotedStringTokenizer.quote(buf, "abcefg\""); - assertEquals("\"abcefg\\\"\"", buf.toString()); - - } - - /* - * Test for String nextToken() - */ - @Test - void testTokenizer4() { - QuotedStringTokenizer tok = new QuotedStringTokenizer("abc'def,ghi'jkl", ","); - tok.setSingle(false); - assertEquals("abc'def", tok.nextToken()); - assertEquals("ghi'jkl", tok.nextToken()); - tok = new QuotedStringTokenizer("abc'def,ghi'jkl", ","); - tok.setSingle(true); - assertEquals("abcdef,ghijkl", tok.nextToken()); - } - - private void checkTok(QuotedStringTokenizer tok, boolean delim, boolean quotes) { - assertTrue(tok.hasMoreElements()); - assertTrue(tok.hasMoreTokens()); - assertEquals("abc", tok.nextToken()); - if (delim) - assertEquals(",", tok.nextToken()); - if (delim) - assertEquals(" ", tok.nextToken()); - - assertEquals(quotes ? "\"d\\\"'\"" : "d\"'", tok.nextElement()); - if (delim) - assertEquals(",", tok.nextToken()); - assertEquals(quotes ? "'p\\',y'" : "p',y", tok.nextToken()); - if (delim) - assertEquals(" ", tok.nextToken()); - assertEquals("z", tok.nextToken()); - assertFalse(tok.hasMoreTokens()); - } - - /* - * Test for String quote(String, String) - */ - @Test - void testQuoteIfNeeded() { - assertEquals("abc", QuotedStringTokenizer.quoteIfNeeded("abc", " ,")); - assertEquals("\"a c\"", QuotedStringTokenizer.quoteIfNeeded("a c", " ,")); - assertEquals("\"a'c\"", QuotedStringTokenizer.quoteIfNeeded("a'c", " ,")); - assertEquals("\"a\\n\\r\\t\"", QuotedStringTokenizer.quote("a\n\r\t")); - assertEquals("\"\\u0000\\u001f\"", QuotedStringTokenizer.quote("\u0000\u001f")); - } - - @Test - void testUnquote() { - assertEquals("abc", QuotedStringTokenizer.unquote("abc")); - assertEquals("a\"c", QuotedStringTokenizer.unquote("\"a\\\"c\"")); - assertEquals("a'c", QuotedStringTokenizer.unquote("\"a'c\"")); - assertEquals("a\n\r\t", QuotedStringTokenizer.unquote("\"a\\n\\r\\t\"")); - assertEquals("\u0000\u001f ", QuotedStringTokenizer.unquote("\"\u0000\u001f\u0020\"")); - assertEquals("\u0000\u001f ", QuotedStringTokenizer.unquote("\"\u0000\u001f\u0020\"")); - assertEquals("ab\u001ec", QuotedStringTokenizer.unquote("ab\u001ec")); - assertEquals("ab\u001ec", QuotedStringTokenizer.unquote("\"ab\u001ec\"")); - } - - @Test - void testUnquoteOnly() { - assertEquals("abc", QuotedStringTokenizer.unquoteOnly("abc")); - assertEquals("a\"c", QuotedStringTokenizer.unquoteOnly("\"a\\\"c\"")); - assertEquals("a'c", QuotedStringTokenizer.unquoteOnly("\"a'c\"")); - assertEquals("a\\n\\r\\t", QuotedStringTokenizer.unquoteOnly("\"a\\\\n\\\\r\\\\t\"")); - assertEquals("ba\\uXXXXaaa", QuotedStringTokenizer.unquoteOnly("\"ba\\\\uXXXXaaa\"")); - } - - /** - * When encountering a Content-Disposition line during a multi-part mime - * file upload, the filename="..." field can contain '\' characters that do - * not belong to a proper escaping sequence, this tests - * QuotedStringTokenizer to ensure that it preserves those slashes for where - * they cannot be escaped. - */ - @Test - void testNextTokenOnContentDisposition() { - String content_disposition = "form-data; name=\"fileup\"; filename=\"Taken on Aug 22 \\ 2012.jpg\""; - - QuotedStringTokenizer tok = new QuotedStringTokenizer(content_disposition, ";", false, true); - - assertEquals("form-data", tok.nextToken().trim()); - assertEquals("name=\"fileup\"", tok.nextToken().trim()); - assertEquals("filename=\"Taken on Aug 22 \\ 2012.jpg\"", tok.nextToken().trim()); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/string/TestStringUtils.java b/firefly-common/src/test/java/com/fireflysource/common/string/TestStringUtils.java deleted file mode 100644 index 08b943ad9..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/string/TestStringUtils.java +++ /dev/null @@ -1,348 +0,0 @@ -package com.fireflysource.common.string; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -class TestStringUtils { - - static final String WHITESPACE; - static final String NON_WHITESPACE; - static final String HARD_SPACE; - static final String TRIMMABLE; - static final String NON_TRIMMABLE; - - static { - String ws = ""; - String nws = ""; - final String hs = String.valueOf(((char) 160)); - String tr = ""; - String ntr = ""; - for (int i = 0; i < Character.MAX_VALUE; i++) { - if (Character.isWhitespace((char) i)) { - ws += String.valueOf((char) i); - if (i > 32) { - ntr += String.valueOf((char) i); - } - } else if (i < 40) { - nws += String.valueOf((char) i); - } - } - for (int i = 0; i <= 32; i++) { - tr += String.valueOf((char) i); - } - WHITESPACE = ws; - NON_WHITESPACE = nws; - HARD_SPACE = hs; - TRIMMABLE = tr; - NON_TRIMMABLE = ntr; - } - - - @Test - void testSplit() { - String byteRangeSet = "500-"; - String[] byteRangeSets = StringUtils.split(byteRangeSet, ','); - System.out.println(Arrays.toString(byteRangeSets)); - assertEquals(byteRangeSets.length, 1); - - byteRangeSet = "500-,"; - byteRangeSets = StringUtils.split(byteRangeSet, ','); - System.out.println(Arrays.toString(byteRangeSets)); - assertEquals(byteRangeSets.length, 1); - - byteRangeSet = ",500-,"; - byteRangeSets = StringUtils.split(byteRangeSet, ','); - System.out.println(Arrays.toString(byteRangeSets)); - assertEquals(byteRangeSets.length, 1); - - byteRangeSet = ",500-,"; - byteRangeSets = StringUtils.split(byteRangeSet, ","); - System.out.println(Arrays.toString(byteRangeSets)); - assertEquals(byteRangeSets.length, 1); - - byteRangeSet = ",500-"; - byteRangeSets = StringUtils.split(byteRangeSet, ','); - System.out.println(Arrays.toString(byteRangeSets)); - assertEquals(byteRangeSets.length, 1); - - byteRangeSet = "500-700,601-999,"; - byteRangeSets = StringUtils.split(byteRangeSet, ','); - assertEquals(byteRangeSets.length, 2); - - byteRangeSet = "500-700,,601-999,"; - byteRangeSets = StringUtils.split(byteRangeSet, ','); - assertEquals(byteRangeSets.length, 2); - - String tmp = "hello#$world#%test#$eee"; - String[] tmps = StringUtils.splitByWholeSeparator(tmp, "#$"); - System.out.println(Arrays.toString(tmps)); - assertEquals(tmps.length, 3); - - tmp = "hello#$"; - tmps = StringUtils.splitByWholeSeparator(tmp, "#$"); - System.out.println(Arrays.toString(tmps)); - assertEquals(tmps.length, 1); - - tmp = "#$hello#$"; - tmps = StringUtils.splitByWholeSeparator(tmp, "#$"); - System.out.println(Arrays.toString(tmps)); - assertEquals(tmps.length, 1); - - tmp = "#$hello"; - tmps = StringUtils.splitByWholeSeparator(tmp, "#$"); - System.out.println(Arrays.toString(tmps)); - assertEquals(tmps.length, 1); - - tmp = "#$hello#$world#$"; - tmps = StringUtils.splitByWholeSeparator(tmp, "#$"); - System.out.println(Arrays.toString(tmps)); - assertEquals(tmps.length, 2); - - tmp = "#$hello#$#$world#$"; - tmps = StringUtils.splitByWholeSeparator(tmp, "#$"); - System.out.println(Arrays.toString(tmps)); - assertEquals(tmps.length, 2); - - } - - @Test - void testSplit_String() { - assertNull(StringUtils.split(null)); - assertEquals(0, StringUtils.split("").length); - - String str = "a b .c"; - String[] res = StringUtils.split(str); - assertEquals(3, res.length); - assertEquals("a", res[0]); - assertEquals("b", res[1]); - assertEquals(".c", res[2]); - - str = " a "; - res = StringUtils.split(str); - assertEquals(1, res.length); - assertEquals("a", res[0]); - - str = "a" + WHITESPACE + "b" + NON_WHITESPACE + "c"; - res = StringUtils.split(str); - assertEquals(2, res.length); - assertEquals("a", res[0]); - assertEquals("b" + NON_WHITESPACE + "c", res[1]); - } - - @Test - void testSplit_StringChar() { - assertNull(StringUtils.split(null, '.')); - assertEquals(0, StringUtils.split("", '.').length); - - String str = "a.b.. c"; - String[] res = StringUtils.split(str, '.'); - assertEquals(3, res.length); - assertEquals("a", res[0]); - assertEquals("b", res[1]); - assertEquals(" c", res[2]); - - str = ".a."; - res = StringUtils.split(str, '.'); - assertEquals(1, res.length); - assertEquals("a", res[0]); - - str = "a b c"; - res = StringUtils.split(str, ' '); - assertEquals(3, res.length); - assertEquals("a", res[0]); - assertEquals("b", res[1]); - assertEquals("c", res[2]); - } - - @Test - void testSplit_StringString_StringStringInt() { - assertNull(StringUtils.split(null, ".")); - assertNull(StringUtils.split(null, ".", 3)); - - assertEquals(0, StringUtils.split("", ".").length); - assertEquals(0, StringUtils.split("", ".", 3).length); - - innerTestSplit('.', ".", ' '); - innerTestSplit('.', ".", ','); - innerTestSplit('.', ".,", 'x'); - for (int i = 0; i < WHITESPACE.length(); i++) { - for (int j = 0; j < NON_WHITESPACE.length(); j++) { - innerTestSplit(WHITESPACE.charAt(i), null, NON_WHITESPACE.charAt(j)); - innerTestSplit(WHITESPACE.charAt(i), String.valueOf(WHITESPACE.charAt(i)), NON_WHITESPACE.charAt(j)); - } - } - - String[] results; - final String[] expectedResults = {"ab", "de fg"}; - results = StringUtils.split("ab de fg", null, 2); - assertEquals(expectedResults.length, results.length); - for (int i = 0; i < expectedResults.length; i++) { - assertEquals(expectedResults[i], results[i]); - } - - final String[] expectedResults2 = {"ab", "cd:ef"}; - results = StringUtils.split("ab:cd:ef", ":", 2); - assertEquals(expectedResults2.length, results.length); - for (int i = 0; i < expectedResults2.length; i++) { - assertEquals(expectedResults2[i], results[i]); - } - } - - private void innerTestSplit(final char separator, final String sepStr, final char noMatch) { - final String msg = "Failed on separator hex(" + Integer.toHexString(separator) + - "), noMatch hex(" + Integer.toHexString(noMatch) + "), sepStr(" + sepStr + ")"; - - final String str = "a" + separator + "b" + separator + separator + noMatch + "c"; - String[] res; - // (str, sepStr) - res = StringUtils.split(str, sepStr); - assertEquals(3, res.length, msg); - assertEquals("a", res[0]); - assertEquals("b", res[1]); - assertEquals(noMatch + "c", res[2]); - - final String str2 = separator + "a" + separator; - res = StringUtils.split(str2, sepStr); - assertEquals(1, res.length, msg); - assertEquals("a", res[0], msg); - - res = StringUtils.split(str, sepStr, -1); - assertEquals(3, res.length, msg); - assertEquals("a", res[0], msg); - assertEquals("b", res[1], msg); - assertEquals(noMatch + "c", res[2], msg); - - res = StringUtils.split(str, sepStr, 0); - assertEquals(3, res.length, msg); - assertEquals("a", res[0], msg); - assertEquals("b", res[1], msg); - assertEquals(noMatch + "c", res[2], msg); - - res = StringUtils.split(str, sepStr, 1); - assertEquals(1, res.length, msg); - assertEquals(str, res[0], msg); - - res = StringUtils.split(str, sepStr, 2); - assertEquals(2, res.length, msg); - assertEquals("a", res[0], msg); - assertEquals(str.substring(2), res[1], msg); - } - - @Test - void testSplitByWholeString_StringStringBoolean() { - assertArrayEquals(null, StringUtils.splitByWholeSeparator(null, ".")); - - assertEquals(0, StringUtils.splitByWholeSeparator("", ".").length); - - final String stringToSplitOnNulls = "ab de fg"; - final String[] splitOnNullExpectedResults = {"ab", "de", "fg"}; - - final String[] splitOnNullResults = StringUtils.splitByWholeSeparator(stringToSplitOnNulls, null); - assertEquals(splitOnNullExpectedResults.length, splitOnNullResults.length); - for (int i = 0; i < splitOnNullExpectedResults.length; i += 1) { - assertEquals(splitOnNullExpectedResults[i], splitOnNullResults[i]); - } - - final String stringToSplitOnCharactersAndString = "abstemiouslyaeiouyabstemiously"; - - final String[] splitOnStringExpectedResults = {"abstemiously", "abstemiously"}; - final String[] splitOnStringResults = StringUtils.splitByWholeSeparator(stringToSplitOnCharactersAndString, "aeiouy"); - assertEquals(splitOnStringExpectedResults.length, splitOnStringResults.length); - for (int i = 0; i < splitOnStringExpectedResults.length; i += 1) { - assertEquals(splitOnStringExpectedResults[i], splitOnStringResults[i]); - } - - final String[] splitWithMultipleSeparatorExpectedResults = {"ab", "cd", "ef"}; - final String[] splitWithMultipleSeparator = StringUtils.splitByWholeSeparator("ab:cd::ef", ":"); - assertEquals(splitWithMultipleSeparatorExpectedResults.length, splitWithMultipleSeparator.length); - for (int i = 0; i < splitWithMultipleSeparatorExpectedResults.length; i++) { - assertEquals(splitWithMultipleSeparatorExpectedResults[i], splitWithMultipleSeparator[i]); - } - } - - @Test - void testSplitByWholeString_StringStringBooleanInt() { - assertArrayEquals(null, StringUtils.splitByWholeSeparator(null, ".", 3)); - - assertEquals(0, StringUtils.splitByWholeSeparator("", ".", 3).length); - - final String stringToSplitOnNulls = "ab de fg"; - final String[] splitOnNullExpectedResults = {"ab", "de fg"}; - //String[] splitOnNullExpectedResults = { "ab", "de" } ; - - final String[] splitOnNullResults = StringUtils.splitByWholeSeparator(stringToSplitOnNulls, null, 2); - assertEquals(splitOnNullExpectedResults.length, splitOnNullResults.length); - for (int i = 0; i < splitOnNullExpectedResults.length; i += 1) { - assertEquals(splitOnNullExpectedResults[i], splitOnNullResults[i]); - } - - final String stringToSplitOnCharactersAndString = "abstemiouslyaeiouyabstemiouslyaeiouyabstemiously"; - - final String[] splitOnStringExpectedResults = {"abstemiously", "abstemiouslyaeiouyabstemiously"}; - //String[] splitOnStringExpectedResults = { "abstemiously", "abstemiously" } ; - final String[] splitOnStringResults = StringUtils.splitByWholeSeparator(stringToSplitOnCharactersAndString, "aeiouy", 2); - assertEquals(splitOnStringExpectedResults.length, splitOnStringResults.length); - for (int i = 0; i < splitOnStringExpectedResults.length; i++) { - assertEquals(splitOnStringExpectedResults[i], splitOnStringResults[i]); - } - } - - @Test - void testHasText() { - String str = "\r\n\t\t"; - assertTrue(StringUtils.hasLength(str)); - assertFalse(StringUtils.hasText(str)); - str = null; - assertFalse(StringUtils.hasText(str)); - } - - @Test - void testReplace() { - String str = "hello ${t1} and ${t2} s"; - Map map = new HashMap<>(); - map.put("t1", "foo"); - map.put("t2", "bar"); - String ret = StringUtils.replace(str, map); - assertEquals(ret, "hello foo and bar s"); - - map = new HashMap<>(); - map.put("t1", "foo"); - map.put("t2", "${dddd}"); - ret = StringUtils.replace(str, map); - assertEquals(ret, "hello foo and ${dddd} s"); - - map = new HashMap<>(); - map.put("t1", null); - map.put("t2", "${dddd}"); - ret = StringUtils.replace(str, map); - assertEquals(ret, "hello null and ${dddd} s"); - - map = new HashMap<>(); - map.put("t1", 33); - map.put("t2", 42L); - ret = StringUtils.replace(str, map); - assertEquals(ret, "hello 33 and 42 s"); - } - - @Test - void testReplace2() { - String str2 = "hello {{{{} and {} mm"; - String ret2 = StringUtils.replace(str2, "foo", "bar"); - assertEquals(ret2, "hello {{{foo and bar mm"); - - ret2 = StringUtils.replace(str2, "foo"); - assertEquals(ret2, "hello {{{foo and {} mm"); - - ret2 = StringUtils.replace(str2, "foo", "bar", "foo2"); - assertEquals(ret2, "hello {{{foo and bar mm"); - - ret2 = StringUtils.replace(str2, 12, 23L, 33); - assertEquals(ret2, "hello {{{12 and 23 mm"); - } - -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/string/Utf8StringBuilderInvalidUtfTest.java b/firefly-common/src/test/java/com/fireflysource/common/string/Utf8StringBuilderInvalidUtfTest.java deleted file mode 100644 index 61ab94ace..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/string/Utf8StringBuilderInvalidUtfTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.common.string; - - -import com.fireflysource.common.object.TypeUtils; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Test various invalid UTF8 byte sequences. - */ -class Utf8StringBuilderInvalidUtfTest { - - @ParameterizedTest - @ValueSource(strings = {"c0af", "EDA080", "f08080af", "f8808080af", "e080af", "F4908080", "fbbfbfbfbf", "10FFFF", - "CeBaE1BdB9Cf83CeBcCeB5EdA080656469746564", "da07", "d807", "EDA087"}) - void testInvalidUTF8(String hex) { - byte[] bytes = TypeUtils.fromHexString(hex); - System.out.printf("Utf8StringBuilderInvalidUtfTest (%s)%n", TypeUtils.toHexString(bytes)); - - assertThrows(Utf8Appendable.NotUtf8Exception.class, () -> { - Utf8StringBuilder buffer = new Utf8StringBuilder(); - buffer.append(bytes, 0, bytes.length); - }); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/string/Utf8StringBuilderTest.java b/firefly-common/src/test/java/com/fireflysource/common/string/Utf8StringBuilderTest.java deleted file mode 100644 index b9e99dca4..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/string/Utf8StringBuilderTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.fireflysource.common.string; - -import com.fireflysource.common.object.TypeUtils; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.*; - - -class Utf8StringBuilderTest { - - @Test - void testFastFail_1() { - byte[] part1 = TypeUtils.fromHexString("cebae1bdb9cf83cebcceb5"); - byte[] part2 = TypeUtils.fromHexString("f4908080"); // INVALID - // Here for test tracking reasons, not needed to satisfy test - // byte[] part3 = TypeUtil.fromHexString("656469746564"); - - Utf8StringBuilder buffer = new Utf8StringBuilder(); - // Part 1 is valid - buffer.append(part1, 0, part1.length); - try { - // Part 2 is invalid - buffer.append(part2, 0, part2.length); - fail("Should have thrown a NotUtf8Exception"); - } catch (Utf8Appendable.NotUtf8Exception e) { - // expected path - } - } - - @Test - void testFastFail_2() { - byte[] part1 = TypeUtils.fromHexString("cebae1bdb9cf83cebcceb5f4"); - byte[] part2 = TypeUtils.fromHexString("90"); // INVALID - // Here for test search/tracking reasons, not needed to satisfy test - // byte[] part3 = TypeUtil.fromHexString("8080656469746564"); - - Utf8StringBuilder buffer = new Utf8StringBuilder(); - // Part 1 is valid - buffer.append(part1, 0, part1.length); - try { - // Part 2 is invalid - buffer.append(part2, 0, part2.length); - fail("Should have thrown a NotUtf8Exception"); - } catch (Utf8Appendable.NotUtf8Exception e) { - // expected path - } - } - - @Test - void testUtfStringBuilder() { - String source = "abcd012345\n\r\u0000\u00a4\u10fb\ufffdfirefly"; - byte[] bytes = source.getBytes(StandardCharsets.UTF_8); - Utf8StringBuilder buffer = new Utf8StringBuilder(); - for (byte aByte : bytes) - buffer.append(aByte); - assertEquals(source, buffer.toString()); - assertTrue(buffer.toString().endsWith("firefly")); - } - - @Test - void testShort() { - assertThrows(IllegalArgumentException.class, () -> { - String source = "abc\u10fb"; - byte[] bytes = source.getBytes(StandardCharsets.UTF_8); - Utf8StringBuilder buffer = new Utf8StringBuilder(); - for (int i = 0; i < bytes.length - 1; i++) { - buffer.append(bytes[i]); - } - buffer.toString(); - }); - } - - @Test - void testLong() { - String source = "abcXX"; - byte[] bytes = source.getBytes(StandardCharsets.UTF_8); - bytes[3] = (byte) 0xc0; - bytes[4] = (byte) 0x00; - - Utf8StringBuilder buffer = new Utf8StringBuilder(); - try { - for (byte aByte : bytes) { - buffer.append(aByte); - } - fail("Should have resulted in an Utf8Appendable.NotUtf8Exception"); - } catch (Utf8Appendable.NotUtf8Exception e) { - // expected path - } - assertEquals("abc\ufffd", buffer.toString()); - } - - @Test - void testUTF32codes() { - String source = "\uD842\uDF9F"; - byte[] bytes = source.getBytes(StandardCharsets.UTF_8); - - String jvmcheck = new String(bytes, 0, bytes.length, StandardCharsets.UTF_8); - assertEquals(source, jvmcheck); - - Utf8StringBuilder buffer = new Utf8StringBuilder(); - buffer.append(bytes, 0, bytes.length); - String result = buffer.toString(); - assertEquals(source, result); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/sys/TestJavaVersion.java b/firefly-common/src/test/java/com/fireflysource/common/sys/TestJavaVersion.java deleted file mode 100644 index 7df2fb450..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/sys/TestJavaVersion.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.common.sys; - -import org.junit.jupiter.api.Test; - -public class TestJavaVersion { - - @Test - void test() { - System.out.println(JavaVersion.VERSION.getVersion()); - System.out.println(JavaVersion.VERSION.getPlatform()); - System.out.println(System.getProperty("java.version")); - } -} diff --git a/firefly-common/src/test/java/com/fireflysource/common/sys/TestProjectVersion.java b/firefly-common/src/test/java/com/fireflysource/common/sys/TestProjectVersion.java deleted file mode 100644 index f2b9602c2..000000000 --- a/firefly-common/src/test/java/com/fireflysource/common/sys/TestProjectVersion.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.common.sys; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -public class TestProjectVersion { - - @Test - @DisplayName("should generate project logo") - void testLogo() { - String logo = ProjectVersion.logo(); - System.out.println(logo); - assertNotNull(logo); - } -} diff --git a/firefly-common/src/test/kotlin/com/fireflysource/common/concurrent/TestCompletableFuture.kt b/firefly-common/src/test/kotlin/com/fireflysource/common/concurrent/TestCompletableFuture.kt deleted file mode 100644 index 1196798db..000000000 --- a/firefly-common/src/test/kotlin/com/fireflysource/common/concurrent/TestCompletableFuture.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.common.concurrent - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.util.concurrent.CompletableFuture - -class TestCompletableFuture { - - @Test - @DisplayName("should receive the exception message.") - fun testException() { - CompletableFuture - .runAsync { throw IllegalStateException("test_error") } - .exceptionallyAccept { assertEquals("test_error", it.cause?.message) } - .get() - } - - @Test - @DisplayName("should exceptionally compose successfully.") - fun testExceptionCompose() { - val value = CompletableFuture - .supplyAsync { throw IllegalStateException("test_error") } - .exceptionallyCompose { CompletableFuture.supplyAsync { "ok" } } - .get() - assertEquals("ok", value) - } -} \ No newline at end of file diff --git a/firefly-common/src/test/kotlin/com/fireflysource/common/coroutine/TestChannelExtension.kt b/firefly-common/src/test/kotlin/com/fireflysource/common/coroutine/TestChannelExtension.kt deleted file mode 100644 index 4b9182c3e..000000000 --- a/firefly-common/src/test/kotlin/com/fireflysource/common/coroutine/TestChannelExtension.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.fireflysource.common.coroutine - -import kotlinx.coroutines.channels.Channel -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -/** - * @author Pengtao Qiu - */ -class TestChannelExtension { - - @Test - @DisplayName("should poll all elements successfully.") - fun testPollAll() { - val channel = Channel(Channel.UNLIMITED) - repeat(3) { - channel.trySend(1) - } - val list = mutableListOf() - channel.consumeAll { - list.add(it) - } - assertEquals(3, list.size) - } - - @Test - @DisplayName("should clear elements successfully.") - fun testClear() { - val channel = Channel(Channel.UNLIMITED) - repeat(3) { - channel.trySend(1) - } - channel.clear() - assertNull(channel.tryReceive().getOrNull()) - } -} \ No newline at end of file diff --git a/firefly-common/src/test/kotlin/com/fireflysource/common/coroutine/TestCoroutineLocal.kt b/firefly-common/src/test/kotlin/com/fireflysource/common/coroutine/TestCoroutineLocal.kt deleted file mode 100644 index e5d4175d9..000000000 --- a/firefly-common/src/test/kotlin/com/fireflysource/common/coroutine/TestCoroutineLocal.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.fireflysource.common.coroutine - -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.util.concurrent.* -import java.util.concurrent.atomic.AtomicInteger - -/** - * @author Pengtao Qiu - */ - -private val dispatchExecutor: ExecutorService = ThreadPoolExecutor( - 2, 2, - 0L, TimeUnit.MILLISECONDS, - ArrayBlockingQueue(20), - Executors.defaultThreadFactory() -) - -class TestCoroutineLocal { - private val ctx = CoroutineLocalContext - - @Test - @DisplayName("should get the coroutine local value across the many coroutines.") - fun test(): Unit = runTest { - val dispatcher: CoroutineDispatcher = dispatchExecutor.asCoroutineDispatcher() - val key = "index" - val jobs = List(5) { i -> - async(dispatcher + ctx.asElement(mutableMapOf(key to i))) { - testAttr(key, i) - withTimeout(2000) { - assertEquals(i, ctx.getAttr(key)) - ctx.computeIfAbsent("key33") { 33 } - ctx.setAttr("newKey", i) - testLocalAttr(key, i) - assertEquals(i, ctx.getAttr(key)) - } - } - } - - jobs.forEach { - it.join() - } - - } - - private suspend fun testAttr(key: String, expect: Int) { - event { println("hello") }.join() - assertEquals(expect, ctx.getAttr(key)) - } - - private suspend fun testLocalAttr(key: String, expect: Int) = withContextInheritable { - assertEquals(33, ctx.getAttr("key33")) - assertEquals(expect, ctx.getAttr("newKey")) - println("beforeSuspend ${ctx.getAttributes()}. context: $coroutineContext") - inheritableLaunch(attributes = mutableMapOf("d1" to 200)) { - ctx.setAttr("c1", 100) - assertEquals(100, ctx.getAttr("c1")) - assertEquals(expect, ctx.getAttr(key)) - assertEquals(33, ctx.getAttr("key33")) - assertEquals(expect, ctx.getAttr("newKey")) - assertEquals("OK", ctx.getAttrOrDefault("keyX") { "OK" }) - println("inner fun. context: $coroutineContext") - assertEquals(200, ctx.getAttr("d1")) - }.join() - - val old = inheritableAsync { - val old = ctx.setAttr("c1", 200) - assertEquals(200, ctx.getAttr("c1")) - assertEquals(33, ctx.getAttr("key33")) - old - } - assertNull(old.await()) - - println("afterSuspend ${ctx.getAttributes()}") - assertNull(ctx.getAttr("d1")) - assertNull(ctx.getAttr("c1")) - } - - @Test - @DisplayName("should cancel the channel") - fun testCancelChannel(): Unit = runTest { - val count = AtomicInteger() - val channel = Channel() - val job = computeAsync { - println("job context: ${Thread.currentThread().name}") - while (true) { - val i = channel.receive() - println("test: $i") - count.incrementAndGet() - } - } - - compute { - (1..10).forEach { - delay(100) - channel.trySend(it) - } - } - - delay(500) - job.cancel() - job.join() - println("end") - assertTrue(count.get() < 10) - } -} \ No newline at end of file diff --git a/firefly-common/src/test/resources/testFile1 b/firefly-common/src/test/resources/testFile1 deleted file mode 100644 index 0bf7f8c4e..000000000 --- a/firefly-common/src/test/resources/testFile1 +++ /dev/null @@ -1,3 +0,0 @@ -the line 1 -the line 2 -hello the end line \ No newline at end of file diff --git a/firefly-example/pom.xml b/firefly-example/pom.xml deleted file mode 100644 index 31fa6d2d0..000000000 --- a/firefly-example/pom.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-example - jar - - firefly-example - http://www.fireflysource.com - - - - com.fireflysource - firefly-net - - - - com.fireflysource - firefly-serialization - - - - com.fireflysource - firefly-slf4j - - - - - firefly-example - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.jks - **/*.ico - **/*.html - **/*.txt - - - **/*.xml - **/*.properties - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/test/resources - false - - **/*.jks - **/*.ico - **/*.html - **/*.txt - - - **/*.xml - **/*.properties - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - diff --git a/firefly-example/src/main/java/com/fireflysource/doc/FeignedExampleDoc.java b/firefly-example/src/main/java/com/fireflysource/doc/FeignedExampleDoc.java deleted file mode 100644 index 6be09192b..000000000 --- a/firefly-example/src/main/java/com/fireflysource/doc/FeignedExampleDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedExampleDoc { -} diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientDemo.kt deleted file mode 100644 index 34900cfd5..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientDemo.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.fx -import kotlinx.coroutines.future.await - - -suspend fun main() { - // http://localhost:8080/hello-world - // http://nghttp2.org - - repeat(1) { - val response = fx.httpClient() - .get("http://nghttp2.org/") - .putQueryString("name", "PT_$it") - .upgradeHttp2() - .submit().await() - println("${response.status} ${response.reason}") - println(response.httpFields) - println(response.stringBody) - println() - } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientMultiPartUploadFileDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientMultiPartUploadFileDemo.kt deleted file mode 100644 index fe0cfcc11..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientMultiPartUploadFileDemo.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.common.annotation.NoArg -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.resourceFileBody -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.stringBody -import java.nio.file.StandardOpenOption - -@NoArg -data class Product(var id: String, var brand: String, var description: String) - -fun main() { - `$`.httpServer() - .router().post("/product/file-upload").handler { ctx -> - val id = ctx.getPart("id") - val brand = ctx.getPart("brand") - val description = ctx.getPart("description") - ctx.end(Product(id.stringBody, brand.stringBody, description.stringBody).toString()) - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().post("$url/product/file-upload") - .addPart("id", stringBody("x01"), null) - .addPart("brand", stringBody("Test"), null) - .addFilePart( - "description", "poem.txt", - resourceFileBody("files/poem.txt", StandardOpenOption.READ), - null - ) - .submit().thenAccept { response -> println(response) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientProxyDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientProxyDemo.kt deleted file mode 100644 index 572cf636d..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientProxyDemo.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.fx -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.ProxyConfig -import kotlinx.coroutines.future.await - -suspend fun main() { - val client = createProxyHttpClient("127.0.0.1", 1091) -// val host = "nghttp2.org" - val host = "www.google.com" - val response = client - .get("https://$host/") - .submit().await() - println("${response.status} ${response.reason}") - println(response.httpFields) - println(response.stringBody) -} - -fun createProxyHttpClient(host: String, port: Int): HttpClient { - val proxyConfig = ProxyConfig() - proxyConfig.host = host - proxyConfig.port = port - val httpConfig = HttpConfig() - httpConfig.proxyConfig = proxyConfig - return fx.createHttpClient(httpConfig) -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientWithoutConnectionPoolDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientWithoutConnectionPoolDemo.kt deleted file mode 100644 index 545b58dcb..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpClientWithoutConnectionPoolDemo.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.common.io.useAwait -import com.fireflysource.net.http.client.impl.connectAsync -import kotlinx.coroutines.delay -import kotlinx.coroutines.future.await - -fun main() { - `$`.httpServer() - .router().get("/hello").handler { ctx -> ctx.end("Hello http! ") } - .listen("localhost", 8090) - - `$`.httpClient().connectAsync("http://localhost:8090") { connection -> - connection.useAwait { - repeat(3) { - val response = connection.get("/hello").submit().await() - println("connection ${connection.id} received: ${response.stringBody}") - delay(1000) - } - } - println("connection ${connection.id} closed. ${connection.isClosed}") - } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerCorsDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerCorsDemo.kt deleted file mode 100644 index 4ec7e3ff8..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerCorsDemo.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.impl.router.handler.CorsConfig -import com.fireflysource.net.http.server.impl.router.handler.CorsHandler - -fun main() { - val corsConfig = CorsConfig("*.cors.test.com") - `$`.httpServer() - .router().path("*").handler(CorsHandler(corsConfig)) - .router().post("/cors-data-request/*") - .handler { it.end("success") } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().post("$url/cors-data-request/xxx") - .put(HttpHeader.ORIGIN, "hello.cors.test.com") - .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN_UTF_8.value) - .body("hello") - .submit().thenAccept { response -> println(response) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerDemo.kt deleted file mode 100644 index fe3e735e8..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerDemo.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.net.http.server.HttpServerFactory -import com.fireflysource.net.http.server.impl.router.handler.CorsConfig -import com.fireflysource.net.http.server.impl.router.handler.CorsHandler -import com.fireflysource.net.http.server.impl.router.handler.FileHandler - -/* -Intel i5 1.4GHz 16GB macbook pro 13 - -wrk -t4 -c16 -d60s --latency http://localhost:9999/test -Running 1m test @ http://localhost:9999/test - 4 threads and 16 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 170.83us 34.94us 4.30ms 77.90% - Req/Sec 22.74k 0.99k 26.71k 81.82% - Latency Distribution - 50% 170.00us - 75% 189.00us - 90% 207.00us - 99% 252.00us - 5438229 requests in 1.00m, 456.39MB read -Requests/sec: 90486.34 -Transfer/sec: 7.59MB -*/ -fun main() { - val httpServer = HttpServerFactory.create() - val corsConfig = CorsConfig("*") - - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().paths(listOf("/favicon.ico", "/poem.html", "/poem.txt")) - .handler(FileHandler.createFileHandlerByResourcePath("files")) - .router().post("/cors-preflight/*").handler { - it.end( - """ - |{"status": "ok"} - """.trimMargin() - ) - } - .router().get("/test").handler { - it.end("Welcome") - } - .listen("localhost", 9999) -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerErrorHandlerDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerErrorHandlerDemo.kt deleted file mode 100644 index d04c7ffe1..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerErrorHandlerDemo.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` - -fun main() { - `$`.httpServer() - .router().post("/product").handler { - throw IllegalStateException("Create product exception") - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().post("$url/product/").submit() - .thenAccept { response -> println(response) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerErrorHandlerDemo2.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerErrorHandlerDemo2.kt deleted file mode 100644 index 4b7a853f3..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerErrorHandlerDemo2.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.net.http.common.model.HttpStatus - -fun main() { - `$`.httpServer() - .onException { ctx, exception -> - ctx.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500) - .end("The server exception. ${exception.message}") - } - .router().post("/product").handler { - throw IllegalStateException("Create product exception") - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().post("$url/product/").submit() - .thenAccept { response -> println(response) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerPathParamDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerPathParamDemo.kt deleted file mode 100644 index ccf7ac46f..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerPathParamDemo.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.net.http.common.model.HttpStatus.NOT_FOUND_404 - -fun main() { - `$`.httpServer() - .router().get("/product/:id").handler { ctx -> - when (val id = ctx.getPathParameter("id")) { - "1" -> ctx.end("Apple") - "2" -> ctx.end("Orange") - else -> ctx.setStatus(NOT_FOUND_404).end("The product $id not found.") - } - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().get("$url/product/1").submit() - .thenAccept { response -> println(response.stringBody) } - - `$`.httpClient().get("$url/product/2").submit() - .thenAccept { response -> println(response.stringBody) } - - `$`.httpClient().get("$url/product/3").submit() - .thenAccept { response -> println(response.stringBody) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerProxyDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerProxyDemo.kt deleted file mode 100644 index e82b8e3fb..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerProxyDemo.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.fx -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.client.HttpClientResponse -import kotlinx.coroutines.delay -import kotlinx.coroutines.future.await - -suspend fun main() { - val proxy = fx.createHttpProxy() - proxy.listen("localhost", 1678) - - val client = createProxyHttpClient("localhost", 1678) - testHttpsProxy(client) - delay(1000) - testHttpProxy(client) -} - -suspend fun testHttpsProxy(client: HttpClient) { - val response = client.get("https://www.baidu.com/").submit().await() - printResponse(response) -} - -suspend fun testHttpProxy(client: HttpClient) { - val response = client.get("http://www.fireflysource.com/").submit().await() - printResponse(response) -} - -private fun printResponse(response: HttpClientResponse) { - println("${response.status} ${response.reason}") - println(response.httpFields) - println(response.stringBody) - println("-------------------------------------------") - println() -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRegexDemo1.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRegexDemo1.kt deleted file mode 100644 index c4dbc6003..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRegexDemo1.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.net.http.common.model.HttpMethod - -fun main() { - `$`.httpServer() - .router().method(HttpMethod.PUT).pathRegex("/product/(.*)/(.*)").handler { ctx -> - val type = ctx.getPathParameterByRegexGroup(1) - val id = ctx.getPathParameterByRegexGroup(2) - val product = ctx.stringBody - ctx.end("Put product success. id: $id, type: $type, product: $product") - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().put("$url/product/fruit/1").body("Apple").submit() - .thenAccept { response -> println(response.stringBody) } - - `$`.httpClient().put("$url/product/book/1").body("Tom and Jerry").submit() - .thenAccept { response -> println(response.stringBody) } - - `$`.httpClient().put("$url/product/book/2").body("The Three-Body Problem").submit() - .thenAccept { response -> println(response.stringBody) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRouterContextDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRouterContextDemo.kt deleted file mode 100644 index c10607ce0..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRouterContextDemo.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` - -fun main() { - `$`.httpServer() - .router().get("/").handler { ctx -> - ctx.attributes["router1"] = "Some one visits the /. " - ctx.write("Hello world! ").next() - } - .router().get("/").handler { ctx -> - val data = ctx.attributes["router1"] - ctx.end("The router data: $data") - } - .listen("localhost", 8090) -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRouterDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRouterDemo.kt deleted file mode 100644 index 7a9f32777..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRouterDemo.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` - -fun main() { - `$`.httpServer() - .router().get("/").handler { ctx -> ctx.write("Hello world! ").next() } - .router().get("/").handler { ctx -> ctx.end("The router demo.") } - .listen("localhost", 8090) -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByAcceptTypeDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByAcceptTypeDemo.kt deleted file mode 100644 index ca22014f7..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByAcceptTypeDemo.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.serialization.SerializationServiceFactory.json - -fun main() { - `$`.httpServer() - .router().get("/product/:id").produces("text/plain") - .handler { ctx -> - ctx.end(Car("Benz", "Black").toString()) - } - .router().get("/product/:id").produces("application/json") - .handler { ctx -> - ctx.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .end(json.write(Car("Benz", "Black"))) - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().get("$url/product/3") - .put(HttpHeader.ACCEPT, "text/plain, application/json;q=0.9, */*;q=0.8") - .submit().thenAccept { response -> println("accept text; ${response.stringBody}") } - - `$`.httpClient().get("$url/product/3") - .put(HttpHeader.ACCEPT, "application/json, text/plain, */*;q=0.8") - .submit().thenAccept { response -> println("accept json; ${response.stringBody}") } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByContentTypeDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByContentTypeDemo.kt deleted file mode 100644 index 1b16aa21b..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByContentTypeDemo.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.common.annotation.NoArg -import com.fireflysource.net.http.common.model.HttpField -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.serialization.SerializationServiceFactory.json -import com.fireflysource.serialization.impl.json.read - -@NoArg -data class Car(var name: String, var color: String) - -fun main() { - `$`.httpServer() - .router().put("/product/:id").consumes("*/json") - .handler { ctx -> - val id = ctx.getPathParameter("id") - val type = ctx.getPathParameter(0) - val car = json().read(ctx.stringBody) - - ctx.write("Update product. id: $id, type: $type. \r\n") - .end(car.toString()) - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient() - .put("$url/product/3") - .add(HttpField(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value)) - .body(json.write(Car("Benz", "Black"))) - .submit().thenAccept { response -> println(response.stringBody) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByMethodDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByMethodDemo.kt deleted file mode 100644 index 4adc8f356..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerRoutingByMethodDemo.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` - -fun main() { - `$`.httpServer() - .router().get("/product/:id").handler { ctx -> - val id = ctx.getPathParameter("id") - ctx.end("Get the product $id") - } - .router().post("/product").handler { ctx -> - ctx.end("Create the product 1") - } - .router().put("/product/:id").handler { ctx -> - val id = ctx.getPathParameter("id") - ctx.end("Update the product $id") - } - .router().delete("/product/:id").handler { ctx -> - val id = ctx.getPathParameter("id") - ctx.end("Delete the product $id") - } - .listen("localhost", 8090) -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerStaticFileDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerStaticFileDemo.kt deleted file mode 100644 index a6259b94b..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerStaticFileDemo.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.server.impl.router.handler.FileHandler - -fun main() { - `$`.httpServer() - .router().method(HttpMethod.GET) - .paths(listOf("/favicon.ico", "/poem.html", "/poem.txt")) - .handler(FileHandler.createFileHandlerByResourcePath("files")) - .listen("localhost", 8090) -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerWildcardDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerWildcardDemo.kt deleted file mode 100644 index bdd8dbbd4..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpServerWildcardDemo.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` - -fun main() { - `$`.httpServer() - .router().put("/product/*/*").handler { ctx -> - val type = ctx.getPathParameter(0) - val id = ctx.getPathParameter(1) - val product = ctx.stringBody - ctx.end("Put product success. id: $id, type: $type, product: $product") - } - .listen("localhost", 8090) - - val url = "http://localhost:8090" - `$`.httpClient().put("$url/product/fruit/1").body("Apple").submit() - .thenAccept { response -> println(response.stringBody) } - - `$`.httpClient().put("$url/product/book/1").body("Tom and Jerry").submit() - .thenAccept { response -> println(response.stringBody) } - - `$`.httpClient().put("$url/product/book/2").body("The Three-Body Problem").submit() - .thenAccept { response -> println(response.stringBody) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpsDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/HttpsDemo.kt deleted file mode 100644 index 88957d51b..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/HttpsDemo.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` - -fun main() { - `$`.httpServer() - .router().get("/").handler { ctx -> ctx.end("Hello https! ") } - .enableSecureConnection() - .listen("localhost", 8090) - - `$`.httpClient().get("https://localhost:8090/").submit() - .thenAccept { response -> println(response.stringBody) } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/TcpServerAndClientDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/TcpServerAndClientDemo.kt deleted file mode 100644 index 85d7d804a..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/TcpServerAndClientDemo.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.BufferUtils.toBuffer -import com.fireflysource.common.io.useAwait -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.aio.connectAsync -import com.fireflysource.net.tcp.aio.onAcceptAsync -import kotlinx.coroutines.delay -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.util.* - -fun main() { - `$`.tcpServer().onAcceptAsync { connection -> - launch { writeLoop("Server", connection) } - launch { readLoop(connection) } - }.listen("localhost", 8090) - - `$`.tcpClient().connectAsync("localhost", 8090) { connection -> - launch { writeLoop("Client", connection) } - launch { readLoop(connection) } - } -} - -private suspend fun readLoop(connection: TcpConnection) = connection.useAwait { - while (true) { - try { - val buffer = connection.read().await() - println(BufferUtils.toString(buffer)) - } catch (e: Exception) { - println("Connection closed.") - break - } - } -} - -private suspend fun writeLoop(data: String, connection: TcpConnection) = connection.useAwait { - (1..10).forEach { - connection.write(toBuffer("TCP ${data}. count: $it, time: ${Date()}")) - delay(1000) - } -} \ No newline at end of file diff --git a/firefly-example/src/main/kotlin/com/fireflysource/example/WebSocketServerDemo.kt b/firefly-example/src/main/kotlin/com/fireflysource/example/WebSocketServerDemo.kt deleted file mode 100644 index b8057f0b0..000000000 --- a/firefly-example/src/main/kotlin/com/fireflysource/example/WebSocketServerDemo.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.fireflysource.example - -import com.fireflysource.`$` -import com.fireflysource.common.io.useAwait -import com.fireflysource.net.websocket.client.impl.connectAsync -import com.fireflysource.net.websocket.client.impl.onClientMessageAsync -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.frame.Frame -import com.fireflysource.net.websocket.common.frame.TextFrame -import com.fireflysource.net.websocket.server.impl.onAcceptAsync -import com.fireflysource.net.websocket.server.impl.onServerMessageAsync -import kotlinx.coroutines.delay -import java.util.* - -fun main() { - `$`.httpServer().websocket("/websocket/hello") - .onServerMessageAsync { frame, _ -> onMessage(frame) } - .onAcceptAsync { connection -> sendMessage("Server", connection) } - .listen("localhost", 8090) - - val url = "ws://localhost:8090" - `$`.httpClient().websocket("$url/websocket/hello") - .extensions(listOf("permessage-deflate")) - .onClientMessageAsync { frame, _ -> onMessage(frame) } - .connectAsync { connection -> sendMessage("Client", connection) } -} - -private suspend fun sendMessage(data: String, connection: WebSocketConnection) = connection.useAwait { - (1..10).forEach { - connection.sendText("WebSocket ${data}. count: $it, time: ${Date()}") - delay(1000) - } -} - -private fun onMessage(frame: Frame) { - if (frame is TextFrame) { - println(frame.payloadAsUTF8) - } -} \ No newline at end of file diff --git a/firefly-example/src/main/resources/files/poem.html b/firefly-example/src/main/resources/files/poem.html deleted file mode 100644 index 415dcaccf..000000000 --- a/firefly-example/src/main/resources/files/poem.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Poem - - -

    家庭

    -

    - 我独自在横跨过田地的路上行走,夕阳像一个守财奴似的,正藏起它的最后的金子。 - 白昼更加深沉地陷入黑暗之中,那已经收割了的孤独的田地, - 默默地躺在那里。 - 天空里突然升起了一个男孩童的尖锐的歌声。他穿过看不见的黑暗,留下他的歌声的辙痕跨过黄昏的静谧。 - 他的乡村的家坐落在荒凉的边上,在甘蔗田的后面,躲藏在香蕉树,瘦长的槟榔树,椰子树和深绿色的贾克果树的阴影里。 - 我在星光下独自走着的路上停留了一会,我看见黑沉沉的大地展开在我的面前,用她的手臂拥抱着无量数的家庭, - 在那些家庭里有着摇篮和床铺,母亲们的心和夜晚的灯,还有年轻轻的生命, - 他们满心快乐,却浑然不知这样的快乐对于世界的价值。 -

    - - \ No newline at end of file diff --git a/firefly-example/src/main/resources/files/poem.txt b/firefly-example/src/main/resources/files/poem.txt deleted file mode 100644 index a9ccadd62..000000000 --- a/firefly-example/src/main/resources/files/poem.txt +++ /dev/null @@ -1,22 +0,0 @@ -偷睡眠的人 -谁从孩童的眼里把睡眠偷了去呢?我一定要知道。 -妈妈把她的水罐挟在腰间,走到近村汲水去了。 -这是正午的时候,孩童们游戏的时间已经过去了,池中的鸭子缄默无声。 -牧童躺在榕树的荫下睡着了。 -白鹤庄重而安静地立在檬果树边的泥泽里。 - -就在这个时候,偷睡眠的人跑来从孩童的两眼里捉住睡眠,便飞去了。 -当妈妈回来时,她看见孩童四肢着地地在屋里爬着。 -谁从孩童的眼里把睡眠偷了去呢?我一定要知道。我一定要找到她,把她锁起来。 -我一定要向那个黑洞里张望,在这个洞里,有一道小泉从圆的和有皱纹的石上滴下来。 -我一定要到醉花林中的沉寂的树影里搜寻,在这林中,鸽子在它们住的地方咕咕地叫着,仙女的脚环在繁星满天的静夜里叮当地响着。 -我要在黄昏时,向静静的萧萧的竹林里窥望,在这林中,萤火虫闪闪地耗费它们的光明,只要遇见一个人,我便要问他:“谁能告诉我偷睡眠者住在什么地方?” - -谁从孩童的眼里把睡眠偷了去呢?我一定要知道。 -只要我能捉住她,怕不会给她一顿好教训! -我要闯入她的巢穴,看她把所有偷来的睡眠藏在什么地方。 -我要把它都夺回来,带回家去。 -我要把她的双翼缚得紧紧的,把她放在河边,然后叫她拿一根芦苇在灯心草和睡莲间钓鱼为戏。 -黄昏,街上已经收了市,村里的孩童们都坐在妈妈的膝上时, -夜鸟便会讥讽地在她耳边说: -“你现在还想偷谁的睡眠呢?” \ No newline at end of file diff --git a/firefly-example/src/main/resources/firefly-log.xml b/firefly-example/src/main/resources/firefly-log.xml deleted file mode 100644 index 9d3dbfa8a..000000000 --- a/firefly-example/src/main/resources/firefly-log.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - firefly-system - INFO - ${log.path} - - - diff --git a/firefly-jni-helper/build.sh b/firefly-jni-helper/build.sh deleted file mode 100755 index a8ee8bd33..000000000 --- a/firefly-jni-helper/build.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -PROJECT_HOME=$(cd "$(dirname "$0")" && pwd) -echo "project dir: $PROJECT_HOME" - -JNI_HELPER_EXAMPLE_HEADERS_DIR="$PROJECT_HOME/target/headers" -echo "JNI helper example headers dir: $JNI_HELPER_EXAMPLE_HEADERS_DIR" - -JNI_HELPER_SOURCES_DIR="$PROJECT_HOME/src/main/cpp/jni-helper" -echo "JNI helper sources dir: $JNI_HELPER_SOURCES_DIR" - - -# generate JNI header -cd "$PROJECT_HOME" && mvn clean compile -cp "$JNI_HELPER_EXAMPLE_HEADERS_DIR/"*.h "$JNI_HELPER_SOURCES_DIR/example" -echo "-- copy JNI example headers is complete" - -# build JNI example project -cd "$JNI_HELPER_SOURCES_DIR" && sh ./build.sh - - -JNI_HELPER_EXAMPLE_LIB_DIR="$JNI_HELPER_SOURCES_DIR/build-release/release" -echo "JNI helper example lib dir: $JNI_HELPER_EXAMPLE_LIB_DIR" - -if [ "$(uname)" == "Darwin" ];then - # Mac OS X 操作系统 - if [ -f "$JNI_HELPER_EXAMPLE_LIB_DIR/lib/libjni_helper_example.dylib" ]; then - cp "$JNI_HELPER_EXAMPLE_LIB_DIR/lib/libjni_helper_example.dylib" "$PROJECT_HOME/src/test/resources/lib/macos" - fi -elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ];then - # GNU/Linux操作系统 - if [ -f "$JNI_HELPER_EXAMPLE_LIB_DIR/lib/libjni_helper_example.so" ]; then - cp "$JNI_HELPER_EXAMPLE_LIB_DIR/lib/libjni_helper_example.so" "$PROJECT_HOME/src/test/resources/lib/linux" - fi -elif [[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" || "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]];then - # Windows NT操作系统 - if [ -f "$JNI_HELPER_EXAMPLE_LIB_DIR/bin/jni_helper_example.dll" ]; then - cp "$JNI_HELPER_EXAMPLE_LIB_DIR/bin/jni_helper_example.dll" "$PROJECT_HOME/src/test/resources/lib/windows/libjni_helper_example.dll" - fi -fi - -echo "-- build JNI example cpp project is complete" - -# run JNI test cases -cd "$PROJECT_HOME" && mvn test - - diff --git a/firefly-jni-helper/pom.xml b/firefly-jni-helper/pom.xml deleted file mode 100644 index e5f8277a2..000000000 --- a/firefly-jni-helper/pom.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-jni-helper - jar - - firefly-jni-helper - http://www.fireflysource.com - - - - com.fireflysource - firefly-common - - - - com.fireflysource - firefly-slf4j - test - - - - - firefly-jni-helper - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.xml - **/*.properties - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/test/resources - false - - **/*.jks - **/*.ico - **/*.html - **/*.txt - **/*.so - **/*.dylib - **/*.dll - - - **/*.xml - **/*.properties - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/.gitignore b/firefly-jni-helper/src/main/cpp/jni-helper/.gitignore deleted file mode 100644 index c8d652170..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -cmake-build-debug -build-release \ No newline at end of file diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/CMakeLists.txt b/firefly-jni-helper/src/main/cpp/jni-helper/CMakeLists.txt deleted file mode 100644 index 79afc4557..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/CMakeLists.txt +++ /dev/null @@ -1,72 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(jni_helper) - -set(CMAKE_CXX_STANDARD 14) - -# set compiler level -if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") - #不管CACHE里有没有设置过CMAKE_BUILD_TYPE这个变量,都强制赋值这个值为RelWithDebInfo - set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE) - - # 当使用cmake-gui的时候,设置构建级别的四个可选项 - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" - "MinSizeRel" "RelWithDebInfo") -endif () - -# set output directory -message(STATUS "project source dir: ${PROJECT_SOURCE_DIR}") -message(STATUS "bin dir: ${CMAKE_BINARY_DIR}") - -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/bin) - -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/bin) - -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/release/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/release/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/release/bin) - -# set java dependency -include(UseJava) -if (DEFINED ENV{JAVA_HOME}) - set(JAVA_HOME "$ENV{JAVA_HOME}") -else () - set(JAVA_HOME /Users/qiupengtao/Develop/jdk/hotspot/jdk8u292-b10/Contents/Home) -endif () -message(STATUS "JAVA_HOME variable is defined or set as '${JAVA_HOME}'") - -if (APPLE) - message(STATUS "os: MacOS") - set(JNI_INCLUDE_DIRS ${JAVA_HOME}/include ${JAVA_HOME}/include/darwin) - set(JNI_LIB_DIRS ${JAVA_HOME}/jre/lib) - add_compile_options(-fPIC) -elseif (WIN32) - message(STATUS "os: Windows") - set(JNI_INCLUDE_DIRS ${JAVA_HOME}/include ${JAVA_HOME}/include/win32 ${JAVA_HOME}/include/win32/bridge) - set(JNI_LIB_DIRS ${JAVA_HOME}/jre/lib) -elseif (UNIX) - message(STATUS "os: Unix like e.g. linux, bsd.") - set(JNI_INCLUDE_DIRS ${JAVA_HOME}/include ${JAVA_HOME}/include/linux) - set(JNI_LIB_DIRS ${JAVA_HOME}/jre/lib) - add_compile_options(-fPIC) -endif () - -message(STATUS "JNI include dirs: ${JNI_INCLUDE_DIRS}") -include_directories(${JNI_INCLUDE_DIRS}) -link_directories(${JNI_LIB_DIRS}) - -# set jni helper sources -set(JNI_HELPER_SOURCES JniHelper.hpp JniHelper.cpp) -add_library(jni_helper_static STATIC ${JNI_HELPER_SOURCES}) -add_library(jni_helper SHARED ${JNI_HELPER_SOURCES}) - -# set jni example sources -set(JNI_HELPER_EXAMPLE_SOURCES - example/com_fireflysource_jni_example_JniExample.h - example/JniExample.cpp) -add_library(jni_helper_example SHARED ${JNI_HELPER_EXAMPLE_SOURCES}) -target_link_libraries(jni_helper_example jni_helper_static) \ No newline at end of file diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/JniHelper.cpp b/firefly-jni-helper/src/main/cpp/jni-helper/JniHelper.cpp deleted file mode 100644 index 6f1233f95..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/JniHelper.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// Created by qiupengtao on 2021/7/9. -// -#include "JniHelper.hpp" -#include - -namespace com { -namespace fireflysource { -namespace jni { - -std::string javaStringToCppString(JNIEnv *env, jstring javaString) { - const char *cString = env->GetStringUTFChars(javaString, nullptr); - std::string result = cString; - env->ReleaseStringUTFChars(javaString, cString); - return result; -} - -jstring newJavaString(JNIEnv *env, const std::string &cppString) { - auto result_buffer = static_cast(std::malloc(cppString.size())); - std::strcpy(result_buffer, cppString.c_str()); - return env->NewStringUTF(result_buffer); -} - -LocalReference newJavaStringLocalReference(JNIEnv *env, const std::string &cppString) { - return LocalReference(env, cppString, newJavaString); -} - -namespace java { - -void println(JNIEnv *env, jstring javaString) { - // Get system class - jclass system = env->FindClass("java/lang/System"); - // Lookup the "out" field - jfieldID fid = env->GetStaticFieldID(system, "out", "Ljava/io/PrintStream;"); - jobject out = env->GetStaticObjectField(system, fid); - // Get PrintStream class - jclass printStream = env->FindClass("java/io/PrintStream"); - // Lookup printLn(String) - jmethodID printlnMethod = env->GetMethodID(printStream, "println", "(Ljava/lang/String;)V"); - env->CallVoidMethod(out, printlnMethod, javaString); -} - -void println(JNIEnv *env, const std::string &str) { - LocalReference javaStringRef = newJavaStringLocalReference(env, str); - jstring javaString = javaStringRef.get(); - println(env, javaString); -} - -} - -} -} -} \ No newline at end of file diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/JniHelper.hpp b/firefly-jni-helper/src/main/cpp/jni-helper/JniHelper.hpp deleted file mode 100644 index de8944be4..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/JniHelper.hpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// Created by qiupengtao on 2021/7/9. -// - -#ifndef TEST_JNI_JNI_HELPER_JNIHELPER_HPP_ -#define TEST_JNI_JNI_HELPER_JNIHELPER_HPP_ -#include -#include - -namespace com { -namespace fireflysource { -namespace jni { - -template -class LocalReference { - public: - template - LocalReference(JNIEnv *env, - const CppType &cppParam, - JniType(*newJavaType)(JNIEnv *, const CppType &)) { - this->env = env; - this->javaObject = newJavaType(env, cppParam); - } - - ~LocalReference() { - env->DeleteLocalRef(javaObject); - }; - - JniType get() { - return javaObject; - } - - private: - JniType javaObject; - JNIEnv *env = nullptr; -}; - -std::string javaStringToCppString(JNIEnv *env, jstring javaString); - -jstring newJavaString(JNIEnv *env, const std::string &cppString); - -LocalReference newJavaStringLocalReference(JNIEnv *env, const std::string &cppString); - -namespace java { - -void println(JNIEnv *env, jstring javaString); - -void println(JNIEnv *env, const std::string &cppString); - -} - -} -} -} - -#endif //TEST_JNI_JNI_HELPER_JNIHELPER_HPP_ diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/build.sh b/firefly-jni-helper/src/main/cpp/jni-helper/build.sh deleted file mode 100755 index 2a99a0277..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/build.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -PROJECT_HOME=$(cd "$(dirname "$0")" && pwd) -echo "project dir: $PROJECT_HOME" -RELEASE_BUILD_DIR="$PROJECT_HOME/build-release" -echo "cmake release dir: $RELEASE_BUILD_DIR" - -if [ ! -d "$RELEASE_BUILD_DIR" ]; then - mkdir "$RELEASE_BUILD_DIR" -else - rm -rf "$RELEASE_BUILD_DIR" -fi - -echo "$(uname)" - -if [ "$(uname)" == "Darwin" ];then - echo "build on MacOS" - cmake -S "$PROJECT_HOME" -B "$RELEASE_BUILD_DIR" - cmake --build "$RELEASE_BUILD_DIR" --target clean - cmake --build "$RELEASE_BUILD_DIR" --target all - cd "$RELEASE_BUILD_DIR" && make -elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ];then - echo "build on Linux" - cmake -S "$PROJECT_HOME" -B "$RELEASE_BUILD_DIR" - cmake --build "$RELEASE_BUILD_DIR" --target clean - cmake --build "$RELEASE_BUILD_DIR" --target all - cd "$RELEASE_BUILD_DIR" && make -elif [[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" || "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]];then - echo "build on Windows" - cmake -S "$PROJECT_HOME" -B "$RELEASE_BUILD_DIR" - cmake --build "$RELEASE_BUILD_DIR" --target clean - cmake --build "$RELEASE_BUILD_DIR" --target ALL_BUILD - cd "$RELEASE_BUILD_DIR" && msbuild.exe ALL_BUILD.vcxproj -t:rebuild -p:Configuration=Release -fi \ No newline at end of file diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/example/JniExample.cpp b/firefly-jni-helper/src/main/cpp/jni-helper/example/JniExample.cpp deleted file mode 100644 index c4c94bdd3..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/example/JniExample.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// -// Created by qiupengtao on 2021/7/12. -// -#include "com_fireflysource_jni_example_JniExample.h" -#include "../JniHelper.hpp" - -namespace com { -namespace fireflysource { -namespace jni { -namespace example { - -std::string sayHello(const std::string &str) { - return "Bonjour, " + str; -} - -} -} -} -} - -using namespace com::fireflysource::jni; - -JNIEXPORT jstring JNICALL Java_com_fireflysource_jni_example_JniExample_sayHello - (JNIEnv *env, jclass javaClass, jstring javaString) { - if (javaString == nullptr) { - return newJavaString(env, "param str cannot be null"); - } - - std::string param = javaStringToCppString(env, javaString); - std::string hello = example::sayHello(param); // Call native library - java::println(env, "call java method to print result: " + hello); - - return newJavaString(env, hello); -} \ No newline at end of file diff --git a/firefly-jni-helper/src/main/cpp/jni-helper/example/com_fireflysource_jni_example_JniExample.h b/firefly-jni-helper/src/main/cpp/jni-helper/example/com_fireflysource_jni_example_JniExample.h deleted file mode 100644 index 4fba3bdac..000000000 --- a/firefly-jni-helper/src/main/cpp/jni-helper/example/com_fireflysource_jni_example_JniExample.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class com_fireflysource_jni_example_JniExample */ - -#ifndef _Included_com_fireflysource_jni_example_JniExample -#define _Included_com_fireflysource_jni_example_JniExample -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: com_fireflysource_jni_example_JniExample - * Method: sayHello - * Signature: (Ljava/lang/String;)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_com_fireflysource_jni_example_JniExample_sayHello - (JNIEnv *, jclass, jstring); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/firefly-jni-helper/src/main/java/com/fireflysource/doc/FeignedJniDoc.java b/firefly-jni-helper/src/main/java/com/fireflysource/doc/FeignedJniDoc.java deleted file mode 100644 index ee78970d3..000000000 --- a/firefly-jni-helper/src/main/java/com/fireflysource/doc/FeignedJniDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedJniDoc { -} diff --git a/firefly-jni-helper/src/main/java/com/fireflysource/jni/example/JniExample.java b/firefly-jni-helper/src/main/java/com/fireflysource/jni/example/JniExample.java deleted file mode 100644 index cf57b9a13..000000000 --- a/firefly-jni-helper/src/main/java/com/fireflysource/jni/example/JniExample.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fireflysource.jni.example; - -import com.fireflysource.common.jni.JniLibLoader; - -public class JniExample { - - static { - JniLibLoader.load("jni_helper_example"); - } - - public static native String sayHello(String s); -} diff --git a/firefly-jni-helper/src/test/java/com/fireflysource/jni/example/TestJniExample.java b/firefly-jni-helper/src/test/java/com/fireflysource/jni/example/TestJniExample.java deleted file mode 100644 index 750a5873a..000000000 --- a/firefly-jni-helper/src/test/java/com/fireflysource/jni/example/TestJniExample.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.jni.example; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class TestJniExample { - - @Test - @EnabledOnOs({OS.MAC, OS.LINUX, OS.WINDOWS}) - void testSayHello() { - String result = JniExample.sayHello("欢迎!"); - System.out.println(result); - assertEquals("Bonjour, 欢迎!", result); - } -} diff --git a/firefly-jni-helper/src/test/resources/firefly-log.xml b/firefly-jni-helper/src/test/resources/firefly-log.xml deleted file mode 100644 index d139af581..000000000 --- a/firefly-jni-helper/src/test/resources/firefly-log.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - firefly-system - INFO - ${log.path} - false - - - - firefly-monitor - INFO - ${log.path} - - - diff --git a/firefly-jni-helper/src/test/resources/lib/linux/libjni_helper_example.so b/firefly-jni-helper/src/test/resources/lib/linux/libjni_helper_example.so deleted file mode 100755 index 30e5e759c..000000000 Binary files a/firefly-jni-helper/src/test/resources/lib/linux/libjni_helper_example.so and /dev/null differ diff --git a/firefly-jni-helper/src/test/resources/lib/macos/libjni_helper_example.dylib b/firefly-jni-helper/src/test/resources/lib/macos/libjni_helper_example.dylib deleted file mode 100755 index 4c16b964a..000000000 Binary files a/firefly-jni-helper/src/test/resources/lib/macos/libjni_helper_example.dylib and /dev/null differ diff --git a/firefly-jni-helper/src/test/resources/lib/windows/libjni_helper_example.dll b/firefly-jni-helper/src/test/resources/lib/windows/libjni_helper_example.dll deleted file mode 100755 index ae960fb93..000000000 Binary files a/firefly-jni-helper/src/test/resources/lib/windows/libjni_helper_example.dll and /dev/null differ diff --git a/firefly-net/pom.xml b/firefly-net/pom.xml deleted file mode 100644 index cdf092e95..000000000 --- a/firefly-net/pom.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-net - jar - - firefly-net - http://www.fireflysource.com - - - - com.fireflysource - firefly-common - - - - org.conscrypt - conscrypt-openjdk-uber - - - - org.openjsse - openjsse - - - - org.wildfly.openssl - wildfly-openssl - - - - com.fireflysource - firefly-slf4j - test - - - - - firefly-net - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.xml - **/*.properties - - - **/*.jks - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/test/resources - false - - **/*.xml - **/*.properties - - - **/*.ico - **/*.html - **/*.txt - **/*.MockMaker - - - - - diff --git a/firefly-net/src/main/java/com/fireflysource/$.java b/firefly-net/src/main/java/com/fireflysource/$.java deleted file mode 100644 index 11142a454..000000000 --- a/firefly-net/src/main/java/com/fireflysource/$.java +++ /dev/null @@ -1,351 +0,0 @@ -package com.fireflysource; - -import com.fireflysource.common.concurrent.CompletableFutures; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.CommonTcpChannelGroup; -import com.fireflysource.net.http.client.HttpClient; -import com.fireflysource.net.http.client.HttpClientFactory; -import com.fireflysource.net.http.common.HttpConfig; -import com.fireflysource.net.http.server.HttpProxy; -import com.fireflysource.net.http.server.HttpServer; -import com.fireflysource.net.http.server.HttpServerFactory; -import com.fireflysource.net.tcp.TcpClient; -import com.fireflysource.net.tcp.TcpClientFactory; -import com.fireflysource.net.tcp.TcpServer; -import com.fireflysource.net.tcp.TcpServerFactory; -import com.fireflysource.net.tcp.aio.TcpConfig; -import com.fireflysource.net.websocket.client.WebSocketClientConnectionBuilder; - -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** - * The Firefly functions start from here. - * - * @author Pengtao Qiu - */ -public interface $ { - - /** - * Get the HTTP client. It is singleton. The client uses the common tcp channel group. - * - * @return The HTTP client. - */ - static HttpClient httpClient() { - return CommonTcpChannelGroup.INSTANCE.getHttpClient(); - } - - /** - * Create a websocket client connection builder. - * - * @param url The websocket url. - * @return The websocket client connection builder. - */ - static WebSocketClientConnectionBuilder websocket(String url) { - return httpClient().websocket(url); - } - - /** - * Create a websocket client connection builder. - * - * @return The websocket client connection builder. - */ - static WebSocketClientConnectionBuilder websocket() { - return httpClient().websocket(); - } - - /** - * Create the HTTP client. The client uses the common tcp channel group. - * - * @param httpConfig The HTTP config. - * @return The HTTP client. - */ - static HttpClient httpClient(HttpConfig httpConfig) { - return CommonTcpChannelGroup.INSTANCE.createHttpClient(httpConfig); - } - - /** - * Create a new HTTP client. - * - * @return The HTTP client. - */ - static HttpClient createHttpClient() { - return HttpClientFactory.create(); - } - - /** - * Create a new HTTP client. - * - * @param httpConfig The HTTP config. - * @return The HTTP client. - */ - static HttpClient createHttpClient(HttpConfig httpConfig) { - return HttpClientFactory.create(httpConfig); - } - - /** - * Create the HTTP server. It uses the common tcp channel group. - * - * @return The HTTP server. - */ - static HttpServer httpServer() { - return CommonTcpChannelGroup.INSTANCE.createHttpServer(); - } - - /** - * Create the HTTP server. It uses the common tcp channel group. - * - * @param httpConfig The HTTP config. - * @return The HTTP server. - */ - static HttpServer httpServer(HttpConfig httpConfig) { - return CommonTcpChannelGroup.INSTANCE.createHttpServer(httpConfig); - } - - /** - * Create a new HTTP server. - * - * @return The HTTP server. - */ - static HttpServer createHttpServer() { - return HttpServerFactory.create(); - } - - /** - * Create a new HTTP server. - * - * @param httpConfig The HTTP config. - * @return The HTTP server. - */ - static HttpServer createHttpServer(HttpConfig httpConfig) { - return HttpServerFactory.create(httpConfig); - } - - /** - * Create a new HTTP proxy. - * - * @return The HTTP proxy. - */ - static HttpProxy createHttpProxy() { - return HttpServerFactory.createHttpProxy(); - } - - /** - * Create a new HTTP proxy. - * - * @param httpConfig The HTTP config. - * @return The HTTP proxy. - */ - static HttpProxy createHttpProxy(HttpConfig httpConfig) { - return HttpServerFactory.createHttpProxy(httpConfig); - } - - /** - * Create the TCP client. It uses the common tcp channel group. - * - * @return The TCP client. - */ - static TcpClient tcpClient() { - return CommonTcpChannelGroup.INSTANCE.createTcpClient(); - } - - /** - * Create the TCP client. It uses the common tcp channel group. - * - * @param tcpConfig The TCP config. - * @return The TCP client. - */ - static TcpClient tcpClient(TcpConfig tcpConfig) { - return CommonTcpChannelGroup.INSTANCE.createTcpClient(tcpConfig); - } - - /** - * Create a new TCP client. - * - * @return The TCP client. - */ - static TcpClient createTcpClient() { - return TcpClientFactory.create(); - } - - /** - * Create a new TCP client. - * - * @param tcpConfig The TCP config. - * @return The TCP client. - */ - static TcpClient createTcpClient(TcpConfig tcpConfig) { - return TcpClientFactory.create(tcpConfig); - } - - /** - * Create the TCP server. It uses the common tcp channel group. - * - * @return The TCP server. - */ - static TcpServer tcpServer() { - return CommonTcpChannelGroup.INSTANCE.createTcpServer(); - } - - /** - * Create the TCP server. It uses the common tcp channel group. - * - * @param tcpConfig The TCP config. - * @return The TCP server. - */ - static TcpServer tcpServer(TcpConfig tcpConfig) { - return CommonTcpChannelGroup.INSTANCE.createTcpServer(tcpConfig); - } - - /** - * Create a new TCP server. - * - * @return The TCP server. - */ - static TcpServer createTcpServer() { - return TcpServerFactory.create(); - } - - /** - * Create a new TCP server. - * - * @param tcpConfig The TCP config. - * @return The TCP server. - */ - static TcpServer createTcpServer(TcpConfig tcpConfig) { - return TcpServerFactory.create(tcpConfig); - } - - /** - * The logger functions. - */ - interface logger { - /** - * Create a lazy logger. - * - * @param name The logger name. - * @return The lazy logger. - */ - static LazyLogger create(String name) { - return LazyLogger.create(name); - } - - /** - * Create a lazy logger. - * - * @param clazz The class name as the logger name. - * @return The lazy logger. - */ - static LazyLogger create(Class clazz) { - return LazyLogger.create(clazz); - } - } - - /** - * The future functions. - */ - interface future { - - /** - * Done future. - * - * @return The done future. - */ - static CompletableFuture done() { - return Result.DONE; - } - - /** - * Done future. - * - * @param future The future. - */ - static void done(CompletableFuture future) { - Result.done(future); - } - - /** - * Create a failed future. - * - * @param t The exception. - * @param The future item type. - * @return The future. - */ - static CompletableFuture failedFuture(Throwable t) { - return CompletableFutures.failedFuture(t); - } - - /** - * Retry the async operation. - * - * @param retryCount The max retry times. - * @param supplier The async operation function. - * @param prepareRetry The callback before retries async operation. - * @param The future result type. - * @return The operation result future. - */ - static CompletableFuture retry(int retryCount, Supplier> supplier, BiConsumer prepareRetry) { - return CompletableFutures.retry(retryCount, supplier, prepareRetry); - } - - } - - /** - * The consumer functions. - */ - interface consumer { - - /** - * Discard the result. - * - * @param The result type. - * @return The consumer that discards the result. - */ - static Consumer> discard() { - return Result.discard(); - } - - /** - * Convert future to the result consumer. - * - * @param future The future. - * @param The result type. - * @return The result consumer. - */ - static Consumer> futureToConsumer(CompletableFuture future) { - return Result.futureToConsumer(future); - } - - /** - * The empty consumer. - * - * @param The consumer item type. - * @return The empty consumer. - */ - static Consumer emptyConsumer() { - return Result.emptyConsumer(); - } - - /** - * Create the failed result. - * - * @param t The exception. - * @return The failed result. - */ - static Result createFailedResult(Throwable t) { - return Result.createFailedResult(t); - } - - /** - * The success result. - * - * @return The success result. - */ - static Result success() { - return Result.SUCCESS; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/doc/FeignedNetDoc.java b/firefly-net/src/main/java/com/fireflysource/doc/FeignedNetDoc.java deleted file mode 100644 index dfa811962..000000000 --- a/firefly-net/src/main/java/com/fireflysource/doc/FeignedNetDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedNetDoc { -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/AbstractConnection.java b/firefly-net/src/main/java/com/fireflysource/net/AbstractConnection.java deleted file mode 100644 index f73d39244..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/AbstractConnection.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.fireflysource.net; - -abstract public class AbstractConnection implements Connection { - - protected final int id; - protected final long openTime; - protected final long maxIdleTime; - protected volatile Object attachment; - protected long closeTime; - protected long lastReadTime; - protected long lastWrittenTime; - protected long readBytes; - protected long writtenBytes; - - public AbstractConnection(int id, long openTime, long maxIdleTime) { - this.id = id; - this.openTime = openTime; - this.maxIdleTime = maxIdleTime; - } - - @Override - public Object getAttachment() { - return attachment; - } - - @Override - public void setAttachment(Object attachment) { - this.attachment = attachment; - } - - @Override - public int getId() { - return id; - } - - @Override - public long getOpenTime() { - return openTime; - } - - @Override - public long getCloseTime() { - return closeTime; - } - - @Override - public long getLastReadTime() { - return lastReadTime; - } - - @Override - public long getLastWrittenTime() { - return lastWrittenTime; - } - - @Override - public long getReadBytes() { - return readBytes; - } - - @Override - public long getWrittenBytes() { - return writtenBytes; - } - - @Override - public long getLastActiveTime() { - return Math.max(lastReadTime, lastWrittenTime); - } - - @Override - public long getIdleTime() { - return System.currentTimeMillis() - getLastActiveTime(); - } - - @Override - public long getMaxIdleTime() { - return maxIdleTime; - } - - @Override - public long getDuration() { - if (isClosed()) { - return closeTime - openTime; - } else { - return System.currentTimeMillis() - openTime; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/Connection.java b/firefly-net/src/main/java/com/fireflysource/net/Connection.java deleted file mode 100644 index 3c4086eaf..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/Connection.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.net; - -import com.fireflysource.common.io.AsyncCloseable; - -import java.net.InetSocketAddress; - -/** - * @author Pengtao Qiu - */ -public interface Connection extends AsyncCloseable { - - Object getAttachment(); - - void setAttachment(Object object); - - int getId(); - - long getOpenTime(); - - long getCloseTime(); - - long getDuration(); - - long getLastReadTime(); - - long getLastWrittenTime(); - - long getLastActiveTime(); - - long getReadBytes(); - - long getWrittenBytes(); - - long getIdleTime(); - - long getMaxIdleTime(); - - boolean isClosed(); - - InetSocketAddress getLocalAddress(); - - InetSocketAddress getRemoteAddress(); - - boolean isInvalid(); - - void setWriteTimeout(long timeout); - - void setReadTimeout(long timeout); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClient.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClient.java deleted file mode 100644 index 97f8e6db3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClient.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.common.lifecycle.LifeCycle; -import com.fireflysource.net.http.common.model.HttpURI; -import com.fireflysource.net.websocket.client.WebSocketClientConnectionBuilder; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * @author Pengtao Qiu - */ -public interface HttpClient extends HttpClientRequestBuilderFactory, LifeCycle { - - /** - * Create a new HTTP client connection. - * - * @param httpURI The HTTP URI. - * @param supportedProtocols The supported application protocols. - * @return The new HTTP client connection. - */ - CompletableFuture createHttpClientConnection(HttpURI httpURI, List supportedProtocols); - - /** - * Create a new HTTP client connection. - * - * @param httpURI The HTTP URI. - * @return The new HTTP client connection. - */ - default CompletableFuture createHttpClientConnection(HttpURI httpURI) { - return createHttpClientConnection(httpURI, Collections.emptyList()); - } - - /** - * Create a new HTTP client connection. - * - * @param uri The HTTP URI. - * @return The new HTTP client connection. - */ - default CompletableFuture createHttpClientConnection(String uri) { - return createHttpClientConnection(new HttpURI(uri)); - } - - /** - * Create a websocket connection builder. - * - * @return The websocket connection builder. - */ - WebSocketClientConnectionBuilder websocket(); - - /** - * Create a websocket connection builder. - * - * @param url The websocket url. - * @return The websocket connection builder. - */ - WebSocketClientConnectionBuilder websocket(String url); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientConnection.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientConnection.java deleted file mode 100644 index a72f1e118..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientConnection.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.HttpConnection; - -import java.util.concurrent.CompletableFuture; - -/** - * The HTTP connection manager creates the HTTP client connection. - * If the TLS is enabled, the connection uses the ALPN to decide the HTTP version, - * or else the connection uses the HTTP Upgrade mechanism to decide the HTTP version. - * The HTTP2 is the default preferred protocol. - * - * @author Pengtao Qiu - */ -public interface HttpClientConnection extends HttpConnection, HttpClientRequestBuilderFactory { - - /** - * Send HTTP request to the remote endpoint. - * - * @param request The HTTP request. - * @return The response future. - */ - CompletableFuture send(HttpClientRequest request); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientConnectionManager.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientConnectionManager.java deleted file mode 100644 index 536ec35d6..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientConnectionManager.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.common.lifecycle.LifeCycle; -import com.fireflysource.net.http.common.model.HttpURI; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The HTTP client connection manager maintains the HTTP client connections. - * If it creates connection and negotiates the HTTP version is 2.0, and it maintains only one connection to send the request and receive the response. - * If the HTTP version is 1.1, it maintains the HTTP connections in a pool. - * - * @author Pengtao Qiu - */ -public interface HttpClientConnectionManager extends LifeCycle { - - /** - * Send HTTP request to the server using the connection pool. - * - * @param request The HTTP request. - * @return The HTTP response. - */ - CompletableFuture send(HttpClientRequest request); - - /** - * Create a new HTTP client connection. - * - * @param httpURI The server URI. - * @return The new HTTP client connection. - */ - default CompletableFuture createHttpClientConnection(HttpURI httpURI) { - return createHttpClientConnection(httpURI, Collections.emptyList()); - } - - /** - * Create a new HTTP client connection. - * - * @param httpURI The HTTP URI. - * @param supportedProtocols The supported application protocols. - * @return The new HTTP client connection. - */ - CompletableFuture createHttpClientConnection(HttpURI httpURI, List supportedProtocols); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentHandler.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentHandler.java deleted file mode 100644 index 493d0e0e0..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.content.handler.HttpContentHandler; - -/** - * @author Pengtao Qiu - */ -public interface HttpClientContentHandler extends HttpContentHandler { -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentHandlerFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentHandlerFactory.java deleted file mode 100644 index 8e28bcf1a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentHandlerFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.client.impl.content.handler.ByteBufferContentHandler; -import com.fireflysource.net.http.client.impl.content.handler.FileContentHandler; -import com.fireflysource.net.http.client.impl.content.handler.StringContentHandler; - -import java.nio.file.OpenOption; -import java.nio.file.Path; - -abstract public class HttpClientContentHandlerFactory { - - public static HttpClientContentHandler bytesHandler() { - return new ByteBufferContentHandler(); - } - - public static HttpClientContentHandler bytesHandler(long maxRequestBodySize) { - return new ByteBufferContentHandler(maxRequestBodySize); - } - - public static HttpClientContentHandler stringHandler() { - return new StringContentHandler(); - } - - public static HttpClientContentHandler stringHandler(long maxRequestBodySize) { - return new StringContentHandler(maxRequestBodySize); - } - - public static HttpClientContentHandler fileHandler(Path path, OpenOption... openOptions) { - return new FileContentHandler(path, openOptions); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentProvider.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentProvider.java deleted file mode 100644 index 05d798ccc..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.content.provider.HttpContentProvider; - -import java.nio.ByteBuffer; - -/** - * @author Pengtao Qiu - */ -public interface HttpClientContentProvider extends HttpContentProvider { - - /** - * The content length. If the length is -1, the content is the data stream. - * - * @return The content length. - */ - long length(); - - /** - * Convert fixed length content to a ByteBuffer. If the content is the data stream, return an empty ByteBuffer. - * - * @return The ByteBuffer. - */ - ByteBuffer toByteBuffer(); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentProviderFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentProviderFactory.java deleted file mode 100644 index be5bd74db..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientContentProviderFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.client.impl.content.provider.ByteBufferContentProvider; -import com.fireflysource.net.http.client.impl.content.provider.FileContentProvider; -import com.fireflysource.net.http.client.impl.content.provider.StringContentProvider; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Set; - -abstract public class HttpClientContentProviderFactory { - - public static HttpClientContentProvider bytesBody(ByteBuffer buffer) { - return new ByteBufferContentProvider(buffer); - } - - public static HttpClientContentProvider stringBody(String string) { - return new StringContentProvider(string, StandardCharsets.UTF_8); - } - - public static HttpClientContentProvider stringBody(String string, Charset charset) { - return new StringContentProvider(string, charset); - } - - public static HttpClientContentProvider fileBody(Path path, OpenOption... openOptions) { - return new FileContentProvider(path, openOptions); - } - - public static HttpClientContentProvider fileBody(Path path, Set openOptions, long position, long length) { - return new FileContentProvider(path, openOptions, position, length); - } - - public static HttpClientContentProvider resourceFileBody(String resourcePath, OpenOption... openOptions) { - URL resource = FileContentProvider.class.getClassLoader().getResource(resourcePath); - try { - if (resource != null) { - URI uri = resource.toURI(); - return fileBody(Paths.get(uri), openOptions); - } else { - return null; - } - } catch (URISyntaxException e) { - return null; - } - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientFactory.java deleted file mode 100644 index 71d94379d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.client.impl.AsyncHttpClient; -import com.fireflysource.net.http.common.HttpConfig; - -abstract public class HttpClientFactory { - - public static HttpClient create(HttpConfig config) { - return new AsyncHttpClient(config); - } - - public static HttpClient create() { - return new AsyncHttpClient(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequest.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequest.java deleted file mode 100644 index 1c6e65f8d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequest.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.codec.UrlEncoded; -import com.fireflysource.net.http.common.model.Cookie; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpURI; -import com.fireflysource.net.http.common.model.HttpVersion; - -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -/** - * The HTTP client request. - * - * @author Pengtao Qiu - */ -public interface HttpClientRequest { - - /** - * Get the HTTP method. - * - * @return The HTTP method. - */ - String getMethod(); - - /** - * Set the HTTP method. - * - * @param method The HTTP method. - */ - void setMethod(String method); - - /** - * Get the HTTP URI. - * - * @return The HTTP URI. - */ - HttpURI getURI(); - - /** - * Set the HTTP URI. - * - * @param uri The HTTP URI. - */ - void setURI(HttpURI uri); - - /** - * Get the HTTP version. - * - * @return The HTTP version. - */ - HttpVersion getHttpVersion(); - - /** - * Set the HTTP version. - * - * @param httpVersion The HTTP version. - */ - void setHttpVersion(HttpVersion httpVersion); - - /** - * Get URL query strings. - * - * @return The URL query strings. - */ - UrlEncoded getQueryStrings(); - - /** - * Set URL query strings. - * - * @param queryStrings The URL query strings. - */ - void setQueryStrings(UrlEncoded queryStrings); - - /** - * Get the web form inputs. - * - * @return The web form inputs. - */ - UrlEncoded getFormInputs(); - - /** - * Set the web form inputs - * - * @param formInputs The web form inputs. - */ - void setFormInputs(UrlEncoded formInputs); - - /** - * Get the HTTP header fields. - * - * @return The HTTP header fields. - */ - HttpFields getHttpFields(); - - /** - * Set the HTTP header fields. - * - * @param httpFields The HTTP header fields. - */ - void setHttpFields(HttpFields httpFields); - - /** - * Get the HTTP cookies. - * - * @return The HTTP cookies. - */ - List getCookies(); - - /** - * Set the HTTP cookies. - * - * @param cookies The HTTP cookies. - */ - void setCookies(List cookies); - - /** - * Get the HTTP trailers. - * - * @return The HTTP trailers. - */ - Supplier getTrailerSupplier(); - - /** - * Set the HTTP trailers. - * - * @param trailerSupplier The HTTP trailers. - */ - void setTrailerSupplier(Supplier trailerSupplier); - - /** - * Set the content provider. When you submit the request, the HTTP client will send the data that read from the content provider. - * - * @param contentProvider When you submit the request, the HTTP client will send the data that read from the content provider. - */ - void setContentProvider(HttpClientContentProvider contentProvider); - - /** - * Get the content provider. - * - * @return the content provider. - */ - HttpClientContentProvider getContentProvider(); - - /** - * Set the HTTP content receiving handler. - * - * @param contentHandler The HTTP content receiving handler. When the HTTP client receives the HTTP body data, - * it will execute this action. It be executed many times. - */ - void setContentHandler(HttpClientContentHandler contentHandler); - - /** - * Get the HTTP content receiving callback. - * - * @return The HTTP content receiving callback. When the HTTP client receives the HTTP body data, - * it will execute this action. It will be executed many times. - */ - HttpClientContentHandler getContentHandler(); - - /** - * Set the HTTP2 settings. - * - * @param settings The HTTP2 settings. - */ - void setHttp2Settings(Map settings); - - /** - * Get the HTTP2 settings. - * - * @return The HTTP2 settings. - */ - Map getHttp2Settings(); - - /** - * Set the HTTP header response complete callback. - * - * @param headerComplete The HTTP header response complete callback. - */ - void setHeaderComplete(BiConsumer headerComplete); - - /** - * Get the HTTP header response complete callback. - * - * @return The HTTP header response complete callback. - */ - BiConsumer getHeaderComplete(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequestBuilder.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequestBuilder.java deleted file mode 100644 index ab6602c05..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequestBuilder.java +++ /dev/null @@ -1,295 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.model.Cookie; -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpHeader; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -/** - * The HTTP client request builder. - * - * @author Pengtao Qiu - */ -public interface HttpClientRequestBuilder { - /** - * Set the cookies. - * - * @param cookies The cookies. - * @return RequestBuilder - */ - HttpClientRequestBuilder cookies(List cookies); - - /** - * Put an HTTP field. It will replace the existed field. - * - * @param name The field name. - * @param list The field values. - * @return RequestBuilder - */ - HttpClientRequestBuilder put(String name, List list); - - /** - * Put an HTTP field. It will replace the existed field. - * - * @param header The field name. - * @param value The value. - * @return RequestBuilder - */ - HttpClientRequestBuilder put(HttpHeader header, String value); - - /** - * Put an HTTP field. It will replace the existed field. - * - * @param name The field name. - * @param value The value. - * @return RequestBuilder - */ - HttpClientRequestBuilder put(String name, String value); - - /** - * Put an HTTP field. It will replace the existed field. - * - * @param field The HTTP field. - * @return RequestBuilder - */ - HttpClientRequestBuilder put(HttpField field); - - /** - * Add some HTTP fields. - * - * @param fields The HTTP fields. - * @return RequestBuilder - */ - HttpClientRequestBuilder addAll(HttpFields fields); - - /** - * Add an HTTP field. - * - * @param field The HTTP field. - * @return RequestBuilder - */ - HttpClientRequestBuilder add(HttpField field); - - /** - * Add the comma separated values, but only if not already present. - * - * @param header The header to add the value(s) to. - * @param values The value(s). - * @return RequestBuilder - */ - HttpClientRequestBuilder addCsv(HttpHeader header, String... values); - - /** - * Add the comma separated values, but only if not already present. - * - * @param header The header to add the value(s) to. - * @param values The value(s). - * @return RequestBuilder - */ - HttpClientRequestBuilder addCsv(String header, String... values); - - /** - * Set the HTTP trailers. - * - * @param trailerSupplier The HTTP trailers. - * @return RequestBuilder - */ - HttpClientRequestBuilder trailerSupplier(Supplier trailerSupplier); - - /** - * Set the text HTTP body data. - * - * @param content The text HTTP body data. - * @return RequestBuilder - */ - HttpClientRequestBuilder body(String content); - - /** - * Set the text HTTP body data. - * - * @param content The text HTTP body data. - * @param charset THe charset of the text. - * @return RequestBuilder - */ - HttpClientRequestBuilder body(String content, Charset charset); - - /** - * Set the HTTP body data. When you submit the request, the data will be sent. - * - * @param buffer The HTTP body data. - * @return RequestBuilder - */ - HttpClientRequestBuilder body(ByteBuffer buffer); - - /** - * Set the content provider. When you submit the request, the HTTP client will send the data that read from the content provider. - * - * @param contentProvider When you submit the request, the HTTP client will send the data that read from the content provider. - * @return RequestBuilder - */ - HttpClientRequestBuilder contentProvider(HttpClientContentProvider contentProvider); - - /** - * Add a multi-part mime content. Such as a file. - * - * @param name The content name. - * @param content Content provider. - * @param fields Multi-part content header fields. - * @return RequestBuilder - */ - HttpClientRequestBuilder addPart(String name, HttpClientContentProvider content, HttpFields fields); - - /** - * Add a multi-part mime content. Such as a file. - * - * @param name The content name. - * @param fileName The content file name. - * @param content Content provider. - * @param fields Multi-part content header fields. - * @return RequestBuilder - */ - HttpClientRequestBuilder addFilePart(String name, String fileName, HttpClientContentProvider content, HttpFields fields); - - /** - * Add a web form input. - * - * @param name Input name. - * @param value Input value. - * @return RequestBuilder - */ - HttpClientRequestBuilder addFormInput(String name, String value); - - /** - * Add web form inputs. - * - * @param name Input name. - * @param values Input values. - * @return RequestBuilder - */ - HttpClientRequestBuilder addFormInputs(String name, List values); - - /** - * Put a web form input. - * - * @param name Input name. - * @param value Input value. - * @return RequestBuilder - */ - HttpClientRequestBuilder putFormInput(String name, String value); - - /** - * Put a web form inputs. - * - * @param name Input name. - * @param values Input values. - * @return RequestBuilder - */ - HttpClientRequestBuilder putFormInputs(String name, List values); - - /** - * Remove web form input. - * - * @param name Input name. - * @return RequestBuilder - */ - HttpClientRequestBuilder removeFormInput(String name); - - /** - * Add a value in an existed URL query strings. - * - * @param name The parameter name. - * @param value The value. - * @return RequestBuilder - */ - HttpClientRequestBuilder addQueryString(String name, String value); - - /** - * Add some values in an existed URL query strings. - * - * @param name The parameter name. - * @param values The parameter values. - * @return RequestBuilder - */ - HttpClientRequestBuilder addQueryStrings(String name, List values); - - /** - * Put a value in the URL query strings. - * - * @param name The parameter name. - * @param value The value. - * @return RequestBuilder - */ - HttpClientRequestBuilder putQueryString(String name, String value); - - /** - * Put a value in the URL query strings. - * - * @param name The parameter name. - * @param values The parameter values. - * @return RequestBuilder - */ - HttpClientRequestBuilder putQueryStrings(String name, List values); - - /** - * Remove a parameter in the URL query strings. - * - * @param name The parameter name. - * @return RequestBuilder - */ - HttpClientRequestBuilder removeQueryString(String name); - - /** - * Set the HTTP content receiving callback. - * - * @param contentHandler The HTTP content receiving callback. When the HTTP client receives the HTTP body data, - * it will execute this action. This action will be executed many times. - * @return RequestBuilder - */ - HttpClientRequestBuilder contentHandler(HttpClientContentHandler contentHandler); - - /** - * Set the HTTP2 settings. - * - * @param http2Settings The HTTP2 settings. - * @return RequestBuilder - */ - HttpClientRequestBuilder http2Settings(Map http2Settings); - - /** - * Try to upgrade HTTP2 protocol via the Upgrade header (h2c). - * - * @return RequestBuilder - */ - HttpClientRequestBuilder upgradeHttp2(); - - /** - * Set the HTTP header response complete callback. - * - * @param headerComplete The HTTP header response complete callback. - * @return RequestBuilder - */ - HttpClientRequestBuilder onHeaderComplete(BiConsumer headerComplete); - - /** - * Submit the HTTP request to the server using the connection pool. - * The HTTP client manages HTTP connection automatically. - * - * @return The HTTP response. - */ - CompletableFuture submit(); - - /** - * Get the current HTTP client request. - * - * @return The current HTTP client request. - */ - HttpClientRequest getHttpClientRequest(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequestBuilderFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequestBuilderFactory.java deleted file mode 100644 index 6e9b1a991..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientRequestBuilderFactory.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.exception.URISyntaxRuntimeException; -import com.fireflysource.net.http.common.model.HttpMethod; -import com.fireflysource.net.http.common.model.HttpURI; - -import java.net.URISyntaxException; -import java.net.URL; - -/** - * Create a new HTTP client request builder. - */ -public interface HttpClientRequestBuilderFactory { - - /** - * Create a RequestBuilder with GET method and URL. - * - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder get(String url) { - return request(HttpMethod.GET, url); - } - - /** - * Create a RequestBuilder with POST method and URL. - * - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder post(String url) { - return request(HttpMethod.POST, url); - } - - /** - * Create a RequestBuilder with HEAD method and URL. - * - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder head(String url) { - return request(HttpMethod.HEAD, url); - } - - /** - * Create a RequestBuilder with PUT method and URL. - * - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder put(String url) { - return request(HttpMethod.PUT, url); - } - - /** - * Create a RequestBuilder with DELETE method and URL. - * - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder delete(String url) { - return request(HttpMethod.DELETE, url); - } - - /** - * Create a RequestBuilder with HTTP method and URL. - * - * @param method The HTTP method. - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder request(HttpMethod method, String url) { - return request(method.getValue(), url); - } - - /** - * Create a RequestBuilder with HTTP method and URL. - * - * @param method The HTTP method. - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder request(String method, String url) { - return request(method, new HttpURI(url)); - } - - /** - * Create a RequestBuilder with HTTP method and URL. - * - * @param method The HTTP method. - * @param url The request URL. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - default HttpClientRequestBuilder request(String method, URL url) { - try { - return request(method, new HttpURI(url.toURI())); - } catch (URISyntaxException e) { - throw new URISyntaxRuntimeException("URI syntax error", e); - } - } - - /** - * Create a RequestBuilder with HTTP method and URL. - * - * @param method The HTTP method. - * @param httpURI The HTTP URI. - * @return A new RequestBuilder that helps you to build an HTTP request. - */ - HttpClientRequestBuilder request(String method, HttpURI httpURI); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientResponse.java b/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientResponse.java deleted file mode 100644 index b007a5aac..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/client/HttpClientResponse.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.fireflysource.net.http.client; - -import com.fireflysource.net.http.common.model.Cookie; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpVersion; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; -import java.util.function.Supplier; - -/** - * @author Pengtao Qiu - */ -public interface HttpClientResponse { - - /** - * Get the HTTP response status code. - * - * @return The HTTP response status code. - */ - int getStatus(); - - /** - * Get the textual description associated with the numeric status code. - * - * @return The textual description associated with the numeric status code. - */ - String getReason(); - - /** - * Get the HTTP version of the current HTTP connection. - * - * @return The HTTP version of the current HTTP connection. - */ - HttpVersion getHttpVersion(); - - /** - * Get the HTTP header fields. - * - * @return The HTTP header fields. - */ - HttpFields getHttpFields(); - - /** - * Get the cookies. - * - * @return The cookies. - */ - List getCookies(); - - /** - * Get the content length. - * - * @return The content length. - */ - long getContentLength(); - - /** - * Get the HTTP trailer fields. - * - * @return The HTTP trailer fields. - */ - Supplier getTrailerSupplier(); - - /** - * Get the HTTP body and convert it to the UTF-8 string. - * - * @return The HTTP body string. - */ - String getStringBody(); - - /** - * Get the HTTP body and convert the specified charset string. - * - * @param charset The charset of the HTTP body string. - * @return The HTTP body string. - */ - String getStringBody(Charset charset); - - /** - * Get the HTTP body raw binary data. - * - * @return The HTTP body raw binary data. - */ - List getBody(); - - /** - * Set the HTTP body content handler. - * - * @param contentHandler The HTTP body content handler. - */ - void setContentHandler(HttpClientContentHandler contentHandler); - - /** - * Get the HTTP body content handler. - * - * @return The HTTP body content handler. - */ - HttpClientContentHandler getContentHandler(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/HttpConfig.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/HttpConfig.java deleted file mode 100644 index ada039e76..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/HttpConfig.java +++ /dev/null @@ -1,251 +0,0 @@ -package com.fireflysource.net.http.common; - -import com.fireflysource.common.coroutine.CoroutineDispatchers; -import com.fireflysource.net.tcp.TcpChannelGroup; -import com.fireflysource.net.tcp.secure.SecureEngineFactory; - -public class HttpConfig implements Cloneable { - - public static int DEFAULT_WINDOW_SIZE = 65535; - public static int DEFAULT_HEADER_TABLE_SIZE = 4096; - - // TCP config - private TcpChannelGroup tcpChannelGroup; - private boolean stopTcpChannelGroup = true; - private long timeout = 30; - private SecureEngineFactory secureEngineFactory; - - // HTTP common config - private int headerBufferSize = 4 * 1024; - private int contentBufferSize = 16 * 1024; - private int maxDynamicTableSize = DEFAULT_HEADER_TABLE_SIZE; - private int maxHeaderSize = 32 * 1024; - private int maxHeaderBlockFragment = 0; - private int initialStreamRecvWindow = 8 * 1024 * 1024; - private int maxConcurrentStreams = -1; - private int initialSessionRecvWindow = 16 * 1024 * 1024; - private long streamIdleTimeout = 0; - - // HTTP client config - private int clientRetryCount = CoroutineDispatchers.INSTANCE.getDefaultPoolSize(); - private int connectionPoolSize = CoroutineDispatchers.INSTANCE.getDefaultPoolSize(); - private long checkConnectionLiveInterval = 15; - private boolean autoGeneratedClientHttp1Headers = true; - private long waitResponse100ContinueTimeout = 5; - private ProxyConfig proxyConfig; - - // HTTP server config - private long maxUploadFileSize = 200 * 1024 * 1024; - private long maxRequestBodySize = (4 * 1024) + (200 * 1024 * 1024); - private int uploadFileSizeThreshold = 4 * 1024 * 1024; - - // HTTP proxy config - private long httpProxyBodySizeThreshold = 10 * 1024 * 1024; - - - public TcpChannelGroup getTcpChannelGroup() { - return tcpChannelGroup; - } - - public void setTcpChannelGroup(TcpChannelGroup tcpChannelGroup) { - this.tcpChannelGroup = tcpChannelGroup; - } - - public boolean isStopTcpChannelGroup() { - return stopTcpChannelGroup; - } - - public void setStopTcpChannelGroup(boolean stopTcpChannelGroup) { - this.stopTcpChannelGroup = stopTcpChannelGroup; - } - - public long getTimeout() { - return timeout; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - public int getConnectionPoolSize() { - return connectionPoolSize; - } - - public void setConnectionPoolSize(int connectionPoolSize) { - this.connectionPoolSize = Math.max(connectionPoolSize, 1); - } - - public long getCheckConnectionLiveInterval() { - return checkConnectionLiveInterval; - } - - public void setCheckConnectionLiveInterval(long checkConnectionLiveInterval) { - this.checkConnectionLiveInterval = checkConnectionLiveInterval; - } - - public int getHeaderBufferSize() { - return headerBufferSize; - } - - public void setHeaderBufferSize(int headerBufferSize) { - this.headerBufferSize = headerBufferSize; - } - - public int getContentBufferSize() { - return contentBufferSize; - } - - public void setContentBufferSize(int contentBufferSize) { - this.contentBufferSize = contentBufferSize; - } - - public SecureEngineFactory getSecureEngineFactory() { - return secureEngineFactory; - } - - public void setSecureEngineFactory(SecureEngineFactory secureEngineFactory) { - this.secureEngineFactory = secureEngineFactory; - } - - public int getMaxDynamicTableSize() { - return maxDynamicTableSize; - } - - public void setMaxDynamicTableSize(int maxDynamicTableSize) { - this.maxDynamicTableSize = maxDynamicTableSize; - } - - public int getMaxHeaderSize() { - return maxHeaderSize; - } - - public void setMaxHeaderSize(int maxHeaderSize) { - this.maxHeaderSize = maxHeaderSize; - } - - public int getMaxHeaderBlockFragment() { - return maxHeaderBlockFragment; - } - - public void setMaxHeaderBlockFragment(int maxHeaderBlockFragment) { - this.maxHeaderBlockFragment = maxHeaderBlockFragment; - } - - public int getInitialStreamRecvWindow() { - return initialStreamRecvWindow; - } - - public void setInitialStreamRecvWindow(int initialStreamRecvWindow) { - this.initialStreamRecvWindow = initialStreamRecvWindow; - } - - public int getMaxConcurrentStreams() { - return maxConcurrentStreams; - } - - public void setMaxConcurrentStreams(int maxConcurrentStreams) { - this.maxConcurrentStreams = maxConcurrentStreams; - } - - public int getInitialSessionRecvWindow() { - return initialSessionRecvWindow; - } - - public void setInitialSessionRecvWindow(int initialSessionRecvWindow) { - this.initialSessionRecvWindow = initialSessionRecvWindow; - } - - public long getStreamIdleTimeout() { - return streamIdleTimeout; - } - - public void setStreamIdleTimeout(long streamIdleTimeout) { - this.streamIdleTimeout = streamIdleTimeout; - } - - public long getMaxUploadFileSize() { - return maxUploadFileSize; - } - - public void setMaxUploadFileSize(long maxUploadFileSize) { - this.maxUploadFileSize = maxUploadFileSize; - } - - public long getMaxRequestBodySize() { - return maxRequestBodySize; - } - - public void setMaxRequestBodySize(long maxRequestBodySize) { - this.maxRequestBodySize = maxRequestBodySize; - } - - public int getUploadFileSizeThreshold() { - return uploadFileSizeThreshold; - } - - public void setUploadFileSizeThreshold(int uploadFileSizeThreshold) { - this.uploadFileSizeThreshold = uploadFileSizeThreshold; - } - - public long getWaitResponse100ContinueTimeout() { - return waitResponse100ContinueTimeout; - } - - public void setWaitResponse100ContinueTimeout(long waitResponse100ContinueTimeout) { - this.waitResponse100ContinueTimeout = waitResponse100ContinueTimeout; - } - - public ProxyConfig getProxyConfig() { - return proxyConfig; - } - - public void setProxyConfig(ProxyConfig proxyConfig) { - this.proxyConfig = proxyConfig; - } - - public boolean isAutoGeneratedClientHttp1Headers() { - return autoGeneratedClientHttp1Headers; - } - - public void setAutoGeneratedClientHttp1Headers(boolean autoGeneratedClientHttp1Headers) { - this.autoGeneratedClientHttp1Headers = autoGeneratedClientHttp1Headers; - } - - public int getClientRetryCount() { - return clientRetryCount; - } - - public void setClientRetryCount(int clientRetryCount) { - this.clientRetryCount = clientRetryCount; - } - - public long getHttpProxyBodySizeThreshold() { - return httpProxyBodySizeThreshold; - } - - public void setHttpProxyBodySizeThreshold(long httpProxyBodySizeThreshold) { - this.httpProxyBodySizeThreshold = httpProxyBodySizeThreshold; - } - - @Override - public String toString() { - return "{" + - "timeout=" + timeout + - ", clientRetryCount=" + clientRetryCount + - ", connectionPoolSize=" + connectionPoolSize + - ", checkConnectionLiveInterval=" + checkConnectionLiveInterval + - ", maxUploadFileSize=" + maxUploadFileSize + - ", maxRequestBodySize=" + maxRequestBodySize + - ", uploadFileSizeThreshold=" + uploadFileSizeThreshold + - '}'; - } - - @Override - public HttpConfig clone() throws CloneNotSupportedException { - HttpConfig config = (HttpConfig) super.clone(); - if (this.proxyConfig != null) { - config.proxyConfig = (ProxyConfig) this.proxyConfig.clone(); - } - return config; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/HttpConnection.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/HttpConnection.java deleted file mode 100644 index afcb263b1..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/HttpConnection.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.net.http.common; - -import com.fireflysource.net.Connection; -import com.fireflysource.net.http.common.model.HttpVersion; -import com.fireflysource.net.tcp.TcpCoroutineDispatcher; - -/** - * The HTTP connection. - * - * @author Pengtao Qiu - */ -public interface HttpConnection extends Connection, TcpCoroutineDispatcher { - - /** - * Get the HTTP version. - * - * @return The HTTP version. - */ - HttpVersion getHttpVersion(); - - /** - * If you enable the TLS protocol, it returns true. - * - * @return If you enable the TLS protocol, it returns true. - */ - boolean isSecureConnection(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/ProxyAuthentication.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/ProxyAuthentication.java deleted file mode 100644 index b6dc4ae64..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/ProxyAuthentication.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.net.http.common; - -public class ProxyAuthentication implements Cloneable { - - private String username; - private String password; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Override - public String toString() { - return "ProxyAuthentication{" + - "username=******'" + '\'' + - ", password='******" + '\'' + - '}'; - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/ProxyConfig.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/ProxyConfig.java deleted file mode 100644 index 985daf0b2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/ProxyConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.fireflysource.net.http.common; - -public class ProxyConfig implements Cloneable { - - private String protocol; - private String host; - private int port; - private ProxyAuthentication proxyAuthentication; - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public ProxyAuthentication getProxyAuthentication() { - return proxyAuthentication; - } - - public void setProxyAuthentication(ProxyAuthentication proxyAuthentication) { - this.proxyAuthentication = proxyAuthentication; - } - - @Override - public String toString() { - return "ProxyConfig{" + - "protocol='" + protocol + '\'' + - ", host='" + host + '\'' + - ", port=" + port + - ", proxyAuthentication=" + proxyAuthentication + - '}'; - } - - @Override - protected Object clone() throws CloneNotSupportedException { - ProxyConfig proxyConfig = (ProxyConfig) super.clone(); - if (this.proxyAuthentication != null) { - proxyConfig.proxyAuthentication = (ProxyAuthentication) this.proxyAuthentication.clone(); - } - return proxyConfig; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/TcpBasedHttpConnection.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/TcpBasedHttpConnection.java deleted file mode 100644 index 9af60e8ee..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/TcpBasedHttpConnection.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.net.http.common; - -import com.fireflysource.net.tcp.TcpConnection; - -/** - * The TCP based HTTP connection. - * - * @author Pengtao Qiu - */ -public interface TcpBasedHttpConnection extends HttpConnection { - - /** - * Get the TCP connection. - * - * @return The TCP connection. - */ - TcpConnection getTcpConnection(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/ContentEncoded.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/ContentEncoded.java deleted file mode 100644 index e3b79dd2c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/ContentEncoded.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.net.http.common.exception.NotSupportContentEncoding; -import com.fireflysource.net.http.common.model.ContentEncoding; - -import java.io.*; -import java.util.zip.*; - -abstract public class ContentEncoded { - - public static byte[] decode(byte[] content, ContentEncoding contentEncoding) throws IOException { - return decode(content, contentEncoding, 512); - } - - public static byte[] decode(byte[] content, ContentEncoding contentEncoding, int bufferSize) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[bufferSize]; - try (ByteArrayInputStream in = new ByteArrayInputStream(content); - InputStream decodingInputStream = createDecodingInputStream(in, contentEncoding, bufferSize) - ) { - while (true) { - int len = decodingInputStream.read(buffer); - if (len < 0) break; - - if (len > 0) { - out.write(buffer, 0, len); - } - } - } - return out.toByteArray(); - } - - public static byte[] encode(byte[] content, ContentEncoding contentEncoding) throws IOException { - return encode(content, contentEncoding, 512); - } - - public static byte[] encode(byte[] content, ContentEncoding contentEncoding, int bufferSize) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (OutputStream encodingOutputStream = createEncodingOutputStream(out, contentEncoding, bufferSize)) { - encodingOutputStream.write(content); - } - return out.toByteArray(); - } - - public static InputStream createDecodingInputStream(InputStream in, ContentEncoding contentEncoding, int bufferSize) throws IOException { - switch (contentEncoding) { - case GZIP: - return new GZIPInputStream(in, bufferSize); - case DEFLATE: - return new InflaterInputStream(in, new Inflater(), bufferSize); - default: - throw new NotSupportContentEncoding("Not support the content encoding"); - } - } - - public static OutputStream createEncodingOutputStream(OutputStream out, ContentEncoding contentEncoding, int bufferSize) throws IOException { - switch (contentEncoding) { - case GZIP: - return new GZIPOutputStream(out, bufferSize); - case DEFLATE: - return new DeflaterOutputStream(out, new Deflater(), bufferSize); - default: - throw new NotSupportContentEncoding("Not support the content encoding"); - } - } - - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/CookieGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/CookieGenerator.java deleted file mode 100644 index e82bd4a12..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/CookieGenerator.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.http.common.model.Cookie; - -import java.util.List; - -abstract public class CookieGenerator { - - public static String generateCookies(List cookies) { - if (cookies == null) { - throw new IllegalArgumentException("the cookie list is null"); - } - - if (cookies.size() == 1) { - return generateCookie(cookies.get(0)); - } else if (cookies.size() > 1) { - StringBuilder str = new StringBuilder(); - - str.append(generateCookie(cookies.get(0))); - for (int i = 1; i < cookies.size(); i++) { - str.append(';').append(generateCookie(cookies.get(i))); - } - - return str.toString(); - } else { - throw new IllegalArgumentException("the cookie list size is 0"); - } - } - - public static String generateCookie(Cookie cookie) { - if (cookie == null) { - throw new IllegalArgumentException("the cookie is null"); - } else { - return cookie.getName() + '=' + cookie.getValue(); - } - } - - public static String generateSetCookie(Cookie cookie) { - if (cookie == null) { - throw new IllegalArgumentException("the cookie is null"); - } else { - StringBuilder str = new StringBuilder(); - - str.append(cookie.getName()).append('=').append(cookie.getValue()); - - if (StringUtils.hasText(cookie.getComment())) { - str.append(";Comment=").append(cookie.getComment()); - } - - if (StringUtils.hasText(cookie.getDomain())) { - str.append(";Domain=").append(cookie.getDomain()); - } - if (cookie.getMaxAge() >= 0) { - str.append(";Max-Age=").append(cookie.getMaxAge()); - } - - String path = !StringUtils.hasText(cookie.getPath()) ? "/" : cookie.getPath(); - str.append(";Path=").append(path); - - if (cookie.getSecure()) { - str.append(";Secure"); - } - - str.append(";Version=").append(cookie.getVersion()); - - return str.toString(); - } - } - - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/CookieParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/CookieParser.java deleted file mode 100644 index 4d7653af2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/CookieParser.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.fireflysource.net.http.common.codec; - - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.http.common.model.Cookie; - -import java.util.ArrayList; -import java.util.List; - -abstract public class CookieParser { - - public static void parseCookies(String cookieStr, CookieParserCallback callback) { - if (!StringUtils.hasText(cookieStr)) { - throw new IllegalArgumentException("the cookie string is empty"); - } else { - String[] cookieKeyValues = StringUtils.split(cookieStr, ';'); - for (String cookieKeyValue : cookieKeyValues) { - String[] kv = StringUtils.split(cookieKeyValue, "=", 2); - if (kv != null) { - if (kv.length == 2) { - callback.cookie(kv[0].trim(), kv[1].trim()); - } else if (kv.length == 1) { - callback.cookie(kv[0].trim(), ""); - } else { - throw new IllegalStateException("the cookie string format error"); - } - } else { - throw new IllegalStateException("the cookie string format error"); - } - } - } - } - - public static Cookie parseSetCookie(String cookieStr) { - final Cookie cookie = new Cookie(); - parseCookies(cookieStr, (name, value) -> { - switch (name.toLowerCase()) { - case "comment": - cookie.setComment(value); - break; - case "domain": - cookie.setDomain(value); - break; - case "max-age": - cookie.setMaxAge(Integer.parseInt(value)); - break; - case "path": - cookie.setPath(value); - break; - case "secure": - cookie.setSecure(true); - break; - case "version": - cookie.setVersion(Integer.parseInt(value)); - break; - default: - cookie.setName(name); - cookie.setValue(value); - break; - } - }); - return cookie; - } - - public static List parseCookie(String cookieStr) { - final List list = new ArrayList<>(); - parseCookies(cookieStr, (name, value) -> list.add(new Cookie(name, value))); - return list; - } - - public interface CookieParserCallback { - void cookie(String name, String value); - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/DateGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/DateGenerator.java deleted file mode 100644 index cfd42f235..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/DateGenerator.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.string.StringUtils; - -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -/** - * ThreadLocal Date formatters for HTTP style dates. - */ -public class DateGenerator { - static final String[] DAYS = {"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; - static final String[] MONTHS = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - "Jan"}; - private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); - private static final ThreadLocal DATE_GENERATOR = ThreadLocal.withInitial(DateGenerator::new); - public final static String JAN_01_1970 = DateGenerator.formatDate(0); - - static { - GMT_TIME_ZONE.setID("GMT"); - } - - private final StringBuilder buf = new StringBuilder(32); - private final GregorianCalendar gc = new GregorianCalendar(GMT_TIME_ZONE); - - /** - * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" - * - * @param date the date in milliseconds - * @return the formatted date - */ - public static String formatDate(long date) { - return DATE_GENERATOR.get().doFormatDate(date); - } - - /** - * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies - * - * @param buf the buffer to put the formatted date into - * @param date the date in milliseconds - */ - public static void formatCookieDate(StringBuilder buf, long date) { - DATE_GENERATOR.get().doFormatCookieDate(buf, date); - } - - /** - * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies - * - * @param date the date in milliseconds - * @return the formatted date - */ - public static String formatCookieDate(long date) { - StringBuilder buf = new StringBuilder(28); - formatCookieDate(buf, date); - return buf.toString(); - } - - /** - * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" - * - * @param date the date in milliseconds - * @return the formatted date - */ - public String doFormatDate(long date) { - buf.setLength(0); - gc.setTimeInMillis(date); - - int day_of_week = gc.get(Calendar.DAY_OF_WEEK); - int day_of_month = gc.get(Calendar.DAY_OF_MONTH); - int month = gc.get(Calendar.MONTH); - int year = gc.get(Calendar.YEAR); - int century = year / 100; - year = year % 100; - - int hours = gc.get(Calendar.HOUR_OF_DAY); - int minutes = gc.get(Calendar.MINUTE); - int seconds = gc.get(Calendar.SECOND); - - buf.append(DAYS[day_of_week]); - buf.append(','); - buf.append(' '); - StringUtils.append2digits(buf, day_of_month); - - buf.append(' '); - buf.append(MONTHS[month]); - buf.append(' '); - StringUtils.append2digits(buf, century); - StringUtils.append2digits(buf, year); - - buf.append(' '); - StringUtils.append2digits(buf, hours); - buf.append(':'); - StringUtils.append2digits(buf, minutes); - buf.append(':'); - StringUtils.append2digits(buf, seconds); - buf.append(" GMT"); - return buf.toString(); - } - - /** - * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies - * - * @param buf the buffer to format the date into - * @param date the date in milliseconds - */ - public void doFormatCookieDate(StringBuilder buf, long date) { - gc.setTimeInMillis(date); - - int day_of_week = gc.get(Calendar.DAY_OF_WEEK); - int day_of_month = gc.get(Calendar.DAY_OF_MONTH); - int month = gc.get(Calendar.MONTH); - int year = gc.get(Calendar.YEAR); - year = year % 10000; - - int epoch = (int) ((date / 1000) % (60 * 60 * 24)); - int seconds = epoch % 60; - epoch = epoch / 60; - int minutes = epoch % 60; - int hours = epoch / 60; - - buf.append(DAYS[day_of_week]); - buf.append(','); - buf.append(' '); - StringUtils.append2digits(buf, day_of_month); - - buf.append('-'); - buf.append(MONTHS[month]); - buf.append('-'); - StringUtils.append2digits(buf, year / 100); - StringUtils.append2digits(buf, year % 100); - - buf.append(' '); - StringUtils.append2digits(buf, hours); - buf.append(':'); - StringUtils.append2digits(buf, minutes); - buf.append(':'); - StringUtils.append2digits(buf, seconds); - buf.append(" GMT"); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/DateParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/DateParser.java deleted file mode 100644 index 75e439c6b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/DateParser.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -/** - * ThreadLocal data parsers for HTTP style dates - */ -public class DateParser { - final static String[] DATE_RECEIVE_FMT = { - "EEE, dd MMM yyyy HH:mm:ss zzz", "EEE, dd-MMM-yy HH:mm:ss", - "EEE MMM dd HH:mm:ss yyyy", - - "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", "EEE dd MMM yyyy HH:mm:ss", - "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", "EEE MMM-dd-yyyy HH:mm:ss zzz", - "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", - "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz", "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz", - "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", - "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss"}; - private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); - private static final ThreadLocal DATE_PARSER = ThreadLocal.withInitial(DateParser::new); - - static { - GMT_TIME_ZONE.setID("GMT"); - } - - final SimpleDateFormat[] DATE_RECEIVE = new SimpleDateFormat[DATE_RECEIVE_FMT.length]; - - public static long parseDate(String date) { - return DATE_PARSER.get().parse(date); - } - - private long parse(final String dateVal) { - for (int i = 0; i < DATE_RECEIVE.length; i++) { - if (DATE_RECEIVE[i] == null) { - DATE_RECEIVE[i] = new SimpleDateFormat(DATE_RECEIVE_FMT[i], Locale.US); - DATE_RECEIVE[i].setTimeZone(GMT_TIME_ZONE); - } - - try { - Date date = (Date) DATE_RECEIVE[i].parseObject(dateVal); - return date.getTime(); - } catch (Exception ignored) { - } - } - - if (dateVal.endsWith(" GMT")) { - final String val = dateVal.substring(0, dateVal.length() - 4); - - for (SimpleDateFormat element : DATE_RECEIVE) { - try { - Date date = (Date) element.parseObject(val); - return date.getTime(); - } catch (Exception ignored) { - } - } - } - return -1; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/Http1FieldPreEncoder.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/Http1FieldPreEncoder.java deleted file mode 100644 index bc8416e77..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/Http1FieldPreEncoder.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpVersion; - -import java.util.Arrays; - -import static java.nio.charset.StandardCharsets.UTF_8; - -public class Http1FieldPreEncoder implements HttpFieldPreEncoder { - - @Override - public HttpVersion getHttpVersion() { - return HttpVersion.HTTP_1_0; - } - - @Override - public byte[] getEncodedField(HttpHeader header, String headerString, String value) { - if (header != null) { - int cbl = header.getBytesColonSpace().length; - byte[] bytes = Arrays.copyOf(header.getBytesColonSpace(), cbl + value.length() + 2); - System.arraycopy(value.getBytes(UTF_8), 0, bytes, cbl, value.length()); - bytes[bytes.length - 2] = (byte) '\r'; - bytes[bytes.length - 1] = (byte) '\n'; - return bytes; - } - - byte[] n = headerString.getBytes(UTF_8); - byte[] v = value.getBytes(UTF_8); - byte[] bytes = Arrays.copyOf(n, n.length + 2 + v.length + 2); - bytes[n.length] = (byte) ':'; - bytes[n.length] = (byte) ' '; - bytes[bytes.length - 2] = (byte) '\r'; - bytes[bytes.length - 1] = (byte) '\n'; - return bytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/HttpFieldPreEncoder.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/HttpFieldPreEncoder.java deleted file mode 100644 index 91d4b3d8f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/HttpFieldPreEncoder.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpVersion; - -/** - * Interface to pre-encode HttpFields. Used by {@link PreEncodedHttpField} - */ -public interface HttpFieldPreEncoder { - - /** - * The major version this encoder is for. Both HTTP/1.0 and HTTP/1.1 use the - * same field encoding, so the {@link HttpVersion#HTTP_1_0} should be return - * for all HTTP/1.x encodings. - * - * @return The major version this encoder is for. - */ - HttpVersion getHttpVersion(); - - byte[] getEncodedField(HttpHeader header, String headerString, String value); -} \ No newline at end of file diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/InclusiveByteRange.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/InclusiveByteRange.java deleted file mode 100644 index bd8578617..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/InclusiveByteRange.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; - -import java.util.*; - -/** - * Byte range inclusive of end points. - * <PRE> - * <p> - * parses the following types of byte ranges: - * <p> - * bytes=100-499 - * bytes=-300 - * bytes=100- - * bytes=1-2,2-3,6-,-2 - * <p> - * given an entity length, converts range to string - * <p> - * bytes 100-499/500 - * - * </PRE> - * <p> - * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2 - * <p> - * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content. - */ -public class InclusiveByteRange { - public static final LazyLogger LOG = SystemLogger.create(InclusiveByteRange.class); - - private long first; - private long last; - - public InclusiveByteRange(long first, long last) { - this.first = first; - this.last = last; - } - - public long getFirst() { - return first; - } - - public long getLast() { - return last; - } - - private void coalesce(InclusiveByteRange r) { - first = Math.min(first, r.first); - last = Math.max(last, r.last); - } - - private boolean overlaps(InclusiveByteRange range) { - return (range.first >= this.first && range.first <= this.last) || - (range.last >= this.first && range.last <= this.last) || - (range.first < this.first && range.last > this.last); - } - - public long getSize() { - return last - first + 1; - } - - public String toHeaderRangeString(long size) { - StringBuilder sb = new StringBuilder(40); - sb.append("bytes "); - sb.append(first); - sb.append('-'); - sb.append(last); - sb.append("/"); - sb.append(size); - return sb.toString(); - } - - @Override - public int hashCode() { - return (int) (first ^ last); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - - if (!(obj instanceof InclusiveByteRange)) - return false; - - return ((InclusiveByteRange) obj).first == this.first && - ((InclusiveByteRange) obj).last == this.last; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(60); - sb.append(first); - sb.append(":"); - sb.append(last); - return sb.toString(); - } - - public static List satisfiableRanges(List headers, long size) { - Iterator iterator = headers.iterator(); - Enumeration enumeration = new Enumeration() { - - @Override - public boolean hasMoreElements() { - return iterator.hasNext(); - } - - @Override - public String nextElement() { - return iterator.next(); - } - }; - return satisfiableRanges(enumeration, size); - } - - /** - * @param headers Enumeration of Range header fields. - * @param size Size of the resource. - * @return List of satisfiable ranges - */ - public static List satisfiableRanges(Enumeration headers, long size) { - List ranges = null; - final long end = size - 1; - - // walk through all Range headers - while (headers.hasMoreElements()) { - String header = headers.nextElement(); - StringTokenizer tok = new StringTokenizer(header, "=,", false); - String t = null; - try { - // read all byte ranges for this header - while (tok.hasMoreTokens()) { - try { - t = tok.nextToken().trim(); - if ("bytes".equals(t)) - continue; - - long first = -1; - long last = -1; - int dash = t.indexOf('-'); - if (dash < 0 || t.indexOf("-", dash + 1) >= 0) { - LOG.warn("Bad range format: {}", t); - break; - } - - if (dash > 0) - first = Long.parseLong(t.substring(0, dash).trim()); - if (dash < (t.length() - 1)) - last = Long.parseLong(t.substring(dash + 1).trim()); - - if (first == -1) { - if (last == -1) { - LOG.warn("Bad range format: {}", t); - break; - } - - if (last == 0) - continue; - - // This is a suffix range - first = Math.max(0, size - last); - last = end; - } else { - // Range starts after end - if (first >= size) - continue; - - if (last == -1) - last = end; - else if (last >= end) - last = end; - } - - if (last < first) { - LOG.warn("Bad range format: {}", t); - break; - } - - InclusiveByteRange range = new InclusiveByteRange(first, last); - if (ranges == null) - ranges = new ArrayList<>(); - - boolean coalesced = false; - for (Iterator i = ranges.listIterator(); i.hasNext(); ) { - InclusiveByteRange r = i.next(); - if (range.overlaps(r)) { - coalesced = true; - r.coalesce(range); - while (i.hasNext()) { - InclusiveByteRange r2 = i.next(); - - if (r2.overlaps(r)) { - r.coalesce(r2); - i.remove(); - } - } - } - } - - if (!coalesced) - ranges.add(range); - } catch (NumberFormatException e) { - LOG.warn("Bad range format: {}", t); - } - } - } catch (Exception e) { - LOG.warn("Bad range format: {}", t); - } - } - - return ranges; - } - - public static String to416HeaderRangeString(long size) { - StringBuilder sb = new StringBuilder(40); - sb.append("bytes */"); - sb.append(size); - return sb.toString(); - } -} - - - diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/MultiPartParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/MultiPartParser.java deleted file mode 100644 index d9f85a89a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/MultiPartParser.java +++ /dev/null @@ -1,631 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.SearchPattern; -import com.fireflysource.common.string.Utf8StringBuilder; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.exception.BadMessageException; -import com.fireflysource.net.http.common.model.HttpTokens; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.EnumSet; - -/** - * A parser for MultiPart content type. - * - * @see https://tools.ietf.org/html/rfc2046#section-5.1 - * @see https://tools.ietf.org/html/rfc2045 - */ -public class MultiPartParser { - public static final LazyLogger LOG = SystemLogger.create(MultiPartParser.class); - - // States - public enum FieldState { - FIELD, - IN_NAME, - AFTER_NAME, - VALUE, - IN_VALUE - } - - // States - public enum State { - PREAMBLE, - DELIMITER, - DELIMITER_PADDING, - DELIMITER_CLOSE, - BODY_PART, - FIRST_OCTETS, - OCTETS, - EPILOGUE, - END - } - - private static final EnumSet DELIMITER_STATES = EnumSet.of(State.DELIMITER, State.DELIMITER_CLOSE, State.DELIMITER_PADDING); - private static final int MAX_HEADER_LINE_LENGTH = 998; - - private final Handler handler; - private final SearchPattern delimiterSearch; - - private String fieldName; - private String fieldValue; - - private State state = State.PREAMBLE; - private FieldState fieldState = FieldState.FIELD; - private int partialBoundary = 2; // No CRLF if no preamble - private boolean cr; - private ByteBuffer patternBuffer; - - private final Utf8StringBuilder string = new Utf8StringBuilder(); - private int length; - - private int totalHeaderLineLength = -1; - - public MultiPartParser(Handler handler, String boundary) { - this.handler = handler; - - String delimiter = "\r\n--" + boundary; - patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII)); - delimiterSearch = SearchPattern.compile(patternBuffer.array()); - } - - public void reset() { - state = State.PREAMBLE; - fieldState = FieldState.FIELD; - partialBoundary = 2; // No CRLF if no preamble - } - - public Handler getHandler() { - return handler; - } - - public State getState() { - return state; - } - - public boolean isState(State state) { - return this.state == state; - } - - private static boolean hasNextByte(ByteBuffer buffer) { - return BufferUtils.hasContent(buffer); - } - - private HttpTokens.Token next(ByteBuffer buffer) { - byte ch = buffer.get(); - HttpTokens.Token t = HttpTokens.TOKENS[0xff & ch]; - - switch (t.getType()) { - case CNTL: - throw new IllegalCharacterException(state, t, buffer); - - case LF: - cr = false; - break; - - case CR: - if (cr) - throw new BadMessageException("Bad EOL"); - - cr = true; - return null; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case HTAB: - case SPACE: - case OTEXT: - case COLON: - if (cr) - throw new BadMessageException("Bad EOL"); - break; - - default: - break; - } - - return t; - } - - private void setString(String s) { - string.reset(); - string.append(s); - length = s.length(); - } - - /* - * Mime Field strings are treated as UTF-8 as per https://tools.ietf.org/html/rfc7578#section-5.1 - */ - private String takeString() { - String s = string.toString(); - // trim trailing whitespace. - if (s.length() > length) - s = s.substring(0, length); - string.reset(); - length = -1; - return s; - } - - /** - * Parse until next Event. - * - * @param buffer the buffer to parse - * @param last whether this buffer contains last bit of content - * @return True if an RequestHandler method called and it returned true; - */ - public boolean parse(ByteBuffer buffer, boolean last) { - boolean handle = false; - while (!handle && BufferUtils.hasContent(buffer)) { - switch (state) { - case PREAMBLE: - parsePreamble(buffer); - continue; - - case DELIMITER: - case DELIMITER_PADDING: - case DELIMITER_CLOSE: - parseDelimiter(buffer); - continue; - - case BODY_PART: - handle = parseMimePartHeaders(buffer); - break; - - case FIRST_OCTETS: - case OCTETS: - handle = parseOctetContent(buffer); - break; - - case EPILOGUE: - BufferUtils.clear(buffer); - break; - - case END: - handle = true; - break; - - default: - throw new IllegalStateException(); - } - } - - if (last && BufferUtils.isEmpty(buffer)) { - if (state == State.EPILOGUE) { - state = State.END; - - if (LOG.isDebugEnabled()) - LOG.debug("messageComplete {}", this); - - return handler.messageComplete(); - } else { - if (LOG.isDebugEnabled()) - LOG.debug("earlyEOF {}", this); - - handler.earlyEOF(); - return true; - } - } - - return handle; - } - - private void parsePreamble(ByteBuffer buffer) { - if (LOG.isDebugEnabled()) - LOG.debug("parsePreamble({})", BufferUtils.toDetailString(buffer)); - - if (partialBoundary > 0) { - int partial = delimiterSearch.startsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), partialBoundary); - if (partial > 0) { - if (partial == delimiterSearch.getLength()) { - buffer.position(buffer.position() + partial - partialBoundary); - partialBoundary = 0; - setState(State.DELIMITER); - return; - } - - partialBoundary = partial; - BufferUtils.clear(buffer); - return; - } - - partialBoundary = 0; - } - - int delimiter = delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); - if (delimiter >= 0) { - buffer.position(delimiter - buffer.arrayOffset() + delimiterSearch.getLength()); - setState(State.DELIMITER); - return; - } - - partialBoundary = delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); - BufferUtils.clear(buffer); - } - - private void parseDelimiter(ByteBuffer buffer) { - if (LOG.isDebugEnabled()) - LOG.debug("parseDelimiter({})", BufferUtils.toDetailString(buffer)); - - while (DELIMITER_STATES.contains(state) && hasNextByte(buffer)) { - HttpTokens.Token t = next(buffer); - if (t == null) - return; - - if (t.getType() == HttpTokens.Type.LF) { - setState(State.BODY_PART); - - if (LOG.isDebugEnabled()) - LOG.debug("startPart {}", this); - - handler.startPart(); - return; - } - - switch (state) { - case DELIMITER: - if (t.getChar() == '-') - setState(State.DELIMITER_CLOSE); - else - setState(State.DELIMITER_PADDING); - continue; - - case DELIMITER_CLOSE: - if (t.getChar() == '-') { - setState(State.EPILOGUE); - return; - } - setState(State.DELIMITER_PADDING); - continue; - - case DELIMITER_PADDING: - default: - } - } - } - - /* - * Parse the message headers and return true if the handler has signaled for a return - */ - protected boolean parseMimePartHeaders(ByteBuffer buffer) { - if (LOG.isDebugEnabled()) - LOG.debug("parseMimePartHeaders({})", BufferUtils.toDetailString(buffer)); - - // Process headers - while (state == State.BODY_PART && hasNextByte(buffer)) { - // process each character - HttpTokens.Token t = next(buffer); - if (t == null) - break; - - if (t.getType() != HttpTokens.Type.LF) - totalHeaderLineLength++; - - if (totalHeaderLineLength > MAX_HEADER_LINE_LENGTH) - throw new IllegalStateException("Header Line Exceeded Max Length"); - - switch (fieldState) { - case FIELD: - switch (t.getType()) { - case SPACE: - case HTAB: { - // Folded field value! - - if (fieldName == null) - throw new IllegalStateException("First field folded"); - - if (fieldValue == null) { - string.reset(); - length = 0; - } else { - setString(fieldValue); - string.append(' '); - length++; - fieldValue = null; - } - setState(FieldState.VALUE); - break; - } - - case LF: - handleField(); - setState(State.FIRST_OCTETS); - partialBoundary = 2; // CRLF is option for empty parts - - if (LOG.isDebugEnabled()) - LOG.debug("headerComplete {}", this); - - if (handler.headerComplete()) - return true; - break; - - case ALPHA: - case DIGIT: - case TCHAR: - // process previous header - handleField(); - - // New header - setState(FieldState.IN_NAME); - string.reset(); - string.append(t.getChar()); - length = 1; - - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case IN_NAME: - switch (t.getType()) { - case COLON: - fieldName = takeString(); - length = -1; - setState(FieldState.VALUE); - break; - - case SPACE: - // Ignore trailing whitespaces - setState(FieldState.AFTER_NAME); - break; - - case LF: { - if (LOG.isDebugEnabled()) - LOG.debug("Line Feed in Name {}", this); - - handleField(); - setState(FieldState.FIELD); - break; - } - - case ALPHA: - case DIGIT: - case TCHAR: - string.append(t.getChar()); - length = string.length(); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case AFTER_NAME: - switch (t.getType()) { - case COLON: - fieldName = takeString(); - length = -1; - setState(FieldState.VALUE); - break; - - case LF: - fieldName = takeString(); - string.reset(); - fieldValue = ""; - length = -1; - break; - - case SPACE: - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case VALUE: - switch (t.getType()) { - case LF: - string.reset(); - fieldValue = ""; - length = -1; - - setState(FieldState.FIELD); - break; - - case SPACE: - case HTAB: - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - case OTEXT: - string.append(t.getByte()); - length = string.length(); - setState(FieldState.IN_VALUE); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case IN_VALUE: - switch (t.getType()) { - case SPACE: - case HTAB: - string.append(' '); - break; - - case LF: - if (length > 0) { - fieldValue = takeString(); - length = -1; - totalHeaderLineLength = -1; - } - setState(FieldState.FIELD); - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - case OTEXT: - string.append(t.getByte()); - length = string.length(); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - default: - throw new IllegalStateException(state.toString()); - } - } - return false; - } - - private void handleField() { - if (LOG.isDebugEnabled()) - LOG.debug("parsedField: _fieldName={} _fieldValue={} {}", fieldName, fieldValue, this); - - if (fieldName != null && fieldValue != null) - handler.parsedField(fieldName, fieldValue); - fieldName = fieldValue = null; - } - - protected boolean parseOctetContent(ByteBuffer buffer) { - if (LOG.isDebugEnabled()) - LOG.debug("parseOctetContent({})", BufferUtils.toDetailString(buffer)); - - // Starts With - if (partialBoundary > 0) { - int partial = delimiterSearch.startsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), partialBoundary); - if (partial > 0) { - if (partial == delimiterSearch.getLength()) { - buffer.position(buffer.position() + delimiterSearch.getLength() - partialBoundary); - setState(State.DELIMITER); - partialBoundary = 0; - - if (LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}", BufferUtils.toDetailString(BufferUtils.EMPTY_BUFFER), true, this); - - return handler.content(BufferUtils.EMPTY_BUFFER, true); - } - - partialBoundary = partial; - BufferUtils.clear(buffer); - return false; - } else { - // output up to _partialBoundary of the search pattern - ByteBuffer content = patternBuffer.slice(); - if (state == State.FIRST_OCTETS) { - setState(State.OCTETS); - content.position(2); - } - content.limit(partialBoundary); - partialBoundary = 0; - - if (LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}", BufferUtils.toDetailString(content), false, this); - - if (handler.content(content, false)) - return true; - } - } - - // Contains - int delimiter = delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); - if (delimiter >= 0) { - ByteBuffer content = buffer.slice(); - content.limit(delimiter - buffer.arrayOffset() - buffer.position()); - - buffer.position(delimiter - buffer.arrayOffset() + delimiterSearch.getLength()); - setState(State.DELIMITER); - - if (LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}", BufferUtils.toDetailString(content), true, this); - - return handler.content(content, true); - } - - // Ends With - partialBoundary = delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); - if (partialBoundary > 0) { - ByteBuffer content = buffer.slice(); - content.limit(content.limit() - partialBoundary); - - if (LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}", BufferUtils.toDetailString(content), false, this); - - BufferUtils.clear(buffer); - return handler.content(content, false); - } - - // There is normal content with no delimiter - ByteBuffer content = buffer.slice(); - - if (LOG.isDebugEnabled()) - LOG.debug("Content={}, Last={} {}", BufferUtils.toDetailString(content), false, this); - - BufferUtils.clear(buffer); - return handler.content(content, false); - } - - private void setState(State state) { - if (LOG.isDebugEnabled()) - LOG.debug("{} --> {}", this.state, state); - this.state = state; - } - - private void setState(FieldState state) { - if (LOG.isDebugEnabled()) - LOG.debug("{}:{} --> {}", this.state, fieldState, state); - fieldState = state; - } - - @Override - public String toString() { - return String.format("%s{s=%s}", getClass().getSimpleName(), state); - } - - /* - * Event Handler interface These methods return true if the caller should process the events so far received (eg return from parseNext and call - * HttpChannel.handle). If multiple callbacks are called in sequence (eg headerComplete then messageComplete) from the same point in the parsing then it is - * sufficient for the caller to process the events only once. - */ - public interface Handler { - default void startPart() { - } - - @SuppressWarnings("unused") - default void parsedField(String name, String value) { - } - - default boolean headerComplete() { - return false; - } - - @SuppressWarnings("unused") - default boolean content(ByteBuffer item, boolean last) { - return false; - } - - default boolean messageComplete() { - return false; - } - - default void earlyEOF() { - } - } - - @SuppressWarnings("serial") - private static class IllegalCharacterException extends BadMessageException { - private IllegalCharacterException(State state, HttpTokens.Token token, ByteBuffer buffer) { - super(400, String.format("Illegal character %s", token)); - if (LOG.isDebugEnabled()) - LOG.debug(String.format("Illegal character %s in state=%s for buffer %s", token, state, BufferUtils.toDetailString(buffer))); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/PreEncodedHttpField.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/PreEncodedHttpField.java deleted file mode 100644 index e9553cf6a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/PreEncodedHttpField.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpVersion; -import com.fireflysource.net.http.common.v2.hpack.HpackFieldPreEncoder; - -import java.nio.ByteBuffer; -import java.util.ServiceLoader; - -/** - * Pre encoded HttpField. - *

    A HttpField that will be cached and used many times can be created as - * a {@link PreEncodedHttpField}, which will use the {@link HttpFieldPreEncoder} - * instances discovered by the {@link ServiceLoader} to pre-encode the header - * for each version of HTTP in use. This will save garbage - * and CPU each time the field is encoded into a response. - *

    - */ -public class PreEncodedHttpField extends HttpField { - private final static HttpFieldPreEncoder[] ENCODERS = new HttpFieldPreEncoder[]{ - new HpackFieldPreEncoder(), - new Http1FieldPreEncoder()}; - - private final byte[][] encodedField = new byte[2][]; - - public PreEncodedHttpField(HttpHeader header, String name, String value) { - super(header, name, value); - - for (HttpFieldPreEncoder e : ENCODERS) { - encodedField[e.getHttpVersion() == HttpVersion.HTTP_2 ? 1 : 0] = e.getEncodedField(header, header.getValue(), value); - } - } - - public PreEncodedHttpField(HttpHeader header, String value) { - this(header, header.getValue(), value); - } - - public PreEncodedHttpField(String name, String value) { - this(null, name, value); - } - - public void putTo(ByteBuffer bufferInFillMode, HttpVersion version) { - bufferInFillMode.put(encodedField[version == HttpVersion.HTTP_2 ? 1 : 0]); - } -} \ No newline at end of file diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/URIUtils.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/URIUtils.java deleted file mode 100644 index 139680534..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/URIUtils.java +++ /dev/null @@ -1,1123 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.string.Utf8Appendable; -import com.fireflysource.common.string.Utf8StringBuilder; -import com.fireflysource.net.http.common.model.HostPort; - -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -/** - * URI Utility methods. - *

    - * This class assists with the decoding and encoding or HTTP URI's. - * It differs from the java.net.URL class as it does not provide - * communications ability, but it does assist with query string - * formatting. - *

    - */ -@SuppressWarnings("unused") -public class URIUtils { - - public static final String SLASH = "/"; - public static final String HTTP = "http"; - public static final String HTTPS = "https"; - - // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars - public static final Charset __CHARSET = StandardCharsets.UTF_8; - - private URIUtils() { - } - - /** - * Encode a URI path. - * This is the same encoding offered by URLEncoder, except that - * the '/' character is not encoded. - * - * @param path The path the encode - * @return The encoded path - */ - public static String encodePath(String path) { - if (path == null || path.length() == 0) - return path; - - StringBuilder buf = encodePath(null, path, 0); - return buf == null ? path : buf.toString(); - } - - /** - * Encode a URI path. - * - * @param path The path the encode - * @param buf StringBuilder to encode path into (or null) - * @return The StringBuilder or null if no substitutions required. - */ - public static StringBuilder encodePath(StringBuilder buf, String path) { - return encodePath(buf, path, 0); - } - - /** - * Encode a URI path. - * - * @param path The path the encode - * @param buf StringBuilder to encode path into (or null) - * @return The StringBuilder or null if no substitutions required. - */ - private static StringBuilder encodePath(StringBuilder buf, String path, int offset) { - byte[] bytes = null; - if (buf == null) { - loop: - for (int i = offset; i < path.length(); i++) { - char c = path.charAt(i); - switch (c) { - case '%': - case '?': - case ';': - case '#': - case '"': - case '\'': - case '<': - case '>': - case ' ': - case '[': - case '\\': - case ']': - case '^': - case '`': - case '{': - case '|': - case '}': - buf = new StringBuilder(path.length() * 2); - break loop; - default: - if (c > 127) { - bytes = path.getBytes(URIUtils.__CHARSET); - buf = new StringBuilder(path.length() * 2); - break loop; - } - } - } - if (buf == null) - return null; - } - - int i; - - loop: - for (i = offset; i < path.length(); i++) { - char c = path.charAt(i); - switch (c) { - case '%': - buf.append("%25"); - continue; - case '?': - buf.append("%3F"); - continue; - case ';': - buf.append("%3B"); - continue; - case '#': - buf.append("%23"); - continue; - case '"': - buf.append("%22"); - continue; - case '\'': - buf.append("%27"); - continue; - case '<': - buf.append("%3C"); - continue; - case '>': - buf.append("%3E"); - continue; - case ' ': - buf.append("%20"); - continue; - case '[': - buf.append("%5B"); - continue; - case '\\': - buf.append("%5C"); - continue; - case ']': - buf.append("%5D"); - continue; - case '^': - buf.append("%5E"); - continue; - case '`': - buf.append("%60"); - continue; - case '{': - buf.append("%7B"); - continue; - case '|': - buf.append("%7C"); - continue; - case '}': - buf.append("%7D"); - continue; - - default: - if (c > 127) { - bytes = path.getBytes(URIUtils.__CHARSET); - break loop; - } - buf.append(c); - } - } - - if (bytes != null) { - for (; i < bytes.length; i++) { - byte c = bytes[i]; - switch (c) { - case '%': - buf.append("%25"); - continue; - case '?': - buf.append("%3F"); - continue; - case ';': - buf.append("%3B"); - continue; - case '#': - buf.append("%23"); - continue; - case '"': - buf.append("%22"); - continue; - case '\'': - buf.append("%27"); - continue; - case '<': - buf.append("%3C"); - continue; - case '>': - buf.append("%3E"); - continue; - case ' ': - buf.append("%20"); - continue; - case '[': - buf.append("%5B"); - continue; - case '\\': - buf.append("%5C"); - continue; - case ']': - buf.append("%5D"); - continue; - case '^': - buf.append("%5E"); - continue; - case '`': - buf.append("%60"); - continue; - case '{': - buf.append("%7B"); - continue; - case '|': - buf.append("%7C"); - continue; - case '}': - buf.append("%7D"); - continue; - default: - if (c < 0) { - buf.append('%'); - TypeUtils.toHex(c, buf); - } else - buf.append((char) c); - } - } - } - - return buf; - } - - /** - * Encode a raw URI String and convert any raw spaces to - * their "%20" equivalent. - * - * @param str input raw string - * @return output with spaces converted to "%20" - */ - public static String encodeSpaces(String str) { - return StringUtils.replaceStr(str, " ", "%20"); - } - - /** - * Encode a raw String and convert any specific characters to their URI encoded equivalent. - * - * @param str input raw string - * @param charsToEncode the list of raw characters that need to be encoded (if encountered) - * @return output with specified characters encoded. - */ - @SuppressWarnings("Duplicates") - public static String encodeSpecific(String str, String charsToEncode) { - if ((str == null) || (str.length() == 0)) - return null; - - if ((charsToEncode == null) || (charsToEncode.length() == 0)) - return str; - - char[] find = charsToEncode.toCharArray(); - int len = str.length(); - StringBuilder ret = new StringBuilder((int) (len * 0.20d)); - for (int i = 0; i < len; i++) { - char c = str.charAt(i); - boolean escaped = false; - for (char f : find) { - if (c == f) { - escaped = true; - ret.append('%'); - int d = 0xf & ((0xF0 & c) >> 4); - ret.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - d = 0xf & c; - ret.append((char) ((d > 9 ? ('A' - 10) : '0') + d)); - break; - } - } - if (!escaped) { - ret.append(c); - } - } - return ret.toString(); - } - - /** - * Decode a raw String and convert any specific URI encoded sequences into characters. - * - * @param str input raw string - * @param charsToDecode the list of raw characters that need to be decoded (if encountered), leaving all other encoded sequences alone. - * @return output with specified characters decoded. - */ - @SuppressWarnings("Duplicates") - public static String decodeSpecific(String str, String charsToDecode) { - if ((str == null) || (str.length() == 0)) - return null; - - if ((charsToDecode == null) || (charsToDecode.length() == 0)) - return str; - - int idx = str.indexOf('%'); - if (idx == -1) { - // no hits - return str; - } - - char[] find = charsToDecode.toCharArray(); - int len = str.length(); - Utf8StringBuilder ret = new Utf8StringBuilder(len); - ret.append(str, 0, idx); - - for (int i = idx; i < len; i++) { - char c = str.charAt(i); - switch (c) { - case '%': - if ((i + 2) < len) { - char u = str.charAt(i + 1); - char l = str.charAt(i + 2); - char result = (char) (0xff & (TypeUtils.convertHexDigit(u) * 16 + TypeUtils.convertHexDigit(l))); - boolean decoded = false; - for (char f : find) { - if (f == result) { - ret.append(result); - decoded = true; - break; - } - } - if (decoded) { - i += 2; - } else { - ret.append(c); - } - } else { - throw new IllegalArgumentException("Bad URI % encoding"); - } - break; - default: - ret.append(c); - break; - } - } - return ret.toString(); - } - - /** - * Encode a URI path. - * - * @param path The path the encode - * @param buf StringBuilder to encode path into (or null) - * @param encode String of characters to encode. % is always encoded. - * @return The StringBuilder or null if no substitutions required. - */ - public static StringBuilder encodeString(StringBuilder buf, - String path, - String encode) { - if (buf == null) { - for (int i = 0; i < path.length(); i++) { - char c = path.charAt(i); - if (c == '%' || encode.indexOf(c) >= 0) { - buf = new StringBuilder(path.length() << 1); - break; - } - } - if (buf == null) - return null; - } - - for (int i = 0; i < path.length(); i++) { - char c = path.charAt(i); - if (c == '%' || encode.indexOf(c) >= 0) { - buf.append('%'); - StringUtils.append(buf, (byte) (0xff & c), 16); - } else - buf.append(c); - } - - return buf; - } - - /* Decode a URI path and strip parameters - */ - public static String decodePath(String path) { - return decodePath(path, 0, path.length()); - } - - /* Decode a URI path and strip parameters of UTF-8 path - */ - public static String decodePath(String path, int offset, int length) { - try { - Utf8StringBuilder builder = null; - int end = offset + length; - for (int i = offset; i < end; i++) { - char c = path.charAt(i); - switch (c) { - case '%': - if (builder == null) { - builder = new Utf8StringBuilder(path.length()); - builder.append(path, offset, i - offset); - } - - // lenient percent decoding - if (i >= end) { - // [LENIENT] a percent sign at end of string. - builder.append('%'); - i = end; - } else if (end > (i + 1)) { - char type = path.charAt(i + 1); - if (type == 'u') { - // We have a possible (deprecated) microsoft unicode code point "%u####" - // - not recommended to use as it's limited to 2 bytes. - if ((i + 5) >= end) { - // [LENIENT] we have a partial "%u####" at the end of a string. - builder.append(path, i, (end - i)); - i = end; - } else { - // this seems wrong, as we are casting to a char, but that's the known - // limitation of this deprecated encoding (only 2 bytes allowed) - if (StringUtils.isHex(path, i + 2, 4)) { - builder.append((char) (0xffff & TypeUtils.parseInt(path, i + 2, 4, 16))); - i += 5; - } else { - // [LENIENT] copy the "%u" as-is. - builder.append(path, i, 2); - i += 1; - } - } - } else if (end > (i + 2)) { - // we have a possible "%##" encoding - if (StringUtils.isHex(path, i + 1, 2)) { - builder.append((byte) TypeUtils.parseInt(path, i + 1, 2, 16)); - i += 2; - } else { - builder.append(path, i, 3); - i += 2; - } - } else { - // [LENIENT] incomplete "%##" sequence at end of string - builder.append(path, i, (end - i)); - i = end; - } - } else { - // [LENIENT] the "%" at the end of the string - builder.append(path, i, (end - i)); - i = end; - } - - break; - - case ';': - if (builder == null) { - builder = new Utf8StringBuilder(path.length()); - builder.append(path, offset, i - offset); - } - - while (++i < end) { - if (path.charAt(i) == '/') { - builder.append('/'); - break; - } - } - - break; - - default: - if (builder != null) - builder.append(c); - break; - } - } - - if (builder != null) - return builder.toString(); - if (offset == 0 && length == path.length()) - return path; - return path.substring(offset, end); - } catch (Utf8Appendable.NotUtf8Exception e) { - return decodeISO88591Path(path, offset, length); - } - } - - /* Decode a URI path and strip parameters of ISO-8859-1 path - */ - private static String decodeISO88591Path(String path, int offset, int length) { - StringBuilder builder = null; - int end = offset + length; - for (int i = offset; i < end; i++) { - char c = path.charAt(i); - switch (c) { - case '%': - if (builder == null) { - builder = new StringBuilder(path.length()); - builder.append(path, offset, i - offset); - } - if ((i + 2) < end) { - char u = path.charAt(i + 1); - if (u == 'u') { - // TODO this is wrong. This is a codepoint not a char - builder.append((char) (0xffff & TypeUtils.parseInt(path, i + 2, 4, 16))); - i += 5; - } else { - builder.append((byte) (0xff & (TypeUtils.convertHexDigit(u) * 16 + TypeUtils.convertHexDigit(path.charAt(i + 2))))); - i += 2; - } - } else { - throw new IllegalArgumentException(); - } - - break; - - case ';': - if (builder == null) { - builder = new StringBuilder(path.length()); - builder.append(path, offset, i - offset); - } - while (++i < end) { - if (path.charAt(i) == '/') { - builder.append('/'); - break; - } - } - break; - - default: - if (builder != null) - builder.append(c); - break; - } - } - - if (builder != null) - return builder.toString(); - if (offset == 0 && length == path.length()) - return path; - return path.substring(offset, end); - } - - /** - * Add two encoded URI path segments. - * Handles null and empty paths, path and query params - * (eg ?a=b or ;JSESSIONID=xxx) and avoids duplicate '/' - * - * @param p1 URI path segment (should be encoded) - * @param p2 URI path segment (should be encoded) - * @return Legally combined path segments. - */ - public static String addEncodedPaths(String p1, String p2) { - if (p1 == null || p1.length() == 0) { - if (p1 != null && p2 == null) - return p1; - return p2; - } - if (p2 == null || p2.length() == 0) - return p1; - - int split = p1.indexOf(';'); - if (split < 0) - split = p1.indexOf('?'); - if (split == 0) - return p2 + p1; - if (split < 0) - split = p1.length(); - - StringBuilder buf = new StringBuilder(p1.length() + p2.length() + 2); - buf.append(p1); - - if (buf.charAt(split - 1) == '/') { - if (p2.startsWith(URIUtils.SLASH)) { - buf.deleteCharAt(split - 1); - buf.insert(split - 1, p2); - } else - buf.insert(split, p2); - } else { - if (p2.startsWith(URIUtils.SLASH)) - buf.insert(split, p2); - else { - buf.insert(split, '/'); - buf.insert(split + 1, p2); - } - } - - return buf.toString(); - } - - /** - * Add two Decoded URI path segments. - * Handles null and empty paths. Path and query params (eg ?a=b or - * ;JSESSIONID=xxx) are not handled - * - * @param p1 URI path segment (should be decoded) - * @param p2 URI path segment (should be decoded) - * @return Legally combined path segments. - */ - public static String addPaths(String p1, String p2) { - if (p1 == null || p1.length() == 0) { - if (p1 != null && p2 == null) - return p1; - return p2; - } - if (p2 == null || p2.length() == 0) - return p1; - - boolean p1EndsWithSlash = p1.endsWith(SLASH); - boolean p2StartsWithSlash = p2.startsWith(SLASH); - - if (p1EndsWithSlash && p2StartsWithSlash) { - if (p2.length() == 1) - return p1; - if (p1.length() == 1) - return p2; - } - - StringBuilder buf = new StringBuilder(p1.length() + p2.length() + 2); - buf.append(p1); - - if (p1.endsWith(SLASH)) { - if (p2.startsWith(SLASH)) - buf.setLength(buf.length() - 1); - } else { - if (!p2.startsWith(SLASH)) - buf.append(SLASH); - } - buf.append(p2); - - return buf.toString(); - } - - /** - * Return the parent Path. - * Treat a URI like a directory path and return the parent directory. - * - * @param p the path to return a parent reference to - * @return the parent path of the URI - */ - public static String parentPath(String p) { - if (p == null || URIUtils.SLASH.equals(p)) - return null; - int slash = p.lastIndexOf('/', p.length() - 2); - if (slash >= 0) - return p.substring(0, slash + 1); - return null; - } - - /** - * Convert a decoded path to a canonical form. - *

    - * All instances of "." and ".." are factored out. - *

    - *

    - * Null is returned if the path tries to .. above its root. - *

    - * - * @param path the path to convert, decoded, with path separators '/' and no queries. - * @return the canonical path, or null if path traversal above root. - */ - public static String canonicalPath(String path) { - if (path == null || path.isEmpty()) - return path; - - boolean slash = true; - int end = path.length(); - int i = 0; - - loop: - while (i < end) { - char c = path.charAt(i); - switch (c) { - case '/': - slash = true; - break; - - case '.': - if (slash) - break loop; - slash = false; - break; - - default: - slash = false; - } - - i++; - } - - if (i == end) - return path; - - StringBuilder canonical = new StringBuilder(path.length()); - canonical.append(path, 0, i); - - int dots = 1; - i++; - while (i <= end) { - char c = i < end ? path.charAt(i) : '\0'; - switch (c) { - case '\0': - case '/': - switch (dots) { - case 0: - if (c != '\0') - canonical.append(c); - break; - - case 1: - break; - - case 2: - if (canonical.length() < 2) - return null; - canonical.setLength(canonical.length() - 1); - canonical.setLength(canonical.lastIndexOf("/") + 1); - break; - - default: - while (dots-- > 0) { - canonical.append('.'); - } - if (c != '\0') - canonical.append(c); - } - - slash = true; - dots = 0; - break; - - case '.': - if (dots > 0) - dots++; - else if (slash) - dots = 1; - else - canonical.append('.'); - slash = false; - break; - - default: - while (dots-- > 0) { - canonical.append('.'); - } - canonical.append(c); - dots = 0; - slash = false; - } - - i++; - } - return canonical.toString(); - } - - /** - * Convert a path to a cananonical form. - *

    - * All instances of "." and ".." are factored out. - *

    - *

    - * Null is returned if the path tries to .. above its root. - *

    - * - * @param path the path to convert (expects URI/URL form, encoded, and with path separators '/') - * @return the canonical path, or null if path traversal above root. - */ - public static String canonicalEncodedPath(String path) { - if (path == null || path.isEmpty()) - return path; - - boolean slash = true; - int end = path.length(); - int i = 0; - - loop: - while (i < end) { - char c = path.charAt(i); - switch (c) { - case '/': - slash = true; - break; - - case '.': - if (slash) - break loop; - slash = false; - break; - - case '?': - return path; - - default: - slash = false; - } - - i++; - } - - if (i == end) - return path; - - StringBuilder canonical = new StringBuilder(path.length()); - canonical.append(path, 0, i); - - int dots = 1; - i++; - while (i <= end) { - char c = i < end ? path.charAt(i) : '\0'; - switch (c) { - case '\0': - case '/': - case '?': - switch (dots) { - case 0: - if (c != '\0') - canonical.append(c); - break; - - case 1: - if (c == '?') - canonical.append(c); - break; - - case 2: - if (canonical.length() < 2) - return null; - canonical.setLength(canonical.length() - 1); - canonical.setLength(canonical.lastIndexOf("/") + 1); - if (c == '?') - canonical.append(c); - break; - default: - while (dots-- > 0) { - canonical.append('.'); - } - if (c != '\0') - canonical.append(c); - } - - slash = true; - dots = 0; - break; - - case '.': - if (dots > 0) - dots++; - else if (slash) - dots = 1; - else - canonical.append('.'); - slash = false; - break; - - default: - while (dots-- > 0) { - canonical.append('.'); - } - canonical.append(c); - dots = 0; - slash = false; - } - - i++; - } - return canonical.toString(); - } - - /** - * Convert a path to a compact form. - * All instances of "//" and "///" etc. are factored out to single "/" - * - * @param path the path to compact - * @return the compacted path - */ - public static String compactPath(String path) { - if (path == null || path.length() == 0) - return path; - - int state = 0; - int end = path.length(); - int i = 0; - - loop: - while (i < end) { - char c = path.charAt(i); - switch (c) { - case '?': - return path; - case '/': - state++; - if (state == 2) - break loop; - break; - default: - state = 0; - } - i++; - } - - if (state < 2) - return path; - - StringBuilder buf = new StringBuilder(path.length()); - buf.append(path, 0, i); - - loop2: - while (i < end) { - char c = path.charAt(i); - switch (c) { - case '?': - buf.append(path, i, end); - break loop2; - case '/': - if (state++ == 0) - buf.append(c); - break; - default: - state = 0; - buf.append(c); - } - i++; - } - - return buf.toString(); - } - - /** - * @param uri URI - * @return True if the uri has a scheme - */ - public static boolean hasScheme(String uri) { - for (int i = 0; i < uri.length(); i++) { - char c = uri.charAt(i); - if (c == ':') { - return true; - } - if (!(c >= 'a' && c <= 'z' || - c >= 'A' && c <= 'Z' || - (i > 0 && (c >= '0' && c <= '9' || c == '.' || c == '+' || c == '-')))) { - break; - } - } - return false; - } - - /** - * Create a new URI from the arguments, handling IPv6 host encoding and default ports - * - * @param scheme the URI scheme - * @param server the URI server - * @param port the URI port - * @param path the URI path - * @param query the URI query - * @return A String URI - */ - public static String newURI(String scheme, String server, int port, String path, String query) { - StringBuilder builder = newURIBuilder(scheme, server, port); - builder.append(path); - if (query != null && query.length() > 0) - builder.append('?').append(query); - return builder.toString(); - } - - /** - * Create a new URI StringBuilder from the arguments, handling IPv6 host encoding and default ports - * - * @param scheme the URI scheme - * @param server the URI server - * @param port the URI port - * @return a StringBuilder containing URI prefix - */ - public static StringBuilder newURIBuilder(String scheme, String server, int port) { - StringBuilder builder = new StringBuilder(); - appendSchemeHostPort(builder, scheme, server, port); - return builder; - } - - /** - * Append scheme, host and port URI prefix, handling IPv6 address encoding and default ports - * - * @param url StringBuilder to append to - * @param scheme the URI scheme - * @param server the URI server - * @param port the URI port - */ - public static void appendSchemeHostPort(StringBuilder url, String scheme, String server, int port) { - url.append(scheme).append("://").append(HostPort.normalizeHost(server)); - - if (port > 0) { - switch (scheme) { - case "http": - if (port != 80) - url.append(':').append(port); - break; - - case "https": - if (port != 443) - url.append(':').append(port); - break; - - default: - url.append(':').append(port); - } - } - } - - public static boolean equalsIgnoreEncodings(String uriA, String uriB) { - int lenA = uriA.length(); - int lenB = uriB.length(); - int a = 0; - int b = 0; - - while (a < lenA && b < lenB) { - int oa = uriA.charAt(a++); - int ca = oa; - if (ca == '%') { - ca = lenientPercentDecode(uriA, a); - if (ca == (-1)) { - ca = '%'; - } else { - a += 2; - } - } - - int ob = uriB.charAt(b++); - int cb = ob; - if (cb == '%') { - cb = lenientPercentDecode(uriB, b); - if (cb == (-1)) { - cb = '%'; - } else { - b += 2; - } - } - - // Don't match on encoded slash - if (ca == '/' && oa != ob) - return false; - - if (ca != cb) - return false; - } - return a == lenA && b == lenB; - } - - private static int lenientPercentDecode(String str, int offset) { - if (offset >= str.length()) - return -1; - - if (StringUtils.isHex(str, offset, 2)) { - return TypeUtils.parseInt(str, offset, 2, 16); - } else { - return -1; - } - } - - public static boolean equalsIgnoreEncodings(URI uriA, URI uriB) { - if (uriA.equals(uriB)) - return true; - - if (uriA.getScheme() == null) { - if (uriB.getScheme() != null) - return false; - } else if (!uriA.getScheme().equalsIgnoreCase(uriB.getScheme())) - return false; - - if ("jar".equalsIgnoreCase(uriA.getScheme())) { - // at this point we know that both uri's are "jar:" - URI uriAssp = URI.create(uriA.getSchemeSpecificPart()); - URI uriBssp = URI.create(uriB.getSchemeSpecificPart()); - return equalsIgnoreEncodings(uriAssp, uriBssp); - } - - if (uriA.getAuthority() == null) { - if (uriB.getAuthority() != null) - return false; - } else if (!uriA.getAuthority().equals(uriB.getAuthority())) - return false; - - return equalsIgnoreEncodings(uriA.getPath(), uriB.getPath()); - } - - /** - * @param uri A URI to add the path to - * @param path A decoded path element - * @return URI with path added. - */ - public static URI addPath(URI uri, String path) { - String base = uri.toASCIIString(); - StringBuilder buf = new StringBuilder(base.length() + path.length() * 3); - buf.append(base); - if (buf.charAt(base.length() - 1) != '/') - buf.append('/'); - - int offset = path.charAt(0) == '/' ? 1 : 0; - encodePath(buf, path, offset); - - return URI.create(buf.toString()); - } - - public static URI getJarSource(URI uri) { - try { - if (!"jar".equals(uri.getScheme())) - return uri; - // Get SSP (retaining encoded form) - String s = uri.getRawSchemeSpecificPart(); - int bangSlash = s.indexOf("!/"); - if (bangSlash >= 0) - s = s.substring(0, bangSlash); - return new URI(s); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - - public static String getJarSource(String uri) { - if (!uri.startsWith("jar:")) - return uri; - int bangSlash = uri.indexOf("!/"); - return (bangSlash >= 0) ? uri.substring(4, bangSlash) : uri.substring(4); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/UrlEncoded.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/UrlEncoded.java deleted file mode 100644 index ebd09cebb..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/codec/UrlEncoded.java +++ /dev/null @@ -1,805 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.collection.map.MultiMap; -import com.fireflysource.common.io.ByteArrayOutputStream2; -import com.fireflysource.common.io.IO; -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.Utf8Appendable; -import com.fireflysource.common.string.Utf8StringBuilder; -import com.fireflysource.common.sys.SystemLogger; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; - -import static com.fireflysource.common.object.TypeUtils.convertHexDigit; - -/** - * Handles coding of MIME "x-www-form-urlencoded". - *

    - * This class handles the encoding and decoding for either the query string of a - * URL or the _content of a POST HTTP request. - *

    - * Notes - *

    - * The UTF-8 charset is assumed, unless otherwise defined by either passing a - * parameter or setting the "org.firefly.utils.UrlEncoding.charset" System - * property. - *

    - *

    - * The hashtable either contains String single values, vectors of String or - * arrays of Strings. - *

    - * - * @see java.net.URLEncoder - */ -@SuppressWarnings("serial") -public class UrlEncoded extends MultiMap implements Cloneable { - - public static final Charset ENCODING; - private static final String __ISO_8859_1 = "iso-8859-1"; - private static final String __UTF8 = "utf-8"; - private static final String __UTF16 = "utf-16"; - private static LazyLogger LOG = SystemLogger.create(UrlEncoded.class); - - static { - Charset encoding; - try { - String charset = System.getProperty("com.firefly.codec.http2.encode.UrlEncoding.charset"); - encoding = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset); - } catch (Exception e) { - LOG.warn("get charset exception", e); - encoding = StandardCharsets.UTF_8; - } - ENCODING = encoding; - } - - public UrlEncoded(UrlEncoded url) { - super(url); - } - - public UrlEncoded() { - } - - public UrlEncoded(String query) { - decodeTo(query, this, ENCODING); - } - - /** - * Encode MultiMap with % encoding. - * - * @param map the map to encode - * @param charset the charset to use for encoding (uses default encoding if null) - * @param equalsForNullValue if True, then an '=' is always used, even - * for parameters without a value. e.g. "blah?a=&b=&c=". - * @return the MultiMap as a string encoded with % encodings. - */ - public static String encode(MultiMap map, Charset charset, boolean equalsForNullValue) { - if (charset == null) - charset = ENCODING; - - StringBuilder result = new StringBuilder(128); - - boolean delim = false; - for (Map.Entry> entry : map.entrySet()) { - String key = entry.getKey(); - List list = entry.getValue(); - int s = list.size(); - - if (delim) { - result.append('&'); - } - - if (s == 0) { - result.append(encodeString(key, charset)); - if (equalsForNullValue) - result.append('='); - } else { - for (int i = 0; i < s; i++) { - if (i > 0) - result.append('&'); - String val = list.get(i); - result.append(encodeString(key, charset)); - - if (val != null) { - if (val.length() > 0) { - result.append('='); - result.append(encodeString(val, charset)); - } else if (equalsForNullValue) - result.append('='); - } else if (equalsForNullValue) - result.append('='); - } - } - delim = true; - } - return result.toString(); - } - - /** - * Decoded parameters to Map. - * - * @param content the string containing the encoded parameters - * @param map the MultiMap to put parsed query parameters into - * @param charset the charset to use for decoding - */ - public static void decodeTo(String content, MultiMap map, String charset) { - decodeTo(content, map, charset == null ? null : Charset.forName(charset)); - } - - /** - * Decoded parameters to Map. - * - * @param content the string containing the encoded parameters - * @param map the MultiMap to put parsed query parameters into - * @param charset the charset to use for decoding - */ - public static void decodeTo(String content, MultiMap map, Charset charset) { - if (charset == null) - charset = ENCODING; - - if (charset == StandardCharsets.UTF_8) { - decodeUtf8To(content, 0, content.length(), map); - return; - } - - String key = null; - String value = null; - int mark = -1; - boolean encoded = false; - for (int i = 0; i < content.length(); i++) { - char c = content.charAt(i); - switch (c) { - case '&': - int l = i - mark - 1; - value = l == 0 ? "" : - (encoded ? decodeString(content, mark + 1, l, charset) : content.substring(mark + 1, i)); - mark = i; - encoded = false; - if (key != null) { - map.add(key, value); - } else if (value != null && value.length() > 0) { - map.add(value, ""); - } - key = null; - value = null; - break; - case '=': - if (key != null) - break; - key = encoded ? decodeString(content, mark + 1, i - mark - 1, charset) : content.substring(mark + 1, i); - mark = i; - encoded = false; - break; - case '+': - encoded = true; - break; - case '%': - encoded = true; - break; - } - } - - if (key != null) { - int l = content.length() - mark - 1; - value = l == 0 ? "" : (encoded ? decodeString(content, mark + 1, l, charset) : content.substring(mark + 1)); - map.add(key, value); - } else if (mark < content.length()) { - key = encoded - ? decodeString(content, mark + 1, content.length() - mark - 1, charset) - : content.substring(mark + 1); - if (key != null && key.length() > 0) { - map.add(key, ""); - } - } - - } - - public static void decodeUtf8To(String query, MultiMap map) { - decodeUtf8To(query, 0, query.length(), map); - } - - /** - * Decoded parameters to Map. - * - * @param query the string containing the encoded parameters - * @param offset the offset within raw to decode from - * @param length the length of the section to decode - * @param map the {@link MultiMap} to populate - */ - public static void decodeUtf8To(String query, int offset, int length, MultiMap map) { - Utf8StringBuilder buffer = new Utf8StringBuilder(); - - String key = null; - String value; - - int end = offset + length; - for (int i = offset; i < end; i++) { - char c = query.charAt(i); - switch (c) { - case '&': - value = buffer.toReplacedString(); - buffer.reset(); - if (key != null) { - map.add(key, value); - } else if (value != null && value.length() > 0) { - map.add(value, ""); - } - key = null; - value = null; - break; - - case '=': - if (key != null) { - buffer.append(c); - break; - } - key = buffer.toReplacedString(); - buffer.reset(); - break; - - case '+': - buffer.append((byte) ' '); - break; - - case '%': - if (i + 2 < end) { - char hi = query.charAt(++i); - char lo = query.charAt(++i); - buffer.append(decodeHexByte(hi, lo)); - } else { - throw new Utf8Appendable.NotUtf8Exception("Incomplete % encoding"); - } - break; - - default: - buffer.append(c); - break; - } - } - - if (key != null) { - value = buffer.toReplacedString(); - buffer.reset(); - map.add(key, value); - } else if (buffer.length() > 0) { - map.add(buffer.toReplacedString(), ""); - } - - } - - /** - * Decoded parameters to MultiMap, using ISO8859-1 encodings. - * - * @param in InputSteam to read - * @param map MultiMap to add parameters to - * @param maxLength maximum length of form to read - * @param maxKeys maximum number of keys to read or -1 for no limit - * @throws IOException if unable to decode inputstream as ISO8859-1 - */ - public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys) - throws IOException { - - StringBuilder buffer = new StringBuilder(); - String key = null; - String value; - - int b; - - int totalLength = 0; - while ((b = in.read()) >= 0) { - switch ((char) b) { - case '&': - value = buffer.length() == 0 ? "" : buffer.toString(); - buffer.setLength(0); - if (key != null) { - map.add(key, value); - } else if (value.length() > 0) { - map.add(value, ""); - } - key = null; - value = null; - if (maxKeys > 0 && map.size() > maxKeys) - throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys)); - break; - - case '=': - if (key != null) { - buffer.append((char) b); - break; - } - key = buffer.toString(); - buffer.setLength(0); - break; - - case '+': - buffer.append(' '); - break; - - case '%': - int code0 = in.read(); - int code1 = in.read(); - buffer.append(decodeHexChar(code0, code1)); - break; - - default: - buffer.append((char) b); - break; - } - if (maxLength >= 0 && (++totalLength > maxLength)) - throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys)); - } - - if (key != null) { - value = buffer.length() == 0 ? "" : buffer.toString(); - buffer.setLength(0); - map.add(key, value); - } else if (buffer.length() > 0) { - map.add(buffer.toString(), ""); - } - - } - - /** - * Decoded parameters to Map. - * - * @param in InputSteam to read - * @param map MultiMap to add parameters to - * @param maxLength maximum form length to decode - * @param maxKeys the maximum number of keys to read or -1 for no limit - * @throws IOException if unable to decode input stream - */ - public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException { - - Utf8StringBuilder buffer = new Utf8StringBuilder(); - String key = null; - String value; - - int b; - - int totalLength = 0; - while ((b = in.read()) >= 0) { - switch ((char) b) { - case '&': - value = buffer.toReplacedString(); - buffer.reset(); - if (key != null) { - map.add(key, value); - } else if (value != null && value.length() > 0) { - map.add(value, ""); - } - key = null; - value = null; - if (maxKeys > 0 && map.size() > maxKeys) - throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys)); - break; - - case '=': - if (key != null) { - buffer.append((byte) b); - break; - } - key = buffer.toReplacedString(); - buffer.reset(); - break; - - case '+': - buffer.append((byte) ' '); - break; - - case '%': - char code0 = (char) in.read(); - char code1 = (char) in.read(); - buffer.append(decodeHexByte(code0, code1)); - break; - - default: - buffer.append((byte) b); - break; - } - if (maxLength >= 0 && (++totalLength > maxLength)) - throw new IllegalStateException("Form is too large"); - } - - if (key != null) { - value = buffer.toReplacedString(); - buffer.reset(); - map.add(key, value); - } else if (buffer.length() > 0) { - map.add(buffer.toReplacedString(), ""); - } - - } - - public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException { - InputStreamReader input = new InputStreamReader(in, StandardCharsets.UTF_16); - StringWriter buf = new StringWriter(8192); - IO.copy(input, buf, maxLength); - - // implement maxKeys - decodeTo(buf.getBuffer().toString(), map, StandardCharsets.UTF_16); - } - - /** - * Decoded parameters to Map. - * - * @param in the stream containing the encoded parameters - * @param map the MultiMap to decode into - * @param charset the charset to use for decoding - * @param maxLength the maximum length of the form to decode - * @param maxKeys the maximum number of keys to decode - * @throws IOException if unable to decode input stream - */ - public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys) throws IOException { - if (charset == null) { - if (ENCODING.equals(StandardCharsets.UTF_8)) - decodeUtf8To(in, map, maxLength, maxKeys); - else - decodeTo(in, map, ENCODING, maxLength, maxKeys); - } else if (__UTF8.equalsIgnoreCase(charset)) - decodeUtf8To(in, map, maxLength, maxKeys); - else if (__ISO_8859_1.equalsIgnoreCase(charset)) - decode88591To(in, map, maxLength, maxKeys); - else if (__UTF16.equalsIgnoreCase(charset)) - decodeUtf16To(in, map, maxLength, maxKeys); - else - decodeTo(in, map, Charset.forName(charset), maxLength, maxKeys); - } - - /** - * Decoded parameters to Map. - * - * @param in the stream containing the encoded parameters - * @param map the MultiMap to decode into - * @param charset the charset to use for decoding - * @param maxLength the maximum length of the form to decode - * @param maxKeys the maximum number of keys to decode - * @throws IOException if unable to decode input stream - */ - public static void decodeTo(InputStream in, MultiMap map, Charset charset, int maxLength, int maxKeys) throws IOException { - //no charset present, use the configured default - if (charset == null) - charset = ENCODING; - - if (StandardCharsets.UTF_8.equals(charset)) { - decodeUtf8To(in, map, maxLength, maxKeys); - return; - } - - if (StandardCharsets.ISO_8859_1.equals(charset)) { - decode88591To(in, map, maxLength, maxKeys); - return; - } - - // Should be all 2 byte encodings - if (StandardCharsets.UTF_16.equals(charset)) { - decodeUtf16To(in, map, maxLength, maxKeys); - return; - } - - - String key = null; - String value; - - int c; - - int totalLength = 0; - - try (ByteArrayOutputStream2 output = new ByteArrayOutputStream2()) { - int size = 0; - - while ((c = in.read()) > 0) { - switch ((char) c) { - case '&': - size = output.size(); - value = size == 0 ? "" : output.toString(charset); - output.setCount(0); - if (key != null) { - map.add(key, value); - } else if (value != null && value.length() > 0) { - map.add(value, ""); - } - key = null; - if (maxKeys > 0 && map.size() > maxKeys) - throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", map.size(), maxKeys)); - break; - case '=': - if (key != null) { - output.write(c); - break; - } - size = output.size(); - key = size == 0 ? "" : output.toString(charset); - output.setCount(0); - break; - case '+': - output.write(' '); - break; - case '%': - int code0 = in.read(); - int code1 = in.read(); - output.write(decodeHexChar(code0, code1)); - break; - default: - output.write(c); - break; - } - - totalLength++; - if (maxLength >= 0 && totalLength > maxLength) - throw new IllegalStateException("Form is too large"); - } - - size = output.size(); - if (key != null) { - value = size == 0 ? "" : output.toString(charset); - output.setCount(0); - map.add(key, value); - } else if (size > 0) - map.add(output.toString(charset), ""); - } - - } - - /** - * Decode String with % encoding. - * This method makes the assumption that the majority of calls - * will need no decoding. - * - * @param encoded the encoded string to decode - * @return the decoded string - */ - public static String decodeString(String encoded) { - return decodeString(encoded, 0, encoded.length(), ENCODING); - } - - /** - * Decode String with % encoding. - * This method makes the assumption that the majority of calls - * will need no decoding. - * - * @param encoded the encoded string to decode - * @param offset the offset in the encoded string to decode from - * @param length the length of characters in the encoded string to decode - * @param charset the charset to use for decoding - * @return the decoded string - */ - public static String decodeString(String encoded, int offset, int length, Charset charset) { - if (charset == null || StandardCharsets.UTF_8.equals(charset)) { - Utf8StringBuilder buffer = null; - - for (int i = 0; i < length; i++) { - char c = encoded.charAt(offset + i); - if (c < 0 || c > 0xff) { - if (buffer == null) { - buffer = new Utf8StringBuilder(length); - buffer.getStringBuilder().append(encoded, offset, offset + i + 1); - } else - buffer.getStringBuilder().append(c); - } else if (c == '+') { - if (buffer == null) { - buffer = new Utf8StringBuilder(length); - buffer.getStringBuilder().append(encoded, offset, offset + i); - } - - buffer.getStringBuilder().append(' '); - } else if (c == '%') { - if (buffer == null) { - buffer = new Utf8StringBuilder(length); - buffer.getStringBuilder().append(encoded, offset, offset + i); - } - - if ((i + 2) < length) { - int o = offset + i + 1; - i += 2; - byte b = (byte) TypeUtils.parseInt(encoded, o, 2, 16); - buffer.append(b); - } else { - buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT); - i = length; - } - } else if (buffer != null) - buffer.getStringBuilder().append(c); - } - - if (buffer == null) { - if (offset == 0 && encoded.length() == length) { - return encoded; - } - return encoded.substring(offset, offset + length); - } - - return buffer.toReplacedString(); - } else { - StringBuilder buffer = null; - - for (int i = 0; i < length; i++) { - char c = encoded.charAt(offset + i); - if (c < 0 || c > 0xff) { - if (buffer == null) { - buffer = new StringBuilder(length); - buffer.append(encoded, offset, offset + i + 1); - } else - buffer.append(c); - } else if (c == '+') { - if (buffer == null) { - buffer = new StringBuilder(length); - buffer.append(encoded, offset, offset + i); - } - - buffer.append(' '); - } else if (c == '%') { - if (buffer == null) { - buffer = new StringBuilder(length); - buffer.append(encoded, offset, offset + i); - } - - byte[] ba = new byte[length]; - int n = 0; - while (c >= 0 && c <= 0xff) { - if (c == '%') { - if (i + 2 < length) { - int o = offset + i + 1; - i += 3; - ba[n] = (byte) TypeUtils.parseInt(encoded, o, 2, 16); - n++; - } else { - ba[n++] = (byte) '?'; - i = length; - } - } else if (c == '+') { - ba[n++] = (byte) ' '; - i++; - } else { - ba[n++] = (byte) c; - i++; - } - - if (i >= length) - break; - c = encoded.charAt(offset + i); - } - - i--; - buffer.append(new String(ba, 0, n, charset)); - - } else if (buffer != null) - buffer.append(c); - } - - if (buffer == null) { - if (offset == 0 && encoded.length() == length) - return encoded; - return encoded.substring(offset, offset + length); - } - - return buffer.toString(); - } - } - - private static char decodeHexChar(int hi, int lo) { - try { - return (char) ((convertHexDigit(hi) << 4) + convertHexDigit(lo)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Not valid encoding '%" + (char) hi + (char) lo + "'"); - } - } - - private static byte decodeHexByte(char hi, char lo) { - try { - return (byte) ((convertHexDigit(hi) << 4) + convertHexDigit(lo)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Not valid encoding '%" + hi + lo + "'"); - } - } - - /** - * Perform URL encoding. - * - * @param string the string to encode - * @return encoded string. - */ - public static String encodeString(String string) { - return encodeString(string, ENCODING); - } - - /** - * Perform URL encoding. - * - * @param string the string to encode - * @param charset the charset to use for encoding - * @return encoded string. - */ - public static String encodeString(String string, Charset charset) { - if (charset == null) - charset = ENCODING; - byte[] bytes = null; - bytes = string.getBytes(charset); - - int len = bytes.length; - byte[] encoded = new byte[bytes.length * 3]; - int n = 0; - boolean noEncode = true; - - for (int i = 0; i < len; i++) { - byte b = bytes[i]; - - if (b == ' ') { - noEncode = false; - encoded[n++] = (byte) '+'; - } else if (b >= 'a' && b <= 'z' || - b >= 'A' && b <= 'Z' || - b >= '0' && b <= '9') { - encoded[n++] = b; - } else { - noEncode = false; - encoded[n++] = (byte) '%'; - byte nibble = (byte) ((b & 0xf0) >> 4); - if (nibble >= 10) - encoded[n++] = (byte) ('A' + nibble - 10); - else - encoded[n++] = (byte) ('0' + nibble); - nibble = (byte) (b & 0xf); - if (nibble >= 10) - encoded[n++] = (byte) ('A' + nibble - 10); - else - encoded[n++] = (byte) ('0' + nibble); - } - } - - if (noEncode) - return string; - - return new String(encoded, 0, n, charset); - } - - public void decode(String query) { - decodeTo(query, this, ENCODING); - } - - public void decode(String query, Charset charset) { - decodeTo(query, this, charset); - } - - /** - * Encode MultiMap with % encoding for UTF8 sequences. - * - * @return the MultiMap as a string with % encoding - */ - public String encode() { - return encode(ENCODING, false); - } - - /** - * Encode MultiMap with % encoding for arbitrary Charset sequences. - * - * @param charset the charset to use for encoding - * @return the MultiMap as a string encoded with % encodings - */ - public String encode(Charset charset) { - return encode(charset, false); - } - - /** - * Encode MultiMap with % encoding. - * - * @param charset the charset to encode with - * @param equalsForNullValue if True, then an '=' is always used, even - * for parameters without a value. e.g. "blah?a=&b=&c=". - * @return the MultiMap as a string encoded with % encodings - */ - public String encode(Charset charset, boolean equalsForNullValue) { - return encode(this, charset, equalsForNullValue); - } - - @Override - public Object clone() { - return new UrlEncoded(this); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/content/handler/HttpContentHandler.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/content/handler/HttpContentHandler.java deleted file mode 100644 index fd3856bcc..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/content/handler/HttpContentHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.net.http.common.content.handler; - -import com.fireflysource.common.io.AsyncCloseable; - -import java.nio.ByteBuffer; -import java.util.function.BiConsumer; - -public interface HttpContentHandler extends BiConsumer, AsyncCloseable { -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/content/provider/HttpContentProvider.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/content/provider/HttpContentProvider.java deleted file mode 100644 index f1200a0c4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/content/provider/HttpContentProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.fireflysource.net.http.common.content.provider; - -import com.fireflysource.common.io.InputChannel; - -public interface HttpContentProvider extends InputChannel { -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/BadMessageException.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/BadMessageException.java deleted file mode 100644 index 6f67d9c8d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/BadMessageException.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -/** - *

    - * Exception thrown to indicate a Bad HTTP Message has either been received or - * attempted to be generated. Typically these are handled with either 400 or 500 - * responses. - *

    - */ -public class BadMessageException extends RuntimeException { - private static final long serialVersionUID = -4907256166019479626L; - private final int code; - private final String reason; - - public BadMessageException() { - this(400, null); - } - - public BadMessageException(int code) { - this(code, null); - } - - public BadMessageException(String reason) { - this(400, reason); - } - - public BadMessageException(int code, String reason) { - super(code + ": " + reason); - this.code = code; - this.reason = reason; - } - - public BadMessageException(String reason, Throwable cause) { - this(400, reason, cause); - } - - public BadMessageException(int code, String reason, Throwable cause) { - super(code + ": " + reason, cause); - this.code = code; - this.reason = reason; - } - - public int getCode() { - return code; - } - - public String getReason() { - return reason; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/NotSupportContentEncoding.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/NotSupportContentEncoding.java deleted file mode 100644 index 76866f9f5..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/NotSupportContentEncoding.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -import java.io.IOException; - -/** - * @author Pengtao Qiu - */ -public class NotSupportContentEncoding extends IOException { - - public NotSupportContentEncoding(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/URISyntaxRuntimeException.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/URISyntaxRuntimeException.java deleted file mode 100644 index 02816b409..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/exception/URISyntaxRuntimeException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -import java.net.URISyntaxException; - -public class URISyntaxRuntimeException extends RuntimeException { - - public URISyntaxRuntimeException(String message, URISyntaxException e) { - super(message, e); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/AcceptMIMEMatchType.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/AcceptMIMEMatchType.java deleted file mode 100644 index 3dc93399c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/AcceptMIMEMatchType.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.model; - -/** - * @author Pengtao Qiu - */ -public enum AcceptMIMEMatchType { - PARENT, CHILD, ALL, EXACT -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/AcceptMIMEType.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/AcceptMIMEType.java deleted file mode 100644 index c3aedd120..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/AcceptMIMEType.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class AcceptMIMEType { - private String parentType; - private String childType; - private float quality = 1.0f; - private AcceptMIMEMatchType matchType; - - public String getParentType() { - return parentType; - } - - public void setParentType(String parentType) { - this.parentType = parentType; - } - - public String getChildType() { - return childType; - } - - public void setChildType(String childType) { - this.childType = childType; - } - - public float getQuality() { - return quality; - } - - public void setQuality(float quality) { - this.quality = quality; - } - - public AcceptMIMEMatchType getMatchType() { - return matchType; - } - - public void setMatchType(AcceptMIMEMatchType matchType) { - this.matchType = matchType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AcceptMIMEType that = (AcceptMIMEType) o; - return Objects.equals(parentType, that.parentType) && - Objects.equals(childType, that.childType); - } - - @Override - public int hashCode() { - return Objects.hash(parentType, childType); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/ContentEncoding.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/ContentEncoding.java deleted file mode 100644 index fcf065c58..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/ContentEncoding.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -public enum ContentEncoding { - - GZIP("gzip"), - DEFLATE("deflate"), - BR("br"); - - private static class Holder { - private static final Map map = new HashMap<>(4); - } - - private final String value; - - ContentEncoding(String value) { - this.value = value; - Holder.map.put(value, this); - } - - public String getValue() { - return value; - } - - public static Optional from(String value) { - return Optional.ofNullable(value).map(Holder.map::get); - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/Cookie.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/Cookie.java deleted file mode 100644 index 930660b88..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/Cookie.java +++ /dev/null @@ -1,348 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import java.util.Locale; - -public class Cookie { - - // - // The value of the cookie itself. - // - - private String name; // NAME= ... "$Name" style is reserved - private String value; // value of NAME - - // - // Attributes encoded in the header's cookie fields. - // - - private String comment; // ;Comment=VALUE ... describes cookie's use - // ;Discard ... implied by maxAge < 0 - private String domain; // ;Domain=VALUE ... domain that sees cookie - private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire - private String path; // ;Path=VALUE ... URLs that see the cookie - private boolean secure; // ;Secure ... e.g. use SSL - private int version = 0; // ;Version=1 ... means RFC 2109++ style - private boolean isHttpOnly = false; - - public Cookie() { - - } - - /** - * Constructs a cookie with the specified name and value. - * - *

    - * The name must conform to RFC 2109. However, vendors may provide a - * configuration option that allows cookie names conforming to the original - * Netscape Cookie Specification to be accepted. - * - *

    - * The name of a cookie cannot be changed once the cookie has been created. - * - *

    - * The value can be anything the server chooses to send. Its value is - * probably of interest only to the server. The cookie's value can be - * changed after creation with the setValue method. - * - *

    - * By default, cookies are created according to the Netscape cookie - * specification. The version can be changed with the - * setVersion method. - * - * @param name the name of the cookie - * @param value the value of the cookie - * @throws IllegalArgumentException if the cookie name is null or empty or contains any illegal - * characters (for example, a comma, space, or semicolon) or - * matches a token reserved for use by the cookie protocol - * @see #setValue - * @see #setVersion - */ - public Cookie(String name, String value) { - if (name == null || name.length() == 0) { - throw new IllegalArgumentException("the cookie name is empty"); - } - - this.name = name; - this.value = value; - } - - /** - * Returns the comment describing the purpose of this cookie, or - * null if the cookie has no comment. - * - * @return the comment of the cookie, or null if unspecified - * @see #setComment - */ - public String getComment() { - return comment; - } - - /** - * Specifies a comment that describes a cookie's purpose. The comment is - * useful if the browser presents the cookie to the user. Comments are not - * supported by Netscape Version 0 cookies. - * - * @param purpose a String specifying the comment to display to the - * user - * @see #getComment - */ - public void setComment(String purpose) { - comment = purpose; - } - - /** - * Gets the domain name of this Cookie. - * - *

    - * Domain names are formatted according to RFC 2109. - * - * @return the domain name of this Cookie - * @see #setDomain - */ - public String getDomain() { - return domain; - } - - /** - * Specifies the domain within which this cookie should be presented. - * - *

    - * The form of the domain name is specified by RFC 2109. A domain name - * begins with a dot (.foo.com) and means that the cookie is - * visible to servers in a specified Domain Name System (DNS) zone (for - * example, www.foo.com, but not a.b.foo.com). By - * default, cookies are only returned to the server that sent them. - * - * @param domain the domain name within which this cookie is visible; form is - * according to RFC 2109 - * @see #getDomain - */ - public void setDomain(String domain) { - this.domain = domain.toLowerCase(Locale.ENGLISH); // IE allegedly needs - // this - } - - /** - * Gets the maximum age in seconds of this Cookie. - * - *

    - * By default, -1 is returned, which indicates that the cookie - * will persist until browser shutdown. - * - * @return an integer specifying the maximum age of the cookie in seconds; - * if negative, means the cookie persists until browser shutdown - * @see #setMaxAge - */ - public int getMaxAge() { - return maxAge; - } - - /** - * Sets the maximum age in seconds for this Cookie. - * - *

    - * A positive value indicates that the cookie will expire after that many - * seconds have passed. Note that the value is the maximum age when - * the cookie will expire, not the cookie's current age. - * - *

    - * A negative value means that the cookie is not stored persistently and - * will be deleted when the Web browser exits. A zero value causes the - * cookie to be deleted. - * - * @param expiry an integer specifying the maximum age of the cookie in - * seconds; if negative, means the cookie is not stored; if zero, - * deletes the cookie - * @see #getMaxAge - */ - public void setMaxAge(int expiry) { - maxAge = expiry; - } - - /** - * Returns the path on the server to which the browser returns this cookie. - * The cookie is visible to all subpaths on the server. - * - * @return a String specifying a path , for example, - * /catalog - * @see #setPath - */ - public String getPath() { - return path; - } - - /** - * Specifies a path for the cookie to which the client should return the - * cookie. - * - *

    - * The cookie is visible to all the pages in the directory you specify, and - * all the pages in that directory's subdirectories. A cookie's path, for - * example, /catalog, which makes the cookie visible to all - * directories on the server under /catalog. - * - *

    - * Consult RFC 2109 (available on the Internet) for more information on - * setting path names for cookies. - * - * @param uri a String specifying a path - * @see #getPath - */ - public void setPath(String uri) { - path = uri; - } - - /** - * Returns true if the browser is sending cookies only over a - * secure protocol, or false if the browser can send cookies - * using any protocol. - * - * @return true if the browser uses a secure protocol, - * false otherwise - * @see #setSecure - */ - public boolean getSecure() { - return secure; - } - - /** - * Indicates to the browser whether the cookie should only be sent using a - * secure protocol, such as HTTPS or SSL. - * - *

    - * The default value is false. - * - * @param flag if true, sends the cookie from the browser to the - * server only when using a secure protocol; if - * false, sent on any protocol - * @see #getSecure - */ - public void setSecure(boolean flag) { - secure = flag; - } - - /** - * Returns the name of the cookie. The name cannot be changed after - * creation. - * - * @return the name of the cookie - */ - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - /** - * Gets the current value of this Cookie. - * - * @return the current value of this Cookie - * @see #setValue - */ - public String getValue() { - return value; - } - - /** - * Assigns a new value to this Cookie. - * - *

    - * If you use a binary value, you may want to use BASE64 encoding. - * - *

    - * With Version 0 cookies, values should not contain white space, brackets, - * parentheses, equals signs, commas, double quotes, slashes, question - * marks, at signs, colons, and semicolons. Empty values may not behave the - * same way on all browsers. - * - * @param newValue the new value of the cookie - * @see #getValue - */ - public void setValue(String newValue) { - value = newValue; - } - - /** - * Returns the version of the protocol this cookie complies with. Version 1 - * complies with RFC 2109, and version 0 complies with the original cookie - * specification drafted by Netscape. Cookies provided by a browser use and - * identify the browser's cookie version. - * - * @return 0 if the cookie complies with the original Netscape - * specification; 1 if the cookie complies with RFC 2109 - * @see #setVersion - */ - public int getVersion() { - return version; - } - - /** - * Sets the version of the cookie protocol that this Cookie complies with. - * - *

    - * Version 0 complies with the original Netscape cookie specification. - * Version 1 complies with RFC 2109. - * - *

    - * Since RFC 2109 is still somewhat new, consider version 1 as experimental; - * do not use it yet on production sites. - * - * @param v 0 if the cookie should comply with the original Netscape - * specification; 1 if the cookie should comply with RFC 2109 - * @see #getVersion - */ - public void setVersion(int v) { - version = v; - } - - /** - * Overrides the standard java.lang.Object.clone method to - * return a copy of this Cookie. - */ - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e.getMessage()); - } - } - - /** - * Checks whether this Cookie has been marked as HttpOnly. - * - * @return true if this Cookie has been marked as HttpOnly, false - * otherwise - */ - public boolean isHttpOnly() { - return isHttpOnly; - } - - /** - * Marks or unmarks this Cookie as HttpOnly. - * - *

    - * If isHttpOnly is set to true, this cookie is marked as - * HttpOnly, by adding the HttpOnly attribute to it. - * - *

    - * HttpOnly cookies are not supposed to be exposed to client-side - * scripting code, and may therefore help mitigate certain kinds of - * cross-site scripting attacks. - * - * @param isHttpOnly true if this cookie is to be marked as HttpOnly, false - * otherwise - */ - public void setHttpOnly(boolean isHttpOnly) { - this.isHttpOnly = isHttpOnly; - } - - @Override - public String toString() { - return "Cookie [name=" + name + ", value=" + value + ", comment=" + comment + ", domain=" + domain + ", maxAge=" - + maxAge + ", path=" + path + ", secure=" + secure + ", version=" + version + ", isHttpOnly=" - + isHttpOnly + "]"; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HostPort.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HostPort.java deleted file mode 100644 index 498694275..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HostPort.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.string.StringUtils; - -/** - * Parse an authority string into Host and Port - *

    Parse a string in the form "host:port", handling IPv4 an IPv6 hosts

    - * - *

    The System property "com.fireflysource.net.http.model.HostPort.STRIP_IPV6" can be set to a boolean - * value to control of the square brackets are stripped off IPv6 addresses (default false).

    - */ -public class HostPort { - private final static boolean STRIP_IPV6 = Boolean.parseBoolean(System.getProperty("com.fireflysource.net.http.model.HostPort.STRIP_IPV6", "false")); - - private final String host; - private final int port; - - public HostPort(String authority) throws IllegalArgumentException { - if (authority == null) - throw new IllegalArgumentException("No Authority"); - try { - if (authority.isEmpty()) { - host = authority; - port = 0; - } else if (authority.charAt(0) == '[') { - // ipv6reference - int close = authority.lastIndexOf(']'); - if (close < 0) - throw new IllegalArgumentException("Bad IPv6 host"); - host = STRIP_IPV6 ? authority.substring(1, close) : authority.substring(0, close + 1); - - if (authority.length() > close + 1) { - if (authority.charAt(close + 1) != ':') - throw new IllegalArgumentException("Bad IPv6 port"); - port = StringUtils.toInt(authority, close + 2); - } else - port = 0; - } else { - // ipv4address or hostname - int c = authority.lastIndexOf(':'); - if (c >= 0) { - host = authority.substring(0, c); - port = StringUtils.toInt(authority, c + 1); - } else { - host = authority; - port = 0; - } - } - } catch (IllegalArgumentException iae) { - throw iae; - } catch (final Exception ex) { - throw new IllegalArgumentException("Bad HostPort") { - { - initCause(ex); - } - }; - } - if (host == null) - throw new IllegalArgumentException("Bad host"); - if (port < 0) - throw new IllegalArgumentException("Bad port"); - } - - /* ------------------------------------------------------------ */ - - /** - * Normalize IPv6 address as per https://www.ietf.org/rfc/rfc2732.txt - * - * @param host A host name - * @return Host name surrounded by '[' and ']' as needed. - */ - public static String normalizeHost(String host) { - // if it is normalized IPv6 or could not be IPv6, return - if (host.isEmpty() || host.charAt(0) == '[' || host.indexOf(':') < 0) - return host; - - // normalize with [ ] - return "[" + host + "]"; - } - - /* ------------------------------------------------------------ */ - - /** - * Get the host. - * - * @return the host - */ - public String getHost() { - return host; - } - - /* ------------------------------------------------------------ */ - - /** - * Get the port. - * - * @return the port - */ - public int getPort() { - return port; - } - - /* ------------------------------------------------------------ */ - - /** - * Get the port. - * - * @param defaultPort, the default port to return if a port is not specified - * @return the port - */ - public int getPort(int defaultPort) { - return port > 0 ? port : defaultPort; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HostPortHttpField.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HostPortHttpField.java deleted file mode 100644 index cd5915576..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HostPortHttpField.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.net.http.common.exception.BadMessageException; - -public class HostPortHttpField extends HttpField { - - private final HostPort hostPort; - - public HostPortHttpField(String authority) { - this(HttpHeader.HOST, HttpHeader.HOST.getValue(), authority); - } - - public HostPortHttpField(HttpHeader header, String name, String authority) { - super(header, name, authority); - try { - hostPort = new HostPort(authority); - } catch (Exception e) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad HostPort", e); - } - } - - /** - * Get the host. - * - * @return the host - */ - public String getHost() { - return hostPort.getHost(); - } - - /** - * Get the port. - * - * @return the port - */ - public int getPort() { - return hostPort.getPort(); - } - - /** - * Get the port. - * - * @param defaultPort The default port to return if no port set - * @return the port - */ - public int getPort(int defaultPort) { - return hostPort.getPort(defaultPort); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpCompliance.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpCompliance.java deleted file mode 100644 index aeb7d2a6d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpCompliance.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; - -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; - -/** - * HTTP compliance modes for HTTP parsing and handling. - * A Compliance mode consists of a set of {@link HttpComplianceSection}s which are applied - * when the mode is enabled. - *

    - * Currently the set of modes is an enum and cannot be dynamically extended, but future major releases may convert this - * to a class. To modify modes there are four custom modes that can be modified by setting the property - * HttpCompliance.CUSTOMn (where 'n' is '0', '1', '2' or '3'), to a comma separated - * list of sections. The list should start with one of the following strings:

    - *
    0
    No {@link HttpComplianceSection}s
    - *
    *
    All {@link HttpComplianceSection}s
    - *
    RFC2616
    The set of {@link HttpComplianceSection}s application to https://tools.ietf.org/html/rfc2616, - * but not https://tools.ietf.org/html/rfc7230
    - *
    RFC7230
    The set of {@link HttpComplianceSection}s application to https://tools.ietf.org/html/rfc7230
    - *
    - * The remainder of the list can contain then names of {@link HttpComplianceSection}s to include them in the mode, or prefixed - * with a '-' to exclude thm from the mode. Note that modes may have some historic minor differences from the strict - * RFC compliance, for example the RFC2616_LEGACY HttpCompliance is defined as - * RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE. - *

    - * Note also that the {@link EnumSet} return by {@link HttpCompliance#sections()} is mutable, so that modes may - * be altered in code and will affect all usages of the mode. - */ -public enum HttpCompliance { - - LEGACY(sectionsBySpec("0,METHOD_CASE_SENSITIVE")), - - /** - * The legacy RFC2616 support, which incorrectly excludes - * {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}, - * {@link HttpComplianceSection#FIELD_COLON}, - * {@link HttpComplianceSection#TRANSFER_ENCODING_WITH_CONTENT_LENGTH}, - * {@link HttpComplianceSection#MULTIPLE_CONTENT_LENGTHS}, - */ - RFC2616_LEGACY(sectionsBySpec("RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE,-TRANSFER_ENCODING_WITH_CONTENT_LENGTH,-MULTIPLE_CONTENT_LENGTHS")), - - /** - * The strict RFC2616 support mode - */ - RFC2616(sectionsBySpec("RFC2616")), - - /** - * RFC7230 support, which incorrectly excludes {@link HttpComplianceSection#METHOD_CASE_SENSITIVE} - */ - RFC7230_LEGACY(sectionsBySpec("RFC7230,-METHOD_CASE_SENSITIVE")), - - /** - * The RFC7230 support mode - */ - RFC7230(sectionsBySpec("RFC7230")), - - @Deprecated - CUSTOM0(sectionsByProperty("CUSTOM0")), - - @Deprecated - CUSTOM1(sectionsByProperty("CUSTOM1")), - - @Deprecated - CUSTOM2(sectionsByProperty("CUSTOM2")), - - @Deprecated - CUSTOM3(sectionsByProperty("CUSTOM3")); - - private static final LazyLogger LOG = SystemLogger.create(HttpCompliance.class); - private final static Map REQUIRED = new HashMap<>(); - - static { - for (HttpComplianceSection section : HttpComplianceSection.values()) { - for (HttpCompliance compliance : HttpCompliance.values()) { - if (compliance.sections().contains(section)) { - REQUIRED.put(section, compliance); - break; - } - } - } - } - - private final EnumSet SECTIONS; - - HttpCompliance(EnumSet sections) { - SECTIONS = sections; - } - - private static EnumSet sectionsByProperty(String property) { - String s = System.getProperty(HttpCompliance.class.getName() + property); - return sectionsBySpec(s == null ? "*" : s); - } - - static EnumSet sectionsBySpec(String spec) { - EnumSet sections; - String[] elements = spec.split("\\s*,\\s*"); - int i = 0; - - switch (elements[i]) { - case "0": - sections = EnumSet.noneOf(HttpComplianceSection.class); - i++; - break; - - case "*": - i++; - sections = EnumSet.allOf(HttpComplianceSection.class); - break; - - case "RFC2616": - sections = EnumSet.complementOf(EnumSet.of( - HttpComplianceSection.NO_FIELD_FOLDING, - HttpComplianceSection.NO_HTTP_0_9)); - i++; - break; - - case "RFC7230": - i++; - sections = EnumSet.allOf(HttpComplianceSection.class); - break; - - default: - sections = EnumSet.noneOf(HttpComplianceSection.class); - break; - } - - while (i < elements.length) { - String element = elements[i++]; - boolean exclude = element.startsWith("-"); - if (exclude) - element = element.substring(1); - HttpComplianceSection section = HttpComplianceSection.valueOf(element); - if (section == null) { - LOG.warn("Unknown section '" + element + "' in HttpCompliance spec: " + spec); - continue; - } - if (exclude) - sections.remove(section); - else - sections.add(section); - - } - - return sections; - } - - /** - * @param section The section to query - * @return The minimum compliance required to enable the section. - */ - public static HttpCompliance requiredCompliance(HttpComplianceSection section) { - return REQUIRED.get(section); - } - - /** - * Get the set of {@link HttpComplianceSection}s supported by this compliance mode. This set - * is mutable, so it can be modified. Any modification will affect all usages of the mode - * within the same {@link ClassLoader}. - * - * @return The set of {@link HttpComplianceSection}s supported by this compliance mode. - */ - public EnumSet sections() { - return SECTIONS; - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpComplianceSection.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpComplianceSection.java deleted file mode 100644 index c4841248a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpComplianceSection.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.net.http.common.model; - -public enum HttpComplianceSection { - CASE_INSENSITIVE_FIELD_VALUE_CACHE("", "Use case insensitive field value cache"), - METHOD_CASE_SENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.1.1", "Method is case-sensitive"), - FIELD_COLON("https://tools.ietf.org/html/rfc7230#section-3.2", "Fields must have a Colon"), - FIELD_NAME_CASE_INSENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.2", "Field name is case-insensitive"), - NO_WS_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2.4", "Whitespace not allowed after field name"), - NO_FIELD_FOLDING("https://tools.ietf.org/html/rfc7230#section-3.2.4", "No line Folding"), - NO_HTTP_0_9("https://tools.ietf.org/html/rfc7230#appendix-A.2", "No HTTP/0.9"), - TRANSFER_ENCODING_WITH_CONTENT_LENGTH("https://tools.ietf.org/html/rfc7230#section-3.3.1", "Transfer-Encoding and Content-Length"), - MULTIPLE_CONTENT_LENGTHS("https://tools.ietf.org/html/rfc7230#section-3.3.1", "Multiple Content-Lengths"); - - final String url; - final String description; - - HttpComplianceSection(String url, String description) { - this.url = url; - this.description = description; - } - - public String getURL() { - return url; - } - - public String getDescription() { - return description; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpField.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpField.java deleted file mode 100644 index 782ec2d29..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpField.java +++ /dev/null @@ -1,356 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.string.StringUtils; - -import java.util.Objects; - -public class HttpField { - - private final static String ZERO_QUALITY = "q=0"; - private final HttpHeader header; - private final String name; - private final String value; - // cached hashcode for case insensitive name - private int hash = 0; - - public HttpField(HttpHeader header, String name, String value) { - this.header = header; - this.name = name; - this.value = value; - } - - public HttpField(HttpHeader header, String value) { - this(header, header.getValue(), value); - } - - public HttpField(HttpHeader header, HttpHeaderValue value) { - this(header, header.getValue(), value.getValue()); - } - - public HttpField(String name, String value) { - this(HttpHeader.CACHE.get(name), name, value); - } - - public HttpHeader getHeader() { - return header; - } - - public String getName() { - return name; - } - - public String getLowerCaseName() { - return header != null ? header.getLowerCaseValue() : StringUtils.asciiToLowerCase(name); - } - - public String getValue() { - return value; - } - - public int getIntValue() { - return Integer.parseInt(value); - } - - public long getLongValue() { - return Long.parseLong(value); - } - - public String[] getValues() { - if (value == null) - return null; - - QuotedCSV list = new QuotedCSV(false, value); - return list.getValues().toArray(new String[list.size()]); - } - - /** - * Look for a value in a possible multi valued field - * - * @param search Values to search for (case insensitive) - * @return True iff the value is contained in the field value entirely or - * as an element of a quoted comma separated list. List element parameters (eg qualities) are ignored, - * except if they are q=0, in which case the item itself is ignored. - */ - public boolean contains(String search) { - if (search == null) - return value == null; - if (search.length() == 0) - return false; - if (value == null) - return false; - if (search.equals(value)) - return true; - - search = StringUtils.asciiToLowerCase(search); - - int state = 0; - int match = 0; - int param = 0; - - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - switch (state) { - case 0: // initial white space - switch (c) { - case '"': // open quote - match = 0; - state = 2; - break; - - case ',': // ignore leading empty field - break; - - case ';': // ignore leading empty field parameter - param = -1; - match = -1; - state = 5; - break; - - case ' ': // more white space - case '\t': - break; - - default: // character - match = Character.toLowerCase(c) == search.charAt(0) ? 1 : -1; - state = 1; - break; - } - break; - - case 1: // In token - switch (c) { - case ',': // next field - // Have we matched the token? - if (match == search.length()) - return true; - state = 0; - break; - - case ';': - param = match >= 0 ? 0 : -1; - state = 5; // parameter - break; - - default: - if (match > 0) { - if (match < search.length()) - match = Character.toLowerCase(c) == search.charAt(match) ? (match + 1) : -1; - else if (c != ' ' && c != '\t') - match = -1; - } - break; - - } - break; - - case 2: // In Quoted token - switch (c) { - case '\\': // quoted character - state = 3; - break; - - case '"': // end quote - state = 4; - break; - - default: - if (match >= 0) { - if (match < search.length()) - match = Character.toLowerCase(c) == search.charAt(match) ? (match + 1) : -1; - else - match = -1; - } - } - break; - - case 3: // In Quoted character in quoted token - if (match >= 0) { - if (match < search.length()) - match = Character.toLowerCase(c) == search.charAt(match) ? (match + 1) : -1; - else - match = -1; - } - state = 2; - break; - - case 4: // WS after end quote - switch (c) { - case ' ': // white space - case '\t': // white space - break; - - case ';': - state = 5; // parameter - break; - - case ',': // end token - // Have we matched the token? - if (match == search.length()) - return true; - state = 0; - break; - - default: - // This is an illegal token, just ignore - match = -1; - } - break; - - case 5: // parameter - switch (c) { - case ',': // end token - // Have we matched the token and not q=0? - if (param != ZERO_QUALITY.length() && match == search.length()) - return true; - param = 0; - state = 0; - break; - - case ' ': // white space - case '\t': // white space - break; - - default: - if (param >= 0) { - if (param < ZERO_QUALITY.length()) - param = Character.toLowerCase(c) == ZERO_QUALITY.charAt(param) ? (param + 1) : -1; - else if (c != '0' && c != '.') - param = -1; - } - - } - break; - - default: - throw new IllegalStateException(); - } - } - - return param != ZERO_QUALITY.length() && match == search.length(); - } - - - @Override - public String toString() { - String v = getValue(); - return getName() + ": " + (v == null ? "" : v); - } - - public boolean isSameName(HttpField field) { - @SuppressWarnings("ReferenceEquality") - boolean sameObject = (field == this); - - if (field == null) - return false; - if (sameObject) - return true; - if (header != null && header == field.getHeader()) - return true; - if (name.equalsIgnoreCase(field.getName())) - return true; - return false; - } - - private int nameHashCode() { - int h = this.hash; - int len = name.length(); - if (h == 0 && len > 0) { - for (int i = 0; i < len; i++) { - // simple case insensitive hash - char c = name.charAt(i); - // assuming us-ascii (per last paragraph on http://tools.ietf.org/html/rfc7230#section-3.2.4) - if ((c >= 'a' && c <= 'z')) - c -= 0x20; - h = 31 * h + c; - } - this.hash = h; - } - return h; - } - - @Override - public int hashCode() { - int vhc = Objects.hashCode(value); - if (header == null) - return vhc ^ nameHashCode(); - return vhc ^ header.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof HttpField)) - return false; - HttpField field = (HttpField) o; - if (header != field.getHeader()) - return false; - if (!name.equalsIgnoreCase(field.getName())) - return false; - if (value == null && field.getValue() != null) - return false; - return Objects.equals(value, field.getValue()); - } - - public static class IntValueHttpField extends HttpField { - private final int intValue; - - public IntValueHttpField(HttpHeader header, String name, String value, int intValue) { - super(header, name, value); - this.intValue = intValue; - } - - public IntValueHttpField(HttpHeader header, String name, String value) { - this(header, name, value, Integer.parseInt(value)); - } - - public IntValueHttpField(HttpHeader header, String name, int intValue) { - this(header, name, Integer.toString(intValue), intValue); - } - - public IntValueHttpField(HttpHeader header, int value) { - this(header, header.getValue(), value); - } - - @Override - public int getIntValue() { - return intValue; - } - - @Override - public long getLongValue() { - return intValue; - } - } - - public static class LongValueHttpField extends HttpField { - private final long longValue; - - public LongValueHttpField(HttpHeader header, String name, String value, long longValue) { - super(header, name, value); - this.longValue = longValue; - } - - public LongValueHttpField(HttpHeader header, String name, String value) { - this(header, name, value, Long.parseLong(value)); - } - - public LongValueHttpField(HttpHeader header, String name, long value) { - this(header, name, Long.toString(value), value); - } - - public LongValueHttpField(HttpHeader header, long value) { - this(header, header.getValue(), value); - } - - @Override - public int getIntValue() { - return (int) longValue; - } - - @Override - public long getLongValue() { - return longValue; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpFields.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpFields.java deleted file mode 100644 index 57286693b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpFields.java +++ /dev/null @@ -1,835 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.QuotedStringTokenizer; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.codec.DateGenerator; -import com.fireflysource.net.http.common.codec.DateParser; - -import java.util.*; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -/** - * HTTP Fields. A collection of HTTP header and or Trailer fields. - * - * <p> - * This class is not synchronized as it is expected that modifications will only - * be performed by a single thread. - * - * <p> - * The cookie handling provided by this class is guided by the Servlet - * specification and RFC6265. - */ -public class HttpFields implements Iterable { - - private static final LazyLogger LOG = SystemLogger.create(HttpFields.class); - - private HttpField[] fields; - private int size; - - /** - * Initialize an empty HttpFields. - */ - public HttpFields() { - fields = new HttpField[20]; - } - - /** - * Initialize an empty HttpFields. - * - * @param capacity the capacity of the http fields - */ - public HttpFields(int capacity) { - fields = new HttpField[capacity]; - } - - /** - * Initialize HttpFields from copy. - * - * @param fields the fields to copy data from - */ - public HttpFields(HttpFields fields) { - this.fields = Arrays.copyOf(fields.fields, fields.fields.length + 10); - size = fields.size; - } - - /** - * Get field value without parameters. Some field values can have - * parameters. This method separates the value from the parameters and - * optionally populates a map with the parameters. For example: - * - * <PRE> - * <p> - * FieldName : Value ; param1=val1 ; param2=val2 - * - * </PRE> - * - * @param value The Field value, possibly with parameters. - * @return The value. - */ - public static String stripParameters(String value) { - if (value == null) - return null; - - int i = value.indexOf(';'); - if (i < 0) - return value; - return value.substring(0, i).trim(); - } - - /** - * Get field value parameters. Some field values can have parameters. This - * method separates the value from the parameters and optionally populates a - * map with the parameters. For example: - * - * <PRE> - * <p> - * FieldName : Value ; param1=val1 ; param2=val2 - * - * </PRE> - * - * @param value The Field value, possibly with parameters. - * @param parameters A map to populate with the parameters, or null - * @return The value. - */ - public static String valueParameters(String value, Map parameters) { - if (value == null) - return null; - - int i = value.indexOf(';'); - if (i < 0) - return value; - if (parameters == null) - return value.substring(0, i).trim(); - - StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true); - while (tok1.hasMoreTokens()) { - String token = tok1.nextToken(); - StringTokenizer tok2 = new QuotedStringTokenizer(token, "= "); - if (tok2.hasMoreTokens()) { - String paramName = tok2.nextToken(); - String paramVal = null; - if (tok2.hasMoreTokens()) - paramVal = tok2.nextToken(); - parameters.put(paramName, paramVal); - } - } - - return value.substring(0, i).trim(); - } - - public int size() { - return size; - } - - @Override - public Iterator iterator() { - return new Itr(); - } - - public Stream stream() { - return StreamSupport.stream(Arrays.spliterator(fields, 0, size), false); - } - - /** - * Get Collection of header names. - * - * @return the unique set of field names. - */ - public Set getFieldNamesCollection() { - final Set set = new HashSet<>(size); - for (HttpField f : this) { - if (f != null) - set.add(f.getName()); - } - return set; - } - - /** - * Get enumeration of header _names. Returns an enumeration of strings - * representing the header _names for this request. - * - * @return an enumeration of field names - */ - public Enumeration getFieldNames() { - return Collections.enumeration(getFieldNamesCollection()); - } - - /** - * Get a Field by index. - * - * @param index the field index - * @return A Field value or null if the Field value has not been set - */ - public HttpField getField(int index) { - if (index >= size) - throw new NoSuchElementException(); - return fields[index]; - } - - public HttpField getField(HttpHeader header) { - for (int i = 0; i < size; i++) { - HttpField f = fields[i]; - if (f.getHeader() == header) - return f; - } - return null; - } - - public HttpField getField(String name) { - for (int i = 0; i < size; i++) { - HttpField f = fields[i]; - if (f.getName().equalsIgnoreCase(name)) - return f; - } - return null; - } - - public boolean contains(HttpField field) { - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.isSameName(field) && (f.equals(field) || f.contains(field.getValue()))) - return true; - } - return false; - } - - public boolean contains(HttpHeader header, String value) { - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.getHeader() == header && f.contains(value)) - return true; - } - return false; - } - - public boolean contains(String name, String value) { - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.getName().equalsIgnoreCase(name) && f.contains(value)) - return true; - } - return false; - } - - public boolean contains(HttpHeader header) { - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.getHeader() == header) - return true; - } - return false; - } - - public boolean containsKey(String name) { - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.getName().equalsIgnoreCase(name)) - return true; - } - return false; - } - - @Deprecated - public String getStringField(HttpHeader header) { - return get(header); - } - - public String get(HttpHeader header) { - for (int i = 0; i < size; i++) { - HttpField f = fields[i]; - if (f.getHeader() == header) - return f.getValue(); - } - return null; - } - - @Deprecated - public String getStringField(String name) { - return get(name); - } - - public String get(String header) { - for (int i = 0; i < size; i++) { - HttpField f = fields[i]; - if (f.getName().equalsIgnoreCase(header)) - return f.getValue(); - } - return null; - } - - /** - * Get multiple header of the same name - * - * @param header the header - * @return List the values - */ - public List getValuesList(HttpHeader header) { - final List list = new LinkedList<>(); - for (HttpField f : this) - if (f.getHeader() == header) - list.add(f.getValue()); - return list; - } - - /** - * Get multiple header of the same name - * - * @param name the case-insensitive field name - * @return List the header values - */ - public List getValuesList(String name) { - final List list = new LinkedList<>(); - for (HttpField f : this) - if (f.getName().equalsIgnoreCase(name)) - list.add(f.getValue()); - return list; - } - - /** - * Add comma separated values, but only if not already present. - * - * @param header The header to add the value(s) to - * @param values The value(s) to add - * @return True if headers were modified - */ - public boolean addCSV(HttpHeader header, String... values) { - QuotedCSV existing = null; - for (HttpField f : this) { - if (f.getHeader() == header) { - if (existing == null) - existing = new QuotedCSV(false); - existing.addValue(f.getValue()); - } - } - - String value = addCSV(existing, values); - if (value != null) { - add(header, value); - return true; - } - return false; - } - - /** - * Add comma separated values, but only if not already present. - * - * @param name The header to add the value(s) to - * @param values The value(s) to add - * @return True if headers were modified - */ - public boolean addCSV(String name, String... values) { - QuotedCSV existing = null; - for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) { - if (existing == null) - existing = new QuotedCSV(false); - existing.addValue(f.getValue()); - } - } - String value = addCSV(existing, values); - if (value != null) { - add(name, value); - return true; - } - return false; - } - - protected String addCSV(QuotedCSV existing, String... values) { - // remove any existing values from the new values - boolean add = true; - if (existing != null && !existing.isEmpty()) { - add = false; - - for (int i = values.length; i-- > 0; ) { - String unquoted = QuotedCSV.unquote(values[i]); - if (existing.getValues().contains(unquoted)) - values[i] = null; - else - add = true; - } - } - - if (add) { - StringBuilder value = new StringBuilder(); - for (String v : values) { - if (v == null) - continue; - if (value.length() > 0) - value.append(", "); - value.append(v); - } - if (value.length() > 0) - return value.toString(); - } - - return null; - } - - /** - * Get multiple field values of the same name, split as a {@link QuotedCSV} - * - * @param header The header - * @param keepQuotes True if the fields are kept quoted - * @return List the values with OWS stripped - */ - public List getCSV(HttpHeader header, boolean keepQuotes) { - QuotedCSV values = null; - for (HttpField f : this) { - if (f.getHeader() == header) { - if (values == null) - values = new QuotedCSV(keepQuotes); - values.addValue(f.getValue()); - } - } - return values == null ? Collections.emptyList() : values.getValues(); - } - - /** - * Get multiple field values of the same name as a {@link QuotedCSV} - * - * @param name the case-insensitive field name - * @param keepQuotes True if the fields are kept quoted - * @return List the values with OWS stripped - */ - public List getCSV(String name, boolean keepQuotes) { - QuotedCSV values = null; - for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) { - if (values == null) - values = new QuotedCSV(keepQuotes); - values.addValue(f.getValue()); - } - } - return values == null ? Collections.emptyList() : values.getValues(); - } - - /** - * Get multiple field values of the same name, split and sorted as a - * {@link QuotedQualityCSV} - * - * @param header The header - * @return List the values in quality order with the q param and OWS - * stripped - */ - public List getQualityCSV(HttpHeader header) { - QuotedQualityCSV values = null; - for (HttpField f : this) { - if (f.getHeader() == header) { - if (values == null) - values = new QuotedQualityCSV(); - values.addValue(f.getValue()); - } - } - - return values == null ? Collections.emptyList() : values.getValues(); - } - - /** - * Get multiple field values of the same name, split and sorted as a - * {@link QuotedQualityCSV} - * - * @param name the case-insensitive field name - * @return List the values in quality order with the q param and OWS - * stripped - */ - public List getQualityCSV(String name) { - QuotedQualityCSV values = null; - for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) { - if (values == null) - values = new QuotedQualityCSV(); - values.addValue(f.getValue()); - } - } - return values == null ? Collections.emptyList() : values.getValues(); - } - - /** - * Get multi headers - * - * @param name the case-insensitive field name - * @return Enumeration of the values - */ - public Enumeration getValues(final String name) { - for (int i = 0; i < size; i++) { - final HttpField f = fields[i]; - - if (f.getName().equalsIgnoreCase(name) && f.getValue() != null) { - final int first = i; - return new Enumeration() { - HttpField field = f; - int i = first + 1; - - @Override - public boolean hasMoreElements() { - if (field == null) { - while (i < size) { - field = fields[i++]; - if (field.getName().equalsIgnoreCase(name) && field.getValue() != null) - return true; - } - field = null; - return false; - } - return true; - } - - @Override - public String nextElement() throws NoSuchElementException { - if (hasMoreElements()) { - String value = field.getValue(); - field = null; - return value; - } - throw new NoSuchElementException(); - } - }; - } - } - - List empty = Collections.emptyList(); - return Collections.enumeration(empty); - } - - public void put(HttpField field) { - boolean put = false; - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.isSameName(field)) { - if (put) { - System.arraycopy(fields, i + 1, fields, i, --size - i); - } else { - fields[i] = field; - put = true; - } - } - } - if (!put) - add(field); - } - - /** - * Set a field. - * - * @param name the name of the field - * @param value the value of the field. If null the field is cleared. - */ - public void put(String name, String value) { - if (value == null) - remove(name); - else - put(new HttpField(name, value)); - } - - public void put(HttpHeader header, HttpHeaderValue value) { - put(header, value.toString()); - } - - /** - * Set a field. - * - * @param header the header name of the field - * @param value the value of the field. If null the field is cleared. - */ - public void put(HttpHeader header, String value) { - if (value == null) - remove(header); - else - put(new HttpField(header, value)); - } - - /** - * Set a field. - * - * @param name the name of the field - * @param list the List value of the field. If null the field is cleared. - */ - public void put(String name, List list) { - remove(name); - for (String v : list) - if (v != null) - add(name, v); - } - - /** - * Add to or set a field. If the field is allowed to have multiple values, - * add will add multiple headers of the same name. - * - * @param name the name of the field - * @param value the value of the field. - */ - public void add(String name, String value) { - if (value == null) - return; - - HttpField field = new HttpField(name, value); - add(field); - } - - public void add(HttpHeader header, HttpHeaderValue value) { - add(header, value.toString()); - } - - /** - * Add to or set a field. If the field is allowed to have multiple values, - * add will add multiple headers of the same name. - * - * @param header the header - * @param value the value of the field. - */ - public void add(HttpHeader header, String value) { - if (value == null) - throw new IllegalArgumentException("null value"); - - HttpField field = new HttpField(header, value); - add(field); - } - - /** - * Remove a field. - * - * @param name the field to remove - * @return the header that was removed - */ - public HttpField remove(HttpHeader name) { - HttpField removed = null; - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.getHeader() == name) { - removed = f; - System.arraycopy(fields, i + 1, fields, i, --size - i); - } - } - return removed; - } - - /** - * Remove a field. - * - * @param name the field to remove - * @return the header that was removed - */ - public HttpField remove(String name) { - HttpField removed = null; - for (int i = size; i-- > 0; ) { - HttpField f = fields[i]; - if (f.getName().equalsIgnoreCase(name)) { - removed = f; - System.arraycopy(fields, i + 1, fields, i, --size - i); - } - } - return removed; - } - - /** - * Get a header as an long value. Returns the value of an integer field or - * -1 if not found. The case of the field name is ignored. - * - * @param name the case-insensitive field name - * @return the value of the field as a long - * @throws NumberFormatException If bad long found - */ - public long getLongField(String name) throws NumberFormatException { - HttpField field = getField(name); - return field == null ? -1L : field.getLongValue(); - } - - /** - * Get a header as a date value. Returns the value of a date field, or -1 if - * not found. The case of the field name is ignored. - * - * @param name the case-insensitive field name - * @return the value of the field as a number of milliseconds since unix - * epoch - */ - public long getDateField(String name) { - HttpField field = getField(name); - if (field == null) - return -1; - - String val = valueParameters(field.getValue(), null); - if (val == null) - return -1; - - final long date = DateParser.parseDate(val); - if (date == -1) - throw new IllegalArgumentException("Cannot convert date: " + val); - return date; - } - - /** - * Sets the value of an long field. - * - * @param name the field name - * @param value the field long value - */ - public void putLongField(HttpHeader name, long value) { - String v = Long.toString(value); - put(name, v); - } - - /** - * Sets the value of an long field. - * - * @param name the field name - * @param value the field long value - */ - public void putLongField(String name, long value) { - String v = Long.toString(value); - put(name, v); - } - - /** - * Sets the value of a date field. - * - * @param name the field name - * @param date the field date value - */ - public void putDateField(HttpHeader name, long date) { - String d = DateGenerator.formatDate(date); - put(name, d); - } - - /** - * Sets the value of a date field. - * - * @param name the field name - * @param date the field date value - */ - public void putDateField(String name, long date) { - String d = DateGenerator.formatDate(date); - put(name, d); - } - - /** - * Sets the value of a date field. - * - * @param name the field name - * @param date the field date value - */ - public void addDateField(String name, long date) { - String d = DateGenerator.formatDate(date); - add(name, d); - } - - @Override - public int hashCode() { - int hash = 0; - for (HttpField field : fields) - hash += field.hashCode(); - return hash; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof HttpFields)) - return false; - - HttpFields that = (HttpFields) o; - - // Order is not important, so we cannot rely on List.equals(). - if (size() != that.size()) - return false; - - loop: - for (HttpField fi : this) { - for (HttpField fa : that) { - if (fi.equals(fa)) - continue loop; - } - return false; - } - return true; - } - - @Override - public String toString() { - try { - StringBuilder buffer = new StringBuilder(); - for (HttpField field : this) { - if (field != null) { - String tmp = field.getName(); - if (tmp != null) - buffer.append(tmp); - buffer.append(": "); - tmp = field.getValue(); - if (tmp != null) - buffer.append(tmp); - buffer.append("\r\n"); - } - } - buffer.append("\r\n"); - return buffer.toString(); - } catch (Exception e) { - LOG.warn("http fields toString exception", e); - return e.toString(); - } - } - - public void clear() { - size = 0; - } - - public void add(HttpField field) { - if (field != null) { - if (size == fields.length) - fields = Arrays.copyOf(fields, size * 2); - fields[size++] = field; - } - } - - public void addAll(HttpFields fields) { - for (int i = 0; i < fields.size; i++) - add(fields.fields[i]); - } - - /** - * Add fields from another HttpFields instance. Single valued fields are - * replaced, while all others are added. - * - * @param fields the fields to add - */ - public void add(HttpFields fields) { - if (fields == null) - return; - - Enumeration e = fields.getFieldNames(); - while (e.hasMoreElements()) { - String name = e.nextElement(); - Enumeration values = fields.getValues(name); - while (values.hasMoreElements()) - add(name, values.nextElement()); - } - } - - private class Itr implements Iterator { - int cursor; // index of next element to return - int last = -1; - - public boolean hasNext() { - return cursor != size; - } - - public HttpField next() { - int i = cursor; - if (i >= size) - throw new NoSuchElementException(); - cursor = i + 1; - return fields[last = i]; - } - - public void remove() { - if (last < 0) - throw new IllegalStateException(); - - System.arraycopy(fields, last + 1, fields, last, --size - last); - cursor = last; - last = -1; - } - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpHeader.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpHeader.java deleted file mode 100644 index 5b1065951..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpHeader.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.string.StringUtils; - - -public enum HttpHeader { - - /** - * General Fields. - */ - CONNECTION("Connection"), - CACHE_CONTROL("Cache-Control"), - DATE("Date"), - PRAGMA("Pragma"), - PROXY_CONNECTION("Proxy-Connection"), - TRAILER("Trailer"), - TRANSFER_ENCODING("Transfer-Encoding"), - UPGRADE("Upgrade"), - VIA("Via"), - WARNING("Warning"), - NEGOTIATE("Negotiate"), - - /** - * Entity Fields. - */ - ALLOW("Allow"), - CONTENT_ENCODING("Content-Encoding"), - CONTENT_LANGUAGE("Content-Language"), - CONTENT_LENGTH("Content-Length"), - CONTENT_LOCATION("Content-Location"), - CONTENT_MD5("Content-MD5"), - CONTENT_RANGE("Content-Range"), - CONTENT_TYPE("Content-Type"), - EXPIRES("Expires"), - LAST_MODIFIED("Last-Modified"), - - /** - * Request Fields. - */ - ACCEPT("Accept"), - ACCEPT_CHARSET("Accept-Charset"), - ACCEPT_ENCODING("Accept-Encoding"), - ACCEPT_LANGUAGE("Accept-Language"), - AUTHORIZATION("Authorization"), - EXPECT("Expect"), - FORWARDED("Forwarded"), - FROM("From"), - HOST("Host"), - IF_MATCH("If-Match"), - IF_MODIFIED_SINCE("If-Modified-Since"), - IF_NONE_MATCH("If-None-Match"), - IF_RANGE("If-Range"), - IF_UNMODIFIED_SINCE("If-Unmodified-Since"), - KEEP_ALIVE("Keep-Alive"), - MAX_FORWARDS("Max-Forwards"), - PROXY_AUTHORIZATION("Proxy-Authorization"), - RANGE("Range"), - REQUEST_RANGE("Request-Range"), - REFERER("Referer"), - TE("TE"), - USER_AGENT("User-Agent"), - X_FORWARDED_FOR("X-Forwarded-For"), - X_FORWARDED_PROTO("X-Forwarded-Proto"), - X_FORWARDED_SERVER("X-Forwarded-Server"), - X_FORWARDED_HOST("X-Forwarded-Host"), - - /** - * Response Fields. - */ - ACCEPT_RANGES("Accept-Ranges"), - AGE("Age"), - ETAG("ETag"), - LOCATION("Location"), - PROXY_AUTHENTICATE("Proxy-Authenticate"), - RETRY_AFTER("Retry-After"), - SERVER("Server"), - SERVLET_ENGINE("Servlet-Engine"), - VARY("Vary"), - WWW_AUTHENTICATE("WWW-Authenticate"), - - /** - * WebSocket Fields. - */ - ORIGIN("Origin"), - SEC_WEBSOCKET_KEY("Sec-WebSocket-Key"), - SEC_WEBSOCKET_VERSION("Sec-WebSocket-Version"), - SEC_WEBSOCKET_EXTENSIONS("Sec-WebSocket-Extensions"), - SEC_WEBSOCKET_SUBPROTOCOL("Sec-WebSocket-Protocol"), - SEC_WEBSOCKET_ACCEPT("Sec-WebSocket-Accept"), - - /** - * Other Fields. - */ - COOKIE("Cookie"), - SET_COOKIE("Set-Cookie"), - SET_COOKIE2("Set-Cookie2"), - MIME_VERSION("MIME-Version"), - IDENTITY("identity"), - - X_POWERED_BY("X-Powered-By"), - HTTP2_SETTINGS("HTTP2-Settings"), - - STRICT_TRANSPORT_SECURITY("Strict-Transport-Security"), - - /** - * HTTP2 Fields. - */ - C_METHOD(":method"), - C_SCHEME(":scheme"), - C_AUTHORITY(":authority"), - C_PATH(":path"), - C_STATUS(":status"), - - /** - * CORS Fields - */ - ACCESS_CONTROL_ALLOW_ORIGIN("Access-Control-Allow-Origin"), - ACCESS_CONTROL_EXPOSE_HEADERS("Access-Control-Expose-Headers"), - ACCESS_CONTROL_MAX_AGE("Access-Control-Max-Age"), - ACCESS_CONTROL_ALLOW_CREDENTIALS("Access-Control-Allow-Credentials"), - ACCESS_CONTROL_ALLOW_METHODS("Access-Control-Allow-Methods"), - ACCESS_CONTROL_ALLOW_HEADERS("Access-Control-Allow-Headers"), - ACCESS_CONTROL_REQUEST_METHOD("Access-Control-Request-Method"), - ACCESS_CONTROL_REQUEST_HEADERS("Access-Control-Request-Headers"), - - UNKNOWN("::UNKNOWN::"); - - public final static Trie CACHE = new ArrayTrie<>(700); - - static { - for (HttpHeader header : HttpHeader.values()) { - if (header != UNKNOWN) { - if (!CACHE.put(header.toString(), header)) { - throw new IllegalStateException(); - } - } - } - } - - private final String value; - private final String lowerCaseValue; - private final byte[] bytes; - private final byte[] bytesColonSpace; - - HttpHeader(String value) { - this.value = value; - this.lowerCaseValue = StringUtils.asciiToLowerCase(value); - bytes = StringUtils.getUtf8Bytes(value); - bytesColonSpace = StringUtils.getUtf8Bytes(value + ": "); - } - - public byte[] getBytes() { - return bytes; - } - - public byte[] getBytesColonSpace() { - return bytesColonSpace; - } - - public boolean is(String value) { - return this.value.equalsIgnoreCase(value); - } - - public String getValue() { - return value; - } - - public String getLowerCaseValue() { - return lowerCaseValue; - } - - @Override - public String toString() { - return value; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpHeaderValue.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpHeaderValue.java deleted file mode 100644 index 790ac70bd..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpHeaderValue.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.string.StringUtils; - -import java.util.EnumSet; - -/** - * - */ -public enum HttpHeaderValue { - - CLOSE("close"), - CHUNKED("chunked"), - GZIP("gzip"), - COMPRESS("compress"), - DEFLATE("deflate"), - BR("br"), - IDENTITY("identity"), - KEEP_ALIVE("keep-alive"), - CONTINUE("100-continue"), - PROCESSING("102-processing"), - TE("TE"), - BYTES("bytes"), - NO_CACHE("no-cache"), - UPGRADE("Upgrade"), - UNKNOWN("::UNKNOWN::"); - - public final static Trie CACHE = new ArrayTrie<>(); - private static final EnumSet __known = EnumSet.of( - HttpHeader.CONNECTION, - HttpHeader.TRANSFER_ENCODING, - HttpHeader.CONTENT_ENCODING); - - static { - for (HttpHeaderValue value : HttpHeaderValue.values()) - if (value != UNKNOWN) - CACHE.put(value.toString(), value); - } - - private final String value; - private final byte[] bytes; - - HttpHeaderValue(String value) { - this.value = value; - bytes = StringUtils.getUtf8Bytes(value); - } - - public static boolean hasKnownValues(HttpHeader header) { - if (header == null) - return false; - return __known.contains(header); - } - - public boolean is(String value) { - return this.value.equalsIgnoreCase(value); - } - - public String getValue() { - return value; - } - - public byte[] getBytes() { - return bytes; - } - - @Override - public String toString() { - return value; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpMethod.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpMethod.java deleted file mode 100644 index dcfc2e38b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpMethod.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.trie.ArrayTernaryTrie; -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.string.StringUtils; - -import java.nio.ByteBuffer; - -/** - * @author Pengtao Qiu - */ -public enum HttpMethod { - GET, - POST, - HEAD, - PUT, - PATCH, - OPTIONS, - DELETE, - TRACE, - CONNECT, - MOVE, - PROXY, - PRI; - - public final static Trie CACHE = new ArrayTernaryTrie<>(false); - public final static Trie INSENSITIVE_CACHE = new ArrayTrie<>(); - - static { - for (HttpMethod method : HttpMethod.values()) - CACHE.put(method.toString(), method); - } - - static { - for (HttpMethod method : HttpMethod.values()) - INSENSITIVE_CACHE.put(method.toString(), method); - } - - private final String value; - private final byte[] bytes; - - HttpMethod() { - value = this.name(); - bytes = StringUtils.getUtf8Bytes(value); - } - - public static HttpMethod from(String value) { - return CACHE.get(value); - } - - /** - * Optimized lookup to find a method name and trailing space in a byte array. - * - * @param bytes Array containing ISO-8859-1 characters - * @param position The first valid index - * @param limit The first non valid index - * @return A HttpMethod if a match or null if no easy match. - */ - public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit) { - int length = limit - position; - if (length < 4) - return null; - switch (bytes[position]) { - case 'G': - if (bytes[position + 1] == 'E' && bytes[position + 2] == 'T' && bytes[position + 3] == ' ') - return GET; - break; - case 'P': - if (bytes[position + 1] == 'O' && bytes[position + 2] == 'S' && bytes[position + 3] == 'T' && length >= 5 && bytes[position + 4] == ' ') - return POST; - if (bytes[position + 1] == 'R' && bytes[position + 2] == 'O' && bytes[position + 3] == 'X' && length >= 6 && bytes[position + 4] == 'Y' && bytes[position + 5] == ' ') - return PROXY; - if (bytes[position + 1] == 'U' && bytes[position + 2] == 'T' && bytes[position + 3] == ' ') - return PUT; - if (bytes[position + 1] == 'R' && bytes[position + 2] == 'I' && bytes[position + 3] == ' ') - return PRI; - break; - case 'H': - if (bytes[position + 1] == 'E' && bytes[position + 2] == 'A' && bytes[position + 3] == 'D' && length >= 5 && bytes[position + 4] == ' ') - return HEAD; - break; - case 'O': - if (bytes[position + 1] == 'P' && bytes[position + 2] == 'T' && bytes[position + 3] == 'I' && length >= 8 && - bytes[position + 4] == 'O' && bytes[position + 5] == 'N' && bytes[position + 6] == 'S' && bytes[position + 7] == ' ') - return OPTIONS; - break; - case 'D': - if (bytes[position + 1] == 'E' && bytes[position + 2] == 'L' && bytes[position + 3] == 'E' && length >= 7 && - bytes[position + 4] == 'T' && bytes[position + 5] == 'E' && bytes[position + 6] == ' ') - return DELETE; - break; - case 'T': - if (bytes[position + 1] == 'R' && bytes[position + 2] == 'A' && bytes[position + 3] == 'C' && length >= 6 && - bytes[position + 4] == 'E' && bytes[position + 5] == ' ') - return TRACE; - break; - case 'C': - if (bytes[position + 1] == 'O' && bytes[position + 2] == 'N' && bytes[position + 3] == 'N' && length >= 8 && - bytes[position + 4] == 'E' && bytes[position + 5] == 'C' && bytes[position + 6] == 'T' && bytes[position + 7] == ' ') - return CONNECT; - break; - case 'M': - if (bytes[position + 1] == 'O' && bytes[position + 2] == 'V' && bytes[position + 3] == 'E' && length >= 5 && bytes[position + 4] == ' ') - return MOVE; - break; - - default: - break; - } - return null; - } - - /** - * Optimized lookup to find a method name and trailing space in a byte array. - * - * @param buffer buffer containing ISO-8859-1 characters, it is not modified. - * @return A HttpMethod if a match or null if no easy match. - */ - public static HttpMethod lookAheadGet(ByteBuffer buffer) { - if (buffer.hasArray()) - return lookAheadGet(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.arrayOffset() + buffer.limit()); - - int l = buffer.remaining(); - if (l >= 4) { - HttpMethod m = CACHE.getBest(buffer, 0, l); - if (m != null) { - int ml = m.getValue().length(); - if (l > ml && buffer.get(buffer.position() + ml) == ' ') - return m; - } - } - return null; - } - - public boolean is(String value) { - return this.value.equalsIgnoreCase(value); - } - - public String getValue() { - return value; - } - - public byte[] getBytes() { - return bytes; - } - - @Override - public String toString() { - return value; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpScheme.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpScheme.java deleted file mode 100644 index a182ddbda..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpScheme.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.fireflysource.net.http.common.model; - - -import com.fireflysource.common.string.StringUtils; - -import java.util.HashMap; -import java.util.Map; - -public enum HttpScheme { - HTTP("http"), HTTPS("https"), WS("ws"), WSS("wss"); - - private final String value; - private final byte[] bytes; - - HttpScheme(String value) { - this.value = value; - bytes = StringUtils.getUtf8Bytes(value); - Holder.cache.put(value, this); - } - - public static HttpScheme from(String value) { - return Holder.cache.get(value); - } - - public boolean is(String value) { - return this.value.equalsIgnoreCase(value); - } - - public String getValue() { - return value; - } - - public byte[] getBytes() { - return bytes; - } - - @Override - public String toString() { - return value; - } - - private static class Holder { - private static final Map cache = new HashMap<>(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpStatus.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpStatus.java deleted file mode 100644 index 3e50bbf5a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpStatus.java +++ /dev/null @@ -1,353 +0,0 @@ -package com.fireflysource.net.http.common.model; - -/** - *

    - * Http Status Codes - *

    - * - * @see IANA HTTP - * Status Code Registry - */ -public class HttpStatus { - public final static int CONTINUE_100 = 100; - public final static int SWITCHING_PROTOCOLS_101 = 101; - public final static int PROCESSING_102 = 102; - - public final static int OK_200 = 200; - public final static int CREATED_201 = 201; - public final static int ACCEPTED_202 = 202; - public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203; - public final static int NO_CONTENT_204 = 204; - public final static int RESET_CONTENT_205 = 205; - public final static int PARTIAL_CONTENT_206 = 206; - public final static int MULTI_STATUS_207 = 207; - - public final static int MULTIPLE_CHOICES_300 = 300; - public final static int MOVED_PERMANENTLY_301 = 301; - public final static int MOVED_TEMPORARILY_302 = 302; - public final static int FOUND_302 = 302; - public final static int SEE_OTHER_303 = 303; - public final static int NOT_MODIFIED_304 = 304; - public final static int USE_PROXY_305 = 305; - public final static int TEMPORARY_REDIRECT_307 = 307; - public final static int PERMANENT_REDIRECT_308 = 308; - - public final static int BAD_REQUEST_400 = 400; - public final static int UNAUTHORIZED_401 = 401; - public final static int PAYMENT_REQUIRED_402 = 402; - public final static int FORBIDDEN_403 = 403; - public final static int NOT_FOUND_404 = 404; - public final static int METHOD_NOT_ALLOWED_405 = 405; - public final static int NOT_ACCEPTABLE_406 = 406; - public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407; - public final static int REQUEST_TIMEOUT_408 = 408; - public final static int CONFLICT_409 = 409; - public final static int GONE_410 = 410; - public final static int LENGTH_REQUIRED_411 = 411; - public final static int PRECONDITION_FAILED_412 = 412; - @Deprecated - public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413; - public final static int PAYLOAD_TOO_LARGE_413 = 413; - @Deprecated - public final static int REQUEST_URI_TOO_LONG_414 = 414; - public final static int URI_TOO_LONG_414 = 414; - public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415; - @Deprecated - public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416; - public final static int RANGE_NOT_SATISFIABLE_416 = 416; - public final static int EXPECTATION_FAILED_417 = 417; - public final static int IM_A_TEAPOT_418 = 418; - public final static int ENHANCE_YOUR_CALM_420 = 420; - public final static int MISDIRECTED_REQUEST_421 = 421; - public final static int UNPROCESSABLE_ENTITY_422 = 422; - public final static int LOCKED_423 = 423; - public final static int FAILED_DEPENDENCY_424 = 424; - public final static int UPGRADE_REQUIRED_426 = 426; - public final static int PRECONDITION_REQUIRED_428 = 428; - public final static int TOO_MANY_REQUESTS_429 = 429; - public final static int REQUEST_HEADER_FIELDS_TOO_LARGE_431 = 431; - public final static int UNAVAILABLE_FOR_LEGAL_REASONS_451 = 451; - - public final static int INTERNAL_SERVER_ERROR_500 = 500; - public final static int NOT_IMPLEMENTED_501 = 501; - public final static int BAD_GATEWAY_502 = 502; - public final static int SERVICE_UNAVAILABLE_503 = 503; - public final static int GATEWAY_TIMEOUT_504 = 504; - public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505; - public final static int INSUFFICIENT_STORAGE_507 = 507; - public final static int LOOP_DETECTED_508 = 508; - public final static int NOT_EXTENDED_510 = 510; - public final static int NETWORK_AUTHENTICATION_REQUIRED_511 = 511; - - public static final int MAX_CODE = 511; - - private static final Code[] codeMap = new Code[MAX_CODE + 1]; - - static { - for (Code code : Code.values()) { - codeMap[code.code] = code; - } - } - - /** - * Get the HttpStatusCode for a specific code - * - * @param code the code to lookup. - * @return the {@link HttpStatus} if found, or null if not found. - */ - public static Code getCode(int code) { - if (code <= MAX_CODE) { - return codeMap[code]; - } - return null; - } - - /** - * Get the status message for a specific code. - * - * @param code the code to look up - * @return the specific message, or the code number itself if code does not - * match known list. - */ - public static String getMessage(int code) { - Code codeEnum = getCode(code); - if (codeEnum != null) { - return codeEnum.getMessage(); - } else { - return Integer.toString(code); - } - } - - /** - * Simple test against an code to determine if it falls into the - * Informational message category as defined in the - * RFC 1945 - HTTP/1.0, and - * RFC 7231 - HTTP/1.1. - * - * @param code the code to test. - * @return true if within range of codes that belongs to - * Informational messages. - */ - public static boolean isInformational(int code) { - return ((100 <= code) && (code <= 199)); - } - - /** - * Simple test against an code to determine if it falls into the - * Success message category as defined in the - * RFC 1945 - HTTP/1.0, and - * RFC 7231 - HTTP/1.1. - * - * @param code the code to test. - * @return true if within range of codes that belongs to - * Success messages. - */ - public static boolean isSuccess(int code) { - return ((200 <= code) && (code <= 299)); - } - - /** - * Simple test against an code to determine if it falls into the - * Redirection message category as defined in the - * RFC 1945 - HTTP/1.0, and - * RFC 7231 - HTTP/1.1. - * - * @param code the code to test. - * @return true if within range of codes that belongs to - * Redirection messages. - */ - public static boolean isRedirection(int code) { - return ((300 <= code) && (code <= 399)); - } - - /** - * Simple test against an code to determine if it falls into the - * Client Error message category as defined in the - * RFC 1945 - HTTP/1.0, and - * RFC 7231 - HTTP/1.1. - * - * @param code the code to test. - * @return true if within range of codes that belongs to - * Client Error messages. - */ - public static boolean isClientError(int code) { - return ((400 <= code) && (code <= 499)); - } - - /** - * Simple test against an code to determine if it falls into the - * Server Error message category as defined in the - * RFC 1945 - HTTP/1.0, and - * RFC 7231 - HTTP/1.1. - * - * @param code the code to test. - * @return true if within range of codes that belongs to - * Server Error messages. - */ - public static boolean isServerError(int code) { - return ((500 <= code) && (code <= 599)); - } - - public enum Code { - CONTINUE(CONTINUE_100, "Continue"), - SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"), - PROCESSING(PROCESSING_102, "Processing"), - - - OK(OK_200, "OK"), - CREATED(CREATED_201, "Created"), - ACCEPTED(ACCEPTED_202, "Accepted"), - NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"), - NO_CONTENT(NO_CONTENT_204, "No Content"), - RESET_CONTENT(RESET_CONTENT_205, "Reset Content"), - PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"), - MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"), - - MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"), - MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"), - MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"), - FOUND(FOUND_302, "Found"), - SEE_OTHER(SEE_OTHER_303, "See Other"), - NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"), - USE_PROXY(USE_PROXY_305, "Use Proxy"), - TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"), - PERMANET_REDIRECT(PERMANENT_REDIRECT_308, "Permanent Redirect"), - - BAD_REQUEST(BAD_REQUEST_400, "Bad Request"), - UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"), - PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"), - FORBIDDEN(FORBIDDEN_403, "Forbidden"), - NOT_FOUND(NOT_FOUND_404, "Not Found"), - METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"), - NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"), - PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"), - REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"), - CONFLICT(CONFLICT_409, "Conflict"), - GONE(GONE_410, "Gone"), - LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"), - PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"), - PAYLOAD_TOO_LARGE(PAYLOAD_TOO_LARGE_413, "Payload Too Large"), - URI_TOO_LONG(URI_TOO_LONG_414, "URI Too Long"), - UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"), - RANGE_NOT_SATISFIABLE(RANGE_NOT_SATISFIABLE_416, "Range Not Satisfiable"), - EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"), - IM_A_TEAPOT(IM_A_TEAPOT_418, "I'm a Teapot"), - ENHANCE_YOUR_CALM(ENHANCE_YOUR_CALM_420, "Enhance your Calm"), - MISDIRECTED_REQUEST(MISDIRECTED_REQUEST_421, "Misdirected Request"), - UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"), - LOCKED(LOCKED_423, "Locked"), - FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"), - UPGRADE_REQUIRED(UPGRADE_REQUIRED_426, "Upgrade Required"), - PRECONDITION_REQUIRED(PRECONDITION_REQUIRED_428, "Precondition Required"), - TOO_MANY_REQUESTS(TOO_MANY_REQUESTS_429, "Too Many Requests"), - REQUEST_HEADER_FIELDS_TOO_LARGE(REQUEST_HEADER_FIELDS_TOO_LARGE_431, "Request Header Fields Too Large"), - UNAVAILABLE_FOR_LEGAL_REASONS(UNAVAILABLE_FOR_LEGAL_REASONS_451, "Unavailable for Legal Reason"), - - INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"), - NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"), - BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"), - SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"), - GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"), - HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"), - INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage"), - LOOP_DETECTED(LOOP_DETECTED_508, "Loop Detected"), - NOT_EXTENDED(NOT_EXTENDED_510, "Not Extended"), - NETWORK_AUTHENTICATION_REQUIRED(NETWORK_AUTHENTICATION_REQUIRED_511, "Network Authentication Required"), - ; - - private final int code; - private final String message; - - Code(int code, String message) { - this.code = code; - this.message = message; - } - - public int getCode() { - return code; - } - - public String getMessage() { - return message; - } - - public boolean equals(int code) { - return (this.code == code); - } - - @Override - public String toString() { - return String.format("[%03d %s]", this.code, this.getMessage()); - } - - /** - * Simple test against an code to determine if it falls into the - * Informational message category as defined in the - * RFC 1945 - HTTP/1.0, - * and RFC 7231 - - * HTTP/1.1. - * - * @return true if within range of codes that belongs to - * Informational messages. - */ - public boolean isInformational() { - return HttpStatus.isInformational(this.code); - } - - /** - * Simple test against an code to determine if it falls into the - * Success message category as defined in the - * RFC 1945 - HTTP/1.0, - * and RFC 7231 - - * HTTP/1.1. - * - * @return true if within range of codes that belongs to - * Success messages. - */ - public boolean isSuccess() { - return HttpStatus.isSuccess(this.code); - } - - /** - * Simple test against an code to determine if it falls into the - * Redirection message category as defined in the - * RFC 1945 - HTTP/1.0, - * and RFC 7231 - - * HTTP/1.1. - * - * @return true if within range of codes that belongs to - * Redirection messages. - */ - public boolean isRedirection() { - return HttpStatus.isRedirection(this.code); - } - - /** - * Simple test against an code to determine if it falls into the - * Client Error message category as defined in the - * RFC 1945 - HTTP/1.0, - * and RFC 7231 - - * HTTP/1.1. - * - * @return true if within range of codes that belongs to - * Client Error messages. - */ - public boolean isClientError() { - return HttpStatus.isClientError(this.code); - } - - /** - * Simple test against an code to determine if it falls into the - * Server Error message category as defined in the - * RFC 1945 - HTTP/1.0, - * and RFC 7231 - - * HTTP/1.1. - * - * @return true if within range of codes that belongs to - * Server Error messages. - */ - public boolean isServerError() { - return HttpStatus.isServerError(this.code); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpTokens.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpTokens.java deleted file mode 100644 index 82a0bfeb7..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpTokens.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.fireflysource.net.http.common.model; - - -import com.fireflysource.common.object.TypeUtils; - -/** - * HTTP constants - */ -public class HttpTokens { - public static final byte COLON = (byte) ':'; - public static final byte TAB = 0x09; - public static final byte LINE_FEED = 0x0A; - public static final byte CARRIAGE_RETURN = 0x0D; - public static final byte SPACE = 0x20; - public static final byte[] CRLF = {CARRIAGE_RETURN, LINE_FEED}; - public final static Token[] TOKENS = new Token[256]; - - static { - for (int b = 0; b < 256; b++) { - // token = 1*tchar - // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" - // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" - // / DIGIT / ALPHA - // ; any VCHAR, except delimiters - // quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE - // qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text - // obs-text = %x80-FF - // comment = "(" *( ctext / quoted-pair / comment ) ")" - // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text - // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) - - switch (b) { - case LINE_FEED: - TOKENS[b] = new Token((byte) b, Type.LF); - break; - case CARRIAGE_RETURN: - TOKENS[b] = new Token((byte) b, Type.CR); - break; - case SPACE: - TOKENS[b] = new Token((byte) b, Type.SPACE); - break; - case TAB: - TOKENS[b] = new Token((byte) b, Type.HTAB); - break; - case COLON: - TOKENS[b] = new Token((byte) b, Type.COLON); - break; - - case '!': - case '#': - case '$': - case '%': - case '&': - case '\'': - case '*': - case '+': - case '-': - case '.': - case '^': - case '_': - case '`': - case '|': - case '~': - TOKENS[b] = new Token((byte) b, Type.TCHAR); - break; - - default: - if (b >= 0x30 && b <= 0x39) // DIGIT - TOKENS[b] = new Token((byte) b, Type.DIGIT); - else if (b >= 0x41 && b <= 0x5A) // ALPHA (uppercase) - TOKENS[b] = new Token((byte) b, Type.ALPHA); - else if (b >= 0x61 && b <= 0x7A) // ALPHA (lowercase) - TOKENS[b] = new Token((byte) b, Type.ALPHA); - else if (b >= 0x21 && b <= 0x7E) // Visible - TOKENS[b] = new Token((byte) b, Type.VCHAR); - else if (b >= 0x80) // OBS - TOKENS[b] = new Token((byte) b, Type.OTEXT); - else - TOKENS[b] = new Token((byte) b, Type.CNTL); - } - } - } - - public enum EndOfContent {UNKNOWN_CONTENT, NO_CONTENT, EOF_CONTENT, CONTENT_LENGTH, CHUNKED_CONTENT} - - public enum Type { - CNTL, // Control characters excluding LF, CR - HTAB, // Horizontal tab - LF, // Line feed - CR, // Carriage return - SPACE, // Space - COLON, // Colon character - DIGIT, // Digit - ALPHA, // Alpha - TCHAR, // token characters excluding COLON,DIGIT,ALPHA, which is equivalent to VCHAR excluding delimiters - VCHAR, // Visible characters excluding COLON,DIGIT,ALPHA - OTEXT // Obsolete text - } - - public static class Token { - private final Type type; - private final byte b; - private final char c; - private final int hex; - - private Token(byte b, Type type) { - this.type = type; - this.b = b; - c = (char) (0xff & b); - char lc = (c >= 'A' & c <= 'Z') ? ((char) (c - 'A' + 'a')) : c; - hex = (this.type == Type.DIGIT || this.type == Type.ALPHA && lc >= 'a' && lc <= 'f') ? TypeUtils.convertHexDigit(b) : -1; - } - - public Type getType() { - return type; - } - - public byte getByte() { - return b; - } - - public char getChar() { - return c; - } - - public boolean isHexDigit() { - return hex >= 0; - } - - public int getHexDigit() { - return hex; - } - - @Override - public String toString() { - switch (type) { - case SPACE: - case COLON: - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - return type + "='" + c + "'"; - - case CR: - return "CR=\\r"; - - case LF: - return "LF=\\n"; - - default: - return String.format("%s=0x%x", type, b); - } - } - - } -} - diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpURI.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpURI.java deleted file mode 100644 index eebe7e6c5..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpURI.java +++ /dev/null @@ -1,663 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.map.MultiMap; -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.net.http.common.codec.URIUtils; -import com.fireflysource.net.http.common.codec.UrlEncoded; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -/** - * Http URI. Parse a HTTP URI from a string or byte array. Given a URI - * http://user@host:port/path/info;param?query#fragment this class - * will split it into the following undecoded optional elements: - *
      - *
    • {@link #getScheme()} - http:
    • - *
    • {@link #getAuthority()} - //name@host:port
    • - *
    • {@link #getHost()} - host
    • - *
    • {@link #getPort()} - port
    • - *
    • {@link #getPath()} - /path/info
    • - *
    • {@link #getParam()} - param
    • - *
    • {@link #getQuery()} - query
    • - *
    • {@link #getFragment()} - fragment
    • - *
    - * - *

    - * Any parameters will be returned from {@link #getPath()}, but are excluded - * from the return value of {@link #getDecodedPath()}. If there are multiple - * parameters, the {@link #getParam()} method returns only the last one. - */ -public class HttpURI { - String uri; - String decodedPath; - private String scheme; - private String user; - private String host; - private int port; - private String path; - private String param; - private String query; - private String fragment; - - public HttpURI() { - } - - public HttpURI(String scheme, String host, int port, String path, String param, String query, String fragment) { - this.scheme = scheme; - this.host = host; - this.port = port; - this.path = path; - this.param = param; - this.query = query; - this.fragment = fragment; - } - - public HttpURI(HttpURI uri) { - this(uri.scheme, uri.host, uri.port, uri.path, uri.param, uri.query, uri.fragment); - this.uri = uri.uri; - } - - public HttpURI(String uri) { - port = -1; - parse(State.START, uri, 0, uri.length()); - } - - public HttpURI(URI uri) { - this.uri = null; - - scheme = uri.getScheme(); - host = uri.getHost(); - if (host == null && uri.getRawSchemeSpecificPart().startsWith("//")) - host = ""; - port = uri.getPort(); - user = uri.getUserInfo(); - path = uri.getRawPath(); - - decodedPath = uri.getPath(); - if (decodedPath != null) { - int p = decodedPath.lastIndexOf(';'); - if (p >= 0) - param = decodedPath.substring(p + 1); - } - query = uri.getRawQuery(); - fragment = uri.getFragment(); - - decodedPath = null; - } - - public HttpURI(String scheme, String host, int port, String pathQuery) { - uri = null; - - this.scheme = scheme; - this.host = host; - this.port = port; - - if (pathQuery != null) - parse(State.PATH, pathQuery, 0, pathQuery.length()); - - } - - /** - * Construct a normalized URI. - * Port is not set if it is the default port. - * - * @param scheme the URI scheme - * @param host the URI hose - * @param port the URI port - * @param path the URI path - * @param param the URI param - * @param query the URI query - * @param fragment the URI fragment - * @return the normalized URI - */ - public static HttpURI createHttpURI(String scheme, String host, int port, String path, String param, String query, String fragment) { - if (port == 80 && HttpScheme.HTTP.is(scheme)) - port = 0; - if (port == 443 && HttpScheme.HTTPS.is(scheme)) - port = 0; - return new HttpURI(scheme, host, port, path, param, query, fragment); - } - - public void parse(String uri) { - clear(); - this.uri = uri; - parse(State.START, uri, 0, uri.length()); - } - - /** - * Parse according to https://tools.ietf.org/html/rfc7230#section-5.3 - * - * @param method the request method - * @param uri the request uri - */ - public void parseRequestTarget(String method, String uri) { - clear(); - this.uri = uri; - - if (HttpMethod.CONNECT.is(method)) - path = uri; - else - parse(uri.startsWith("/") ? State.PATH : State.START, uri, 0, uri.length()); - } - - @Deprecated - public void parseConnect(String uri) { - clear(); - this.uri = uri; - path = uri; - } - - public void parse(String uri, int offset, int length) { - clear(); - int end = offset + length; - this.uri = uri.substring(offset, end); - parse(State.START, uri, offset, end); - } - - private void parse(State state, final String uri, final int offset, final int end) { - boolean encoded = false; - int mark = offset; - int path_mark = 0; - - for (int i = offset; i < end; i++) { - char c = uri.charAt(i); - - switch (state) { - case START: { - switch (c) { - case '/': - mark = i; - state = State.HOST_OR_PATH; - break; - case ';': - mark = i + 1; - state = State.PARAM; - break; - case '?': - // assume empty path (if seen at start) - path = ""; - mark = i + 1; - state = State.QUERY; - break; - case '#': - mark = i + 1; - state = State.FRAGMENT; - break; - case '*': - path = "*"; - state = State.ASTERISK; - break; - - default: - mark = i; - if (scheme == null) - state = State.SCHEME_OR_PATH; - else { - path_mark = i; - state = State.PATH; - } - } - - continue; - } - - case SCHEME_OR_PATH: { - switch (c) { - case ':': - // must have been a scheme - scheme = uri.substring(mark, i); - // Start again with scheme set - state = State.START; - break; - - case '/': - // must have been in a path and still are - state = State.PATH; - break; - - case ';': - // must have been in a path - mark = i + 1; - state = State.PARAM; - break; - - case '?': - // must have been in a path - path = uri.substring(mark, i); - mark = i + 1; - state = State.QUERY; - break; - - case '%': - // must have be in an encoded path - encoded = true; - state = State.PATH; - break; - - case '#': - // must have been in a path - path = uri.substring(mark, i); - state = State.FRAGMENT; - break; - } - continue; - } - - case HOST_OR_PATH: { - switch (c) { - case '/': - host = ""; - mark = i + 1; - state = State.HOST; - break; - - case '@': - case ';': - case '?': - case '#': - // was a path, look again - i--; - path_mark = mark; - state = State.PATH; - break; - default: - // it is a path - path_mark = mark; - state = State.PATH; - } - continue; - } - - case HOST: { - switch (c) { - case '/': - host = uri.substring(mark, i); - path_mark = mark = i; - state = State.PATH; - break; - case ':': - if (i > mark) - host = uri.substring(mark, i); - mark = i + 1; - state = State.PORT; - break; - case '@': - if (user != null) - throw new IllegalArgumentException("Bad authority"); - user = uri.substring(mark, i); - mark = i + 1; - break; - - case '[': - state = State.IPV6; - break; - } - continue; - } - - case IPV6: { - switch (c) { - case '/': - throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri); - case ']': - c = uri.charAt(++i); - host = uri.substring(mark, i); - if (c == ':') { - mark = i + 1; - state = State.PORT; - } else { - path_mark = mark = i; - state = State.PATH; - } - break; - } - - continue; - } - - case PORT: { - if (c == '@') { - if (user != null) - throw new IllegalArgumentException("Bad authority"); - // It wasn't a port, but a password! - user = host + ":" + uri.substring(mark, i); - mark = i + 1; - state = State.HOST; - } else if (c == '/') { - port = TypeUtils.parseInt(uri, mark, i - mark, 10); - path_mark = mark = i; - state = State.PATH; - } - continue; - } - - case PATH: { - switch (c) { - case ';': - mark = i + 1; - state = State.PARAM; - break; - case '?': - path = uri.substring(path_mark, i); - mark = i + 1; - state = State.QUERY; - break; - case '#': - path = uri.substring(path_mark, i); - mark = i + 1; - state = State.FRAGMENT; - break; - case '%': - encoded = true; - break; - } - continue; - } - - case PARAM: { - switch (c) { - case '?': - path = uri.substring(path_mark, i); - param = uri.substring(mark, i); - mark = i + 1; - state = State.QUERY; - break; - case '#': - path = uri.substring(path_mark, i); - param = uri.substring(mark, i); - mark = i + 1; - state = State.FRAGMENT; - break; - case '/': - encoded = true; - // ignore internal params - state = State.PATH; - break; - case ';': - // multiple parameters - mark = i + 1; - break; - } - continue; - } - - case QUERY: { - if (c == '#') { - query = uri.substring(mark, i); - mark = i + 1; - state = State.FRAGMENT; - } - continue; - } - - case ASTERISK: { - throw new IllegalArgumentException("Bad character '*'"); - } - - case FRAGMENT: { - fragment = uri.substring(mark, end); - i = end; - } - } - } - - - switch (state) { - case START: - break; - case SCHEME_OR_PATH: - path = uri.substring(mark, end); - break; - - case HOST_OR_PATH: - path = uri.substring(mark, end); - break; - - case HOST: - if (end > mark) - host = uri.substring(mark, end); - break; - - case IPV6: - throw new IllegalArgumentException("No closing ']' for ipv6 in " + uri); - - case PORT: - port = TypeUtils.parseInt(uri, mark, end - mark, 10); - break; - - case ASTERISK: - break; - - case FRAGMENT: - fragment = uri.substring(mark, end); - break; - - case PARAM: - path = uri.substring(path_mark, end); - param = uri.substring(mark, end); - break; - - case PATH: - path = uri.substring(path_mark, end); - break; - - case QUERY: - query = uri.substring(mark, end); - break; - } - - if (!encoded) { - if (param == null) - decodedPath = path; - else - decodedPath = path.substring(0, path.length() - param.length() - 1); - } - } - - public String getScheme() { - return scheme; - } - - public void setScheme(String scheme) { - this.scheme = scheme; - uri = null; - } - - public String getHost() { - // Return null for empty host to retain compatibility with java.net.URI - if (host != null && host.length() == 0) - return null; - return host; - } - - public int getPort() { - return port; - } - - /** - * The parsed Path. - * - * @return the path as parsed on valid URI. null for invalid URI. - */ - public String getPath() { - return path; - } - - /** - * @param path the path - */ - public void setPath(String path) { - uri = null; - this.path = path; - decodedPath = null; - } - - public String getDecodedPath() { - if (decodedPath == null && path != null) - decodedPath = URIUtils.decodePath(path); - return decodedPath; - } - - public String getParam() { - return param; - } - - public String getQuery() { - return query; - } - - public void setQuery(String query) { - this.query = query; - uri = null; - } - - public boolean hasQuery() { - return query != null && query.length() > 0; - } - - public String getFragment() { - return fragment; - } - - public void decodeQueryTo(MultiMap parameters) { - if (query == null) - return; - UrlEncoded.decodeUtf8To(query, parameters); - } - - public void decodeQueryTo(MultiMap parameters, String encoding) throws UnsupportedEncodingException { - decodeQueryTo(parameters, Charset.forName(encoding)); - } - - public void decodeQueryTo(MultiMap parameters, Charset encoding) throws UnsupportedEncodingException { - if (query == null) - return; - - if (encoding == null || StandardCharsets.UTF_8.equals(encoding)) - UrlEncoded.decodeUtf8To(query, parameters); - else - UrlEncoded.decodeTo(query, parameters, encoding); - } - - public void clear() { - uri = null; - - scheme = null; - host = null; - port = -1; - path = null; - param = null; - query = null; - fragment = null; - - decodedPath = null; - } - - public boolean isAbsolute() { - return scheme != null && scheme.length() > 0; - } - - @Override - public String toString() { - if (uri == null) { - StringBuilder out = new StringBuilder(); - - if (scheme != null) - out.append(scheme).append(':'); - - if (host != null) { - out.append("//"); - if (user != null) - out.append(user).append('@'); - out.append(host); - } - - if (port > 0) - out.append(':').append(port); - - if (path != null) - out.append(path); - - if (query != null) - out.append('?').append(query); - - if (fragment != null) - out.append('#').append(fragment); - - if (out.length() > 0) - uri = out.toString(); - else - uri = ""; - } - return uri; - } - - @Override - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof HttpURI)) - return false; - return toString().equals(o.toString()); - } - - /** - * @param host the host - * @param port the port - */ - public void setAuthority(String host, int port) { - this.host = host; - this.port = port; - uri = null; - } - - public URI toURI() throws URISyntaxException { - return new URI(scheme, null, host, port, path, query == null ? null : UrlEncoded.decodeString(query), fragment); - } - - public String getPathQuery() { - if (query == null) - return path; - return path + "?" + query; - } - - public void setPathQuery(String path) { - uri = null; - this.path = null; - decodedPath = null; - param = null; - fragment = null; - if (path != null) - parse(State.PATH, path, 0, path.length()); - } - - public String getAuthority() { - if (port > 0) - return host + ":" + port; - return host; - } - - public String getUser() { - return user; - } - - - private enum State { - START, - HOST_OR_PATH, - SCHEME_OR_PATH, - HOST, - IPV6, - PORT, - PATH, - PARAM, - QUERY, - FRAGMENT, - ASTERISK - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpVersion.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpVersion.java deleted file mode 100644 index e21e23b14..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/HttpVersion.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.string.StringUtils; - -import java.nio.ByteBuffer; - -public enum HttpVersion { - HTTP_0_9("HTTP/0.9", 9), - HTTP_1_0("HTTP/1.0", 10), - HTTP_1_1("HTTP/1.1", 11), - HTTP_2("HTTP/2.0", 20); - - public final static Trie CACHE = new ArrayTrie<>(); - - static { - for (HttpVersion version : HttpVersion.values()) - CACHE.put(version.toString(), version); - } - - private final String value; - private final int version; - private final byte[] bytes; - - HttpVersion(String value, int version) { - this.value = value; - this.version = version; - bytes = StringUtils.getUtf8Bytes(value); - } - - public static HttpVersion from(String value) { - return CACHE.get(value); - } - - public static HttpVersion fromVersion(int version) { - switch (version) { - case 9: - return HttpVersion.HTTP_0_9; - case 10: - return HttpVersion.HTTP_1_0; - case 11: - return HttpVersion.HTTP_1_1; - case 20: - return HttpVersion.HTTP_2; - default: - throw new IllegalArgumentException(); - } - } - - /** - * Optimised lookup to find a Http Version and whitespace in a byte array. - * - * @param bytes Array containing ISO-8859-1 characters - * @param position The first valid index - * @param limit The first non valid index - * @return A HttpMethod if a match or null if no easy match. - */ - public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit) { - int length = limit - position; - if (length < 9) - return null; - - if (bytes[position + 4] == '/' && bytes[position + 6] == '.' && Character.isWhitespace((char) bytes[position + 8]) && - ((bytes[position] == 'H' && bytes[position + 1] == 'T' && bytes[position + 2] == 'T' && bytes[position + 3] == 'P') || - (bytes[position] == 'h' && bytes[position + 1] == 't' && bytes[position + 2] == 't' && bytes[position + 3] == 'p'))) { - switch (bytes[position + 5]) { - case '1': - switch (bytes[position + 7]) { - case '0': - return HTTP_1_0; - case '1': - return HTTP_1_1; - } - break; - case '2': - switch (bytes[position + 7]) { - case '0': - return HTTP_2; - } - break; - } - } - - return null; - } - - /** - * Optimised lookup to find a HTTP Version and trailing white space in a byte array. - * - * @param buffer buffer containing ISO-8859-1 characters - * @return A HttpVersion if a match or null if no easy match. - */ - public static HttpVersion lookAheadGet(ByteBuffer buffer) { - if (buffer.hasArray()) - return lookAheadGet(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.arrayOffset() + buffer.limit()); - return null; - } - - public boolean is(String value) { - return this.value.equalsIgnoreCase(value); - } - - public String getValue() { - return value; - } - - public byte[] getBytes() { - return bytes; - } - - public int getVersion() { - return version; - } - - @Override - public String toString() { - return value; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/MetaData.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/MetaData.java deleted file mode 100644 index 36f1599ab..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/MetaData.java +++ /dev/null @@ -1,279 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import java.util.Collections; -import java.util.Iterator; -import java.util.Optional; -import java.util.function.Supplier; - -public class MetaData implements Iterable { - private final HttpFields fields; - private HttpVersion httpVersion; - private long contentLength; - private Supplier trailers; - private boolean onlyTrailer; - - public MetaData(HttpVersion version, HttpFields fields) { - this(version, fields, Long.MIN_VALUE); - } - - public MetaData(HttpVersion version, HttpFields fields, long contentLength) { - httpVersion = version; - this.fields = fields; - this.contentLength = contentLength < 0 ? Long.MIN_VALUE : contentLength; - } - - protected void recycle() { - httpVersion = null; - if (fields != null) { - fields.clear(); - } - contentLength = Long.MIN_VALUE; - trailers = null; - } - - public boolean isRequest() { - return false; - } - - public boolean isResponse() { - return false; - } - - /** - * @return the HTTP version of this MetaData object - */ - public HttpVersion getHttpVersion() { - return httpVersion; - } - - /** - * @param httpVersion the HTTP version to set - */ - public void setHttpVersion(HttpVersion httpVersion) { - this.httpVersion = httpVersion; - } - - /** - * @return the HTTP fields of this MetaData object - */ - public HttpFields getFields() { - return fields; - } - - public Supplier getTrailerSupplier() { - return trailers; - } - - public boolean isOnlyTrailer() { - return onlyTrailer; - } - - public void setOnlyTrailer(boolean onlyTrailer) { - this.onlyTrailer = onlyTrailer; - } - - public void setTrailerSupplier(Supplier trailers) { - this.trailers = trailers; - } - - /** - * @return the content length if available, otherwise {@link Long#MIN_VALUE} - */ - public long getContentLength() { - if (contentLength == Long.MIN_VALUE) { - return Optional.ofNullable(fields.getField(HttpHeader.CONTENT_LENGTH)) - .map(HttpField::getValue) - .map(Long::parseLong) - .orElse(Long.MIN_VALUE); - } else { - return contentLength; - } - } - - /** - * @return an iterator over the HTTP fields - * @see #getFields() - */ - public Iterator iterator() { - HttpFields fields = getFields(); - return fields == null ? Collections.emptyIterator() : fields.iterator(); - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder(); - for (HttpField field : this) - out.append(field).append(System.lineSeparator()); - return out.toString(); - } - - public static class Request extends MetaData { - private String method; - private HttpURI uri; - - public Request(HttpFields fields) { - this(null, null, null, fields); - } - - public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields) { - this(method, uri, version, fields, Long.MIN_VALUE); - } - - public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength) { - super(version, fields, contentLength); - this.method = method; - this.uri = uri; - } - - public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields) { - this(method, new HttpURI(scheme == null ? null : scheme.getValue(), hostPort.getHost(), hostPort.getPort(), uri), version, fields); - } - - public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength) { - this(method, new HttpURI(scheme == null ? null : scheme.getValue(), hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength); - } - - public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength) { - this(method, new HttpURI(scheme, hostPort.getHost(), hostPort.getPort(), uri), version, fields, contentLength); - } - - public Request(Request request) { - super(request.getHttpVersion(), new HttpFields(request.getFields()), request.getContentLength()); - this.method = request.getMethod(); - this.uri = new HttpURI(request.getURI()); - } - - public void recycle() { - super.recycle(); - method = null; - if (uri != null) - uri.clear(); - } - - @Override - public boolean isRequest() { - return true; - } - - /** - * @return the HTTP method - */ - public String getMethod() { - return method; - } - - /** - * @param method the HTTP method to set - */ - public void setMethod(String method) { - this.method = method; - } - - /** - * @return the HTTP URI - */ - public HttpURI getURI() { - return uri; - } - - /** - * @param uri the HTTP URI to set - */ - public void setURI(HttpURI uri) { - this.uri = uri; - } - - /** - * @return the HTTP URI in string form - */ - public String getURIString() { - return uri == null ? null : uri.toString(); - } - - @Override - public String toString() { - HttpFields fields = getFields(); - return String.format("%s{u=%s,%s,h=%d,cl=%d}", - getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength()); - } - } - - public static class Response extends MetaData { - private int status; - private String reason; - - public Response() { - this(null, 0, null); - } - - public Response(HttpFields httpFields) { - this(null, 0, httpFields); - } - - public Response(HttpVersion version, int status, HttpFields fields) { - this(version, status, fields, Long.MIN_VALUE); - } - - public Response(HttpVersion version, int status, HttpFields fields, long contentLength) { - super(version, fields, contentLength); - this.status = status; - } - - public Response(HttpVersion version, int status, String reason, HttpFields fields, long contentLength) { - super(version, fields, contentLength); - this.reason = reason; - this.status = status; - } - - public Response(Response response) { - super(response.getHttpVersion(), new HttpFields(response.getFields()), response.getContentLength()); - this.reason = response.reason; - this.status = response.status; - } - - @Override - public boolean isResponse() { - return true; - } - - /** - * @return the HTTP status - */ - public int getStatus() { - return status; - } - - /** - * @param status the HTTP status to set - */ - public void setStatus(int status) { - this.status = status; - } - - /** - * @return the HTTP reason - */ - public String getReason() { - return reason; - } - - /** - * @param reason the HTTP reason to set - */ - public void setReason(String reason) { - this.reason = reason; - } - - public void recycle() { - super.recycle(); - reason = null; - status = 0; - } - - @Override - public String toString() { - HttpFields fields = getFields(); - return String.format("%s{s=%d,h=%d,cl=%d}", getHttpVersion(), getStatus(), fields == null ? -1 : fields.size(), getContentLength()); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/MimeTypes.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/MimeTypes.java deleted file mode 100644 index ab32b47a6..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/MimeTypes.java +++ /dev/null @@ -1,629 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.codec.PreEncodedHttpField; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class MimeTypes { - - private static final LazyLogger LOG = SystemLogger.create(MimeTypes.class); - - private static final Map DFT_MIME_MAP = new HashMap<>(); - private static final Map INFERRED_ENCODINGS = new HashMap<>(); - private static final Map ASSUMED_ENCODINGS = new HashMap<>(); - private static final Trie CACHE = new ArrayTrie<>(512); - - static { - for (Type type : Type.values()) { - CACHE.put(type.toString(), type); - - int charset = type.toString().indexOf(";charset="); - if (charset > 0) { - String alt = type.toString().replace(";charset=", "; charset="); - CACHE.put(alt, type); - } - - if (type.isAssumedCharset()) - ASSUMED_ENCODINGS.put(type.getValue(), type.getCharsetString()); - } - - String path = MimeTypes.class.getPackage().getName().replace('.', '/'); - String resourceName = path + "/mime.properties"; - try (InputStream stream = MimeTypes.class.getClassLoader().getResourceAsStream(resourceName)) { - if (stream == null) { - LOG.warn("Missing mime-type resource: {}", resourceName); - } else { - try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) { - Properties props = new Properties(); - props.load(reader); - props.stringPropertyNames().stream() - .filter(Objects::nonNull) - .forEach(x -> - DFT_MIME_MAP.put(StringUtils.asciiToLowerCase(x), normalizeMimeType(props.getProperty(x)))); - - if (DFT_MIME_MAP.size() == 0) { - LOG.warn("Empty mime types at {}", resourceName); - } else if (DFT_MIME_MAP.size() < props.keySet().size()) { - LOG.warn("Duplicate or null mime-type extension in resource: {}", resourceName); - } - } catch (IOException e) { - LOG.warn(e.toString()); - } - - } - } catch (IOException e) { - LOG.warn(e.toString()); - } - - resourceName = path + "/encoding.properties"; - try (InputStream stream = MimeTypes.class.getClassLoader().getResourceAsStream(resourceName)) { - if (stream == null) - LOG.warn("Missing encoding resource: {}", resourceName); - else { - try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) { - Properties props = new Properties(); - props.load(reader); - props.stringPropertyNames().stream() - .filter(Objects::nonNull) - .forEach(t -> - { - String charset = props.getProperty(t); - if (charset.startsWith("-")) - ASSUMED_ENCODINGS.put(t, charset.substring(1)); - else - INFERRED_ENCODINGS.put(t, props.getProperty(t)); - }); - - if (INFERRED_ENCODINGS.size() == 0) { - LOG.warn("Empty encodings at {}", resourceName); - } else if ((INFERRED_ENCODINGS.size() + ASSUMED_ENCODINGS.size()) < props.keySet().size()) { - LOG.warn("Null or duplicate encodings in resource: {}", resourceName); - } - } catch (IOException e) { - LOG.warn(e.toString()); - } - } - } catch (IOException e) { - LOG.warn(e.toString()); - } - } - - private final Map _mimeMap = new HashMap<>(); - - /** - * Constructor. - */ - public MimeTypes() { - } - - /** - * Get the MIME type by filename extension. - * Lookup only the static default mime map. - * - * @param filename A file name - * @return MIME type matching the longest dot extension of the - * file name. - */ - public static String getDefaultMimeByExtension(String filename) { - String type = null; - - if (filename != null) { - int i = -1; - while (type == null) { - i = filename.indexOf(".", i + 1); - - if (i < 0 || i >= filename.length()) - break; - - String ext = StringUtils.asciiToLowerCase(filename.substring(i + 1)); - type = DFT_MIME_MAP.get(ext); - } - } - - if (type == null) { - type = DFT_MIME_MAP.get("*"); - } - - return type; - } - - public static Set getKnownMimeTypes() { - return new HashSet<>(DFT_MIME_MAP.values()); - } - - private static String normalizeMimeType(String type) { - Type t = CACHE.get(type); - if (t != null) - return t.getValue(); - - return StringUtils.asciiToLowerCase(type); - } - - public static String getCharsetFromContentType(String value) { - if (value == null) - return null; - int end = value.length(); - int state = 0; - int start = 0; - boolean quote = false; - int i = 0; - for (; i < end; i++) { - char b = value.charAt(i); - - if (quote && state != 10) { - if ('"' == b) - quote = false; - continue; - } - - if (';' == b && state <= 8) { - state = 1; - continue; - } - - switch (state) { - case 0: - if ('"' == b) { - quote = true; - break; - } - break; - - case 1: - if ('c' == b) state = 2; - else if (' ' != b) state = 0; - break; - case 2: - if ('h' == b) state = 3; - else state = 0; - break; - case 3: - if ('a' == b) state = 4; - else state = 0; - break; - case 4: - if ('r' == b) state = 5; - else state = 0; - break; - case 5: - if ('s' == b) state = 6; - else state = 0; - break; - case 6: - if ('e' == b) state = 7; - else state = 0; - break; - case 7: - if ('t' == b) state = 8; - else state = 0; - break; - - case 8: - if ('=' == b) state = 9; - else if (' ' != b) state = 0; - break; - - case 9: - if (' ' == b) - break; - if ('"' == b) { - quote = true; - start = i + 1; - state = 10; - break; - } - start = i; - state = 10; - break; - - case 10: - if (!quote && (';' == b || ' ' == b) || - (quote && '"' == b)) - return StringUtils.normalizeCharset(value, start, i - start); - } - } - - if (state == 10) - return StringUtils.normalizeCharset(value, start, i - start); - - return null; - } - - /** - * Access a mutable map of mime type to the charset inferred from that content type. - * An inferred encoding is used by when encoding/decoding a stream and is - * explicitly set in any metadata (eg Content-Type). - * - * @return Map of mime type to charset - */ - public static Map getInferredEncodings() { - return INFERRED_ENCODINGS; - } - - /** - * Access a mutable map of mime type to the charset assumed for that content type. - * An assumed encoding is used by when encoding/decoding a stream, but is not - * explicitly set in any metadata (eg Content-Type). - * - * @return Map of mime type to charset - */ - public static Map getAssumedEncodings() { - return INFERRED_ENCODINGS; - } - - @Deprecated - public static String inferCharsetFromContentType(String contentType) { - return getCharsetAssumedFromContentType(contentType); - } - - public static String getCharsetInferredFromContentType(String contentType) { - return INFERRED_ENCODINGS.get(contentType); - } - - public static String getCharsetAssumedFromContentType(String contentType) { - return ASSUMED_ENCODINGS.get(contentType); - } - - public static String getContentTypeWithoutCharset(String value) { - int end = value.length(); - int state = 0; - int start = 0; - boolean quote = false; - int i = 0; - StringBuilder builder = null; - for (; i < end; i++) { - char b = value.charAt(i); - - if ('"' == b) { - quote = !quote; - - switch (state) { - case 11: - builder.append(b); - break; - case 10: - break; - case 9: - builder = new StringBuilder(); - builder.append(value, 0, start + 1); - state = 10; - break; - default: - start = i; - state = 0; - } - continue; - } - - if (quote) { - if (builder != null && state != 10) - builder.append(b); - continue; - } - - switch (state) { - case 0: - if (';' == b) - state = 1; - else if (' ' != b) - start = i; - break; - - case 1: - if ('c' == b) state = 2; - else if (' ' != b) state = 0; - break; - case 2: - if ('h' == b) state = 3; - else state = 0; - break; - case 3: - if ('a' == b) state = 4; - else state = 0; - break; - case 4: - if ('r' == b) state = 5; - else state = 0; - break; - case 5: - if ('s' == b) state = 6; - else state = 0; - break; - case 6: - if ('e' == b) state = 7; - else state = 0; - break; - case 7: - if ('t' == b) state = 8; - else state = 0; - break; - case 8: - if ('=' == b) state = 9; - else if (' ' != b) state = 0; - break; - - case 9: - if (' ' == b) - break; - builder = new StringBuilder(); - builder.append(value, 0, start + 1); - state = 10; - break; - - case 10: - if (';' == b) { - builder.append(b); - state = 11; - } - break; - case 11: - if (' ' != b) - builder.append(b); - } - } - if (builder == null) - return value; - return builder.toString(); - - } - - public static String getContentTypeMIMEType(String contentType) { - if (StringUtils.hasText(contentType)) { - // parsing content-type - String[] strings = StringUtils.split(contentType, ';'); - return strings[0]; - } else { - return null; - } - } - - public static List getAcceptMIMETypes(String accept) { - if (StringUtils.hasText(accept)) { - List list = new ArrayList<>(); - // parsing accept - String[] strings = StringUtils.split(accept, ','); - for (String string : strings) { - String[] s = StringUtils.split(string, ';'); - list.add(s[0].trim()); - } - return list; - } else { - return Collections.emptyList(); - } - } - - public static List parseAcceptMIMETypes(String accept) { - return Optional.ofNullable(accept) - .filter(StringUtils::hasText) - .map(a -> StringUtils.split(a, ',')) - .map(Arrays::stream) - .map(MimeTypes::apply) - .orElse(Collections.emptyList()); - } - - private static List apply(Stream stream) { - return stream.map(String::trim) - .filter(StringUtils::hasText) - .map(type -> { - String[] mimeTypeAndFields = StringUtils.split(type, ';'); - AcceptMIMEType acceptMIMEType = new AcceptMIMEType(); - - // parse the MIME type - String[] mimeType = StringUtils.split(mimeTypeAndFields[0].trim(), '/'); - String parentType = mimeType[0].trim(); - String childType = mimeType[1].trim(); - acceptMIMEType.setParentType(parentType); - acceptMIMEType.setChildType(childType); - if (parentType.equals("*")) { - if (childType.equals("*")) { - acceptMIMEType.setMatchType(AcceptMIMEMatchType.ALL); - } else { - acceptMIMEType.setMatchType(AcceptMIMEMatchType.CHILD); - } - } else { - if (childType.equals("*")) { - acceptMIMEType.setMatchType(AcceptMIMEMatchType.PARENT); - } else { - acceptMIMEType.setMatchType(AcceptMIMEMatchType.EXACT); - } - } - - // parse the quality - if (mimeTypeAndFields.length > 1) { - Arrays.stream(mimeTypeAndFields) - .filter(v -> v.contains("=")) - .map(v -> StringUtils.split(v, '=')) - .filter(v -> v.length > 1) - .filter(v -> v[0].contains("q")) - .map(v -> v[1].trim()) - .map(Float::parseFloat) - .findAny() - .ifPresent(acceptMIMEType::setQuality); - } - - return acceptMIMEType; - }) - .sorted((a1, a2) -> Float.compare(a2.getQuality(), a1.getQuality())) - .collect(Collectors.toList()); - } - - public Map getMimeMap() { - return _mimeMap; - } - - /** - * @param mimeMap A Map of file extension to mime-type. - */ - public void setMimeMap(Map mimeMap) { - _mimeMap.clear(); - if (mimeMap != null) { - for (Entry ext : mimeMap.entrySet()) { - _mimeMap.put(StringUtils.asciiToLowerCase(ext.getKey()), normalizeMimeType(ext.getValue())); - } - } - } - - /** - * Get the MIME type by filename extension. - * Lookup the content and static default mime maps. - * - * @param filename A file name - * @return MIME type matching the longest dot extension of the - * file name. - */ - public String getMimeByExtension(String filename) { - String type = null; - - if (filename != null) { - int i = -1; - while (type == null) { - i = filename.indexOf(".", i + 1); - - if (i < 0 || i >= filename.length()) - break; - - String ext = StringUtils.asciiToLowerCase(filename.substring(i + 1)); - type = _mimeMap.get(ext); - if (type == null) - type = DFT_MIME_MAP.get(ext); - } - } - - if (type == null) { - type = _mimeMap.get("*"); - if (type == null) { - type = DFT_MIME_MAP.get("*"); - } - } - - return type; - } - - /** - * Set a mime mapping - * - * @param extension the extension - * @param type the mime type - */ - public void addMimeMapping(String extension, String type) { - _mimeMap.put(StringUtils.asciiToLowerCase(extension), normalizeMimeType(type)); - } - - public enum Type { - FORM_ENCODED("application/x-www-form-urlencoded"), - MESSAGE_HTTP("message/http"), - MULTIPART_BYTERANGES("multipart/byteranges"), - MULTIPART_FORM_DATA("multipart/form-data"), - - TEXT_HTML("text/html"), - TEXT_PLAIN("text/plain"), - TEXT_XML("text/xml"), - TEXT_JSON("text/json", StandardCharsets.UTF_8), - APPLICATION_JSON("application/json", StandardCharsets.UTF_8), - - TEXT_HTML_8859_1("text/html;charset=iso-8859-1", TEXT_HTML), - TEXT_HTML_UTF_8("text/html;charset=utf-8", TEXT_HTML), - - TEXT_PLAIN_8859_1("text/plain;charset=iso-8859-1", TEXT_PLAIN), - TEXT_PLAIN_UTF_8("text/plain;charset=utf-8", TEXT_PLAIN), - - TEXT_XML_8859_1("text/xml;charset=iso-8859-1", TEXT_XML), - TEXT_XML_UTF_8("text/xml;charset=utf-8", TEXT_XML), - - TEXT_JSON_8859_1("text/json;charset=iso-8859-1", TEXT_JSON), - TEXT_JSON_UTF_8("text/json;charset=utf-8", TEXT_JSON), - - APPLICATION_JSON_8859_1("application/json;charset=iso-8859-1", APPLICATION_JSON), - APPLICATION_JSON_UTF_8("application/json;charset=utf-8", APPLICATION_JSON); - - - private final String value; - private final byte[] bytes; - private final Type baseType; - private final Charset charset; - private final String charsetString; - private final boolean assumedCharset; - private final HttpField field; - - Type(String value) { - this.value = value; - bytes = StringUtils.getUtf8Bytes(value); - baseType = this; - charset = null; - charsetString = null; - assumedCharset = false; - field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, this.value); - } - - Type(String value, Type baseType) { - this.value = value; - bytes = StringUtils.getUtf8Bytes(value); - this.baseType = baseType; - int i = value.indexOf(";charset="); - charset = Charset.forName(value.substring(i + 9)); - charsetString = charset.toString().toLowerCase(Locale.ENGLISH); - assumedCharset = false; - field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, this.value); - } - - Type(String value, Charset charset) { - this.value = value; - bytes = StringUtils.getUtf8Bytes(value); - baseType = this; - this.charset = charset; - charsetString = this.charset == null ? null : this.charset.toString().toLowerCase(Locale.ENGLISH); - assumedCharset = true; - field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, this.value); - } - - public Charset getCharset() { - return charset; - } - - public String getCharsetString() { - return charsetString; - } - - public boolean is(String s) { - return value.equalsIgnoreCase(s); - } - - public String getValue() { - return value; - } - - public byte[] getBytes() { - return bytes; - } - - @Override - public String toString() { - return value; - } - - public boolean isAssumedCharset() { - return assumedCharset; - } - - public HttpField getContentTypeField() { - return field; - } - - public Type getBaseType() { - return baseType; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/QuotedCSV.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/QuotedCSV.java deleted file mode 100644 index 51c7b89e4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/QuotedCSV.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.string.StringUtils; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * Implements a quoted comma separated list of values - * in accordance with RFC7230. - * OWS is removed and quoted characters ignored for parsing. - * - * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" - * @see "https://tools.ietf.org/html/rfc7230#section-7" - */ -public class QuotedCSV implements Iterable { - - protected final List values = new ArrayList<>(); - protected final boolean keepQuotes; - - public QuotedCSV(String... values) { - this(true, values); - } - - public QuotedCSV(boolean keepQuotes, String... values) { - this.keepQuotes = keepQuotes; - for (String v : values) - addValue(v); - } - - public static String unquote(String s) { - if (!StringUtils.hasText(s)) { - return s; - } - // handle trivial cases - int l = s.length(); - // Look for any quotes - int i = 0; - for (; i < l; i++) { - char c = s.charAt(i); - if (c == '"') - break; - } - if (i == l) - return s; - - boolean quoted = true; - boolean sloshed = false; - StringBuilder buffer = new StringBuilder(); - buffer.append(s, 0, i); - i++; - for (; i < l; i++) { - char c = s.charAt(i); - if (quoted) { - if (sloshed) { - buffer.append(c); - sloshed = false; - } else if (c == '"') - quoted = false; - else if (c == '\\') - sloshed = true; - else - buffer.append(c); - } else if (c == '"') - quoted = true; - else - buffer.append(c); - } - return buffer.toString(); - } - - /** - * Add and parse a value string(s) - * - * @param value A value that may contain one or more Quoted CSV items. - */ - public void addValue(String value) { - if (value == null) - return; - - StringBuilder buffer = new StringBuilder(); - - int l = value.length(); - State state = State.VALUE; - boolean quoted = false; - boolean sloshed = false; - int nws_length = 0; - int last_length = 0; - int value_length = -1; - int param_name = -1; - int param_value = -1; - - for (int i = 0; i <= l; i++) { - char c = i == l ? 0 : value.charAt(i); - - // Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6 - if (quoted && c != 0) { - if (sloshed) - sloshed = false; - else { - switch (c) { - case '\\': - sloshed = true; - if (!keepQuotes) - continue; - break; - case '"': - quoted = false; - if (!keepQuotes) - continue; - break; - } - } - - buffer.append(c); - nws_length = buffer.length(); - continue; - } - - // Handle common cases - switch (c) { - case ' ': - case '\t': - if (buffer.length() > last_length) // not leading OWS - buffer.append(c); - continue; - - case '"': - quoted = true; - if (keepQuotes) { - if (state == State.PARAM_VALUE && param_value < 0) - param_value = nws_length; - buffer.append(c); - } else if (state == State.PARAM_VALUE && param_value < 0) - param_value = nws_length; - nws_length = buffer.length(); - continue; - - case ';': - buffer.setLength(nws_length); // trim following OWS - if (state == State.VALUE) { - parsedValue(buffer); - value_length = buffer.length(); - } else - parsedParam(buffer, value_length, param_name, param_value); - nws_length = buffer.length(); - param_name = param_value = -1; - buffer.append(c); - last_length = ++nws_length; - state = State.PARAM_NAME; - continue; - - case ',': - case 0: - if (nws_length > 0) { - buffer.setLength(nws_length); // trim following OWS - switch (state) { - case VALUE: - parsedValue(buffer); -// value_length = buffer.length(); - break; - case PARAM_NAME: - case PARAM_VALUE: - parsedParam(buffer, value_length, param_name, param_value); - break; - } - values.add(buffer.toString()); - } - buffer.setLength(0); - last_length = 0; - nws_length = 0; - value_length = param_name = param_value = -1; - state = State.VALUE; - continue; - - case '=': - switch (state) { - case VALUE: - // It wasn't really a value, it was a param name - value_length = param_name = 0; - buffer.setLength(nws_length); // trim following OWS - String param = buffer.toString(); - buffer.setLength(0); - parsedValue(buffer); - value_length = buffer.length(); - buffer.append(param); - buffer.append(c); - last_length = ++nws_length; - state = State.PARAM_VALUE; - continue; - - case PARAM_NAME: - buffer.setLength(nws_length); // trim following OWS - buffer.append(c); - last_length = ++nws_length; - state = State.PARAM_VALUE; - continue; - - case PARAM_VALUE: - if (param_value < 0) - param_value = nws_length; - buffer.append(c); - nws_length = buffer.length(); - continue; - } - continue; - - default: { - switch (state) { - case VALUE: { - buffer.append(c); - nws_length = buffer.length(); - continue; - } - - case PARAM_NAME: { - if (param_name < 0) - param_name = nws_length; - buffer.append(c); - nws_length = buffer.length(); - continue; - } - - case PARAM_VALUE: { - if (param_value < 0) - param_value = nws_length; - buffer.append(c); - nws_length = buffer.length(); - } - } - } - } - } - } - - /** - * Called when a value has been parsed - * - * @param buffer Containing the trimmed value, which may be mutated - */ - protected void parsedValue(StringBuilder buffer) { - } - - /** - * Called when a parameter has been parsed - * - * @param buffer Containing the trimmed value and all parameters, which may be mutated - * @param valueLength The length of the value - * @param paramName The index of the start of the parameter just parsed - * @param paramValue The index of the start of the parameter value just parsed, or -1 - */ - protected void parsedParam(StringBuilder buffer, int valueLength, int paramName, int paramValue) { - } - - public int size() { - return values.size(); - } - - public boolean isEmpty() { - return values.isEmpty(); - } - - public List getValues() { - return values; - } - - @Override - public Iterator iterator() { - return values.iterator(); - } - - @Override - public String toString() { - List list = new ArrayList<>(); - for (String s : this) { - list.add(s); - } - return list.toString(); - } - - private enum State {VALUE, PARAM_NAME, PARAM_VALUE} -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/QuotedQualityCSV.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/model/QuotedQualityCSV.java deleted file mode 100644 index aaf95a8ce..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/model/QuotedQualityCSV.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; - -import static java.lang.Integer.MIN_VALUE; - -/** - * Implements a quoted comma separated list of quality values in accordance with - * RFC7230 and RFC7231. Values are returned sorted in quality order, with OWS - * and the quality parameters removed. - * - * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" - * @see "https://tools.ietf.org/html/rfc7230#section-7" - * @see "https://tools.ietf.org/html/rfc7231#section-5.3.1" - */ -public class QuotedQualityCSV extends QuotedCSV implements Iterable { - private final static Double ZERO = 0.0; - private final static Double ONE = 1.0; - - /** - * Function to apply a most specific MIME encoding secondary ordering - */ - public static Function MOST_SPECIFIC = s -> { - String[] elements = s.split("/"); - return 1000000 * elements.length + 1000 * elements[0].length() + elements[elements.length - 1].length(); - }; - - private final List quality = new ArrayList<>(); - private final Function secondaryOrdering; - private boolean sorted = false; - - - /** - * Sorts values with equal quality according to the length of the value String. - */ - public QuotedQualityCSV() { - this((s) -> 0); - } - - /** - * Sorts values with equal quality according to given order. - * - * @param preferredOrder Array indicating the preferred order of known values - */ - public QuotedQualityCSV(String[] preferredOrder) { - this((s) -> { - for (int i = 0; i < preferredOrder.length; ++i) - if (preferredOrder[i].equals(s)) - return preferredOrder.length - i; - - if ("*".equals(s)) - return preferredOrder.length; - - return MIN_VALUE; - }); - } - - /** - * Orders values with equal quality with the given function. - * - * @param secondaryOrdering Function to apply an ordering other than specified by quality - */ - public QuotedQualityCSV(Function secondaryOrdering) { - this.secondaryOrdering = secondaryOrdering; - } - - @Override - protected void parsedValue(StringBuilder buffer) { - super.parsedValue(buffer); - quality.add(ONE); - } - - @Override - protected void parsedParam(StringBuilder buffer, int valueLength, int paramName, int paramValue) { - if (paramName < 0) { - if (buffer.charAt(buffer.length() - 1) == ';') { - buffer.setLength(buffer.length() - 1); - } - } else if (paramValue >= 0 && - buffer.charAt(paramName) == 'q' && paramValue > paramName && - buffer.length() >= paramName && buffer.charAt(paramName + 1) == '=') { - Double q; - try { - q = (keepQuotes && buffer.charAt(paramValue) == '"') - ? Double.parseDouble(buffer.substring(paramValue + 1, buffer.length() - 1)) - : Double.parseDouble(buffer.substring(paramValue)); - } catch (Exception e) { - q = ZERO; - } - buffer.setLength(Math.max(0, paramName - 1)); - - if (!ONE.equals(q)) { - quality.set(quality.size() - 1, q); - } - } - } - - public List getValues() { - if (!sorted) { - sort(); - } - return values; - } - - @Override - public Iterator iterator() { - if (!sorted) { - sort(); - } - return values.iterator(); - } - - protected void sort() { - sorted = true; - - Double last = ZERO; - int lastSecondaryOrder = Integer.MIN_VALUE; - - for (int i = values.size(); i-- > 0; ) { - String v = values.get(i); - Double q = quality.get(i); - - int compare = last.compareTo(q); - if (compare > 0 || (compare == 0 && secondaryOrdering.apply(v) < lastSecondaryOrder)) { - values.set(i, values.get(i + 1)); - values.set(i + 1, v); - quality.set(i, quality.get(i + 1)); - quality.set(i + 1, q); - last = ZERO; - lastSecondaryOrder = 0; - i = values.size(); - continue; - } - - last = q; - lastSecondaryOrder = secondaryOrdering.apply(v); - } - - int last_element = quality.size(); - while (last_element > 0 && quality.get(--last_element).equals(ZERO)) { - quality.remove(last_element); - values.remove(last_element); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v1/decoder/HttpParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v1/decoder/HttpParser.java deleted file mode 100644 index 1715a51e3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v1/decoder/HttpParser.java +++ /dev/null @@ -1,1787 +0,0 @@ -package com.fireflysource.net.http.common.v1.decoder; - -import com.fireflysource.common.collection.trie.ArrayTernaryTrie; -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.Utf8StringBuilder; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.codec.PreEncodedHttpField; -import com.fireflysource.net.http.common.exception.BadMessageException; -import com.fireflysource.net.http.common.model.*; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.EnumSet; -import java.util.List; -import java.util.Locale; - -import static com.fireflysource.net.http.common.model.HttpComplianceSection.MULTIPLE_CONTENT_LENGTHS; -import static com.fireflysource.net.http.common.model.HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH; -import static com.fireflysource.net.http.common.model.HttpTokens.EndOfContent; - - -/** - * A Parser for 1.0 and 1.1 as defined by RFC7230 - *

    - * This parser parses HTTP client and server messages from buffers - * passed in the {@link #parseNext(ByteBuffer)} method. The parsed - * elements of the HTTP message are passed as event calls to the - * {@link HttpHandler} instance the parser is constructed with. - * If the passed handler is a {@link RequestHandler} then server side - * parsing is performed and if it is a {@link ResponseHandler}, then - * client side parsing is done. - *

    - *

    - * The contract of the {@link HttpHandler} API is that if a call returns - * true then the call to {@link #parseNext(ByteBuffer)} will return as - * soon as possible also with a true response. Typically this indicates - * that the parsing has reached a stage where the caller should process - * the events accumulated by the handler. It is the preferred calling - * style that handling such as calling a servlet to process a request, - * should be done after a true return from {@link #parseNext(ByteBuffer)} - * rather than from within the scope of a call like - * {@link RequestHandler#messageComplete()} - *

    - *

    - * For performance, the parse is heavily dependent on the - * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a - * single pass for both the structure ( : and CRLF ) and semantic (which - * header and value) of a header. Specifically the static {@link HttpHeader#CACHE} - * is used to lookup common combinations of headers and values - * (eg. "Connection: close"), or just header names (eg. "Connection:" ). - * For headers who's value is not known statically (eg. Host, COOKIE) then a - * per parser dynamic Trie of {@link HttpFields} from previous parsed messages - * is used to help the parsing of subsequent messages. - *

    - *

    - * The parser can work in varying compliance modes: - *

    - *
    RFC7230
    (default) Compliance with RFC7230
    - *
    RFC2616
    Wrapped headers and HTTP/0.9 supported
    - *
    LEGACY
    (aka STRICT) Adherence to Servlet Specification requirement for - * exact case of header names, bypassing the header caches, which are case insensitive, - * otherwise equivalent to RFC2616
    - *
    - * - * @see RFC 7230 - */ -public class HttpParser { - public static final LazyLogger LOG = SystemLogger.create(HttpParser.class); - @Deprecated - public final static String STRICT = "com.fireflysource.net.http.common.v1.decoder.HttpParser.STRICT"; - public final static int INITIAL_URI_LENGTH = 256; - /** - * Cache of common {@link HttpField}s including:
      - *
    • Common static combinations such as:
        - *
      • Connection: close - *
      • Accept-Encoding: gzip - *
      • Content-Length: 0 - *
      - *
    • Combinations of Content-Type header for common mime types by common charsets - *
    • Most common headers with null values so that a lookup will at least - * determine the header name even if the name:value combination is not cached - *
    - */ - public final static Trie CACHE = new ArrayTrie<>(2048); - private final static int MAX_CHUNK_LENGTH = Integer.MAX_VALUE / 16 - 16; - private final static EnumSet IDLE_STATES = EnumSet.of(State.START, State.END, State.CLOSE, State.CLOSED); - private final static EnumSet COMPLETE_STATES = EnumSet.of(State.END, State.CLOSE, State.CLOSED); - private static final boolean DEBUG = LOG.isDebugEnabled(); // Cache debug to help branch prediction - - static { - CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE)); - CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE)); - CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.UPGRADE)); - CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate, br")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip,deflate,sdch")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,en;q=0.5")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-US;q=0.8,en;q=0.6")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-AU,en;q=0.9,it-IT;q=0.8,it;q=0.7,en-GB;q=0.6,en-US;q=0.5")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.3")); - CACHE.put(new HttpField(HttpHeader.ACCEPT, "*/*")); - CACHE.put(new HttpField(HttpHeader.ACCEPT, "image/png,image/*;q=0.8,*/*;q=0.5")); - CACHE.put(new HttpField(HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); - CACHE.put(new HttpField(HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")); - CACHE.put(new HttpField(HttpHeader.ACCEPT_RANGES, HttpHeaderValue.BYTES)); - CACHE.put(new HttpField(HttpHeader.PRAGMA, "no-cache")); - CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "private, no-cache, no-cache=Set-Cookie, proxy-revalidate")); - CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "no-cache")); - CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "max-age=0")); - CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH, "0")); - CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING, "gzip")); - CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING, "deflate")); - CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING, "chunked")); - CACHE.put(new HttpField(HttpHeader.EXPIRES, "Fri, 01 Jan 1990 00:00:00 GMT")); - - // Add common Content types as fields - for (String type : new String[]{"text/plain", "text/html", "text/xml", "text/json", "application/json", "application/x-www-form-urlencoded"}) { - HttpField field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type); - CACHE.put(field); - - for (String charset : new String[]{"utf-8", "iso-8859-1"}) { - CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset)); - CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset)); - CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset.toUpperCase(Locale.ENGLISH))); - CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset.toUpperCase(Locale.ENGLISH))); - } - } - - // Add headers with null values so HttpParser can avoid looking up name again for unknown values - for (HttpHeader h : HttpHeader.values()) - if (!CACHE.put(new HttpField(h, (String) null))) - throw new IllegalStateException("CACHE FULL"); - } - - private final HttpHandler handler; - private final RequestHandler requestHandler; - private final ResponseHandler responseHandler; - private final ComplianceHandler complianceHandler; - private final int maxHeaderBytes; - private final HttpCompliance compliance; - private final EnumSet complianceSections; - private final StringBuilder string = new StringBuilder(); - private HttpField field; - private HttpHeader header; - private String headerString; - private String valueString; - private int responseStatus; - private int headerBytes; - private boolean host; - private boolean headerComplete; - - private State state = State.START; - private FieldState fieldState = FieldState.FIELD; - private boolean eof; - private HttpMethod method; - private String methodString; - private HttpVersion version; - private final Utf8StringBuilder uri = new Utf8StringBuilder(INITIAL_URI_LENGTH); // Tune? - private EndOfContent endOfContent; - private boolean hasContentLength; - private long contentLength = -1; - private long contentPosition; - private int chunkLength; - private int chunkPosition; - private boolean headResponse; - private boolean cr; - private ByteBuffer contentChunk; - private Trie fieldCache; - - private int length; - - public HttpParser(RequestHandler handler) { - this(handler, -1, compliance()); - } - - public HttpParser(ResponseHandler handler) { - this(handler, -1, compliance()); - } - - public HttpParser(RequestHandler handler, int maxHeaderBytes) { - this(handler, maxHeaderBytes, compliance()); - } - - - public HttpParser(ResponseHandler handler, int maxHeaderBytes) { - this(handler, maxHeaderBytes, compliance()); - } - - - @Deprecated - public HttpParser(RequestHandler handler, int maxHeaderBytes, boolean strict) { - this(handler, maxHeaderBytes, strict ? HttpCompliance.LEGACY : compliance()); - } - - - @Deprecated - public HttpParser(ResponseHandler handler, int maxHeaderBytes, boolean strict) { - this(handler, maxHeaderBytes, strict ? HttpCompliance.LEGACY : compliance()); - } - - - public HttpParser(RequestHandler handler, HttpCompliance compliance) { - this(handler, -1, compliance); - } - - - public HttpParser(RequestHandler handler, int maxHeaderBytes, HttpCompliance compliance) { - this(handler, null, maxHeaderBytes, compliance == null ? compliance() : compliance); - } - - - public HttpParser(ResponseHandler handler, int maxHeaderBytes, HttpCompliance compliance) { - this(null, handler, maxHeaderBytes, compliance == null ? compliance() : compliance); - } - - - private HttpParser(RequestHandler requestHandler, ResponseHandler responseHandler, int maxHeaderBytes, HttpCompliance compliance) { - handler = requestHandler != null ? requestHandler : responseHandler; - this.requestHandler = requestHandler; - this.responseHandler = responseHandler; - this.maxHeaderBytes = maxHeaderBytes; - this.compliance = compliance; - complianceSections = compliance.sections(); - complianceHandler = (ComplianceHandler) (handler instanceof ComplianceHandler ? handler : null); - } - - private static HttpCompliance compliance() { - boolean strict = Boolean.getBoolean(STRICT); - if (strict) { - LOG.warn("Deprecated property used: " + STRICT); - return HttpCompliance.LEGACY; - } - return HttpCompliance.RFC7230; - } - - public HttpHandler getHandler() { - return handler; - } - - /** - * Check RFC compliance violation - * - * @param violation The compliance section violation - * @return True if the current compliance level is set so as to Not allow this violation - */ - protected boolean complianceViolation(HttpComplianceSection violation) { - return complianceViolation(violation, null); - } - - /** - * Check RFC compliance violation - * - * @param violation The compliance section violation - * @param reason The reason for the violation - * @return True if the current compliance level is set so as to Not allow this violation - */ - protected boolean complianceViolation(HttpComplianceSection violation, String reason) { - if (complianceSections.contains(violation)) - return true; - if (reason == null) - reason = violation.getDescription(); - if (complianceHandler != null) - complianceHandler.onComplianceViolation(compliance, violation, reason); - - return false; - } - - protected void handleViolation(HttpComplianceSection section, String reason) { - if (complianceHandler != null) - complianceHandler.onComplianceViolation(compliance, section, reason); - } - - protected String caseInsensitiveHeader(String orig, String normative) { - if (complianceSections.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)) - return normative; - if (!orig.equals(normative)) - handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, orig); - return orig; - } - - public long getContentLength() { - return contentLength; - } - - public long getContentRead() { - return contentPosition; - } - - /** - * Set if a HEAD response is expected - * - * @param head true if head response is expected - */ - public void setHeadResponse(boolean head) { - headResponse = head; - } - - protected void setResponseStatus(int status) { - responseStatus = status; - } - - public State getState() { - return state; - } - - protected void setState(State state) { - if (DEBUG) - LOG.debug("{} --> {}", this.state, state); - this.state = state; - } - - protected void setState(FieldState state) { - if (DEBUG) - LOG.debug("{}:{} --> {}", this.state, field != null ? field : headerString != null ? headerString : string, state); - fieldState = state; - } - - public boolean inContentState() { - return state.ordinal() >= State.CONTENT.ordinal() && state.ordinal() < State.END.ordinal(); - } - - - public boolean inHeaderState() { - return state.ordinal() < State.CONTENT.ordinal(); - } - - - public boolean isChunking() { - return endOfContent == EndOfContent.CHUNKED_CONTENT; - } - - - public boolean isStart() { - return isState(State.START); - } - - - public boolean isClose() { - return isState(State.CLOSE); - } - - - public boolean isClosed() { - return isState(State.CLOSED); - } - - - public boolean isIdle() { - return IDLE_STATES.contains(state); - } - - - public boolean isComplete() { - return COMPLETE_STATES.contains(state); - } - - - public boolean isState(State state) { - return this.state == state; - } - - - private HttpTokens.Token next(ByteBuffer buffer) { - byte ch = buffer.get(); - - HttpTokens.Token t = HttpTokens.TOKENS[0xff & ch]; - - switch (t.getType()) { - case CNTL: - throw new IllegalCharacterException(state, t, buffer); - - case LF: - cr = false; - break; - - case CR: - if (cr) - throw new BadMessageException("Bad EOL"); - - cr = true; - if (buffer.hasRemaining()) { - // Don't count the CRs and LFs of the chunked encoding. - if (maxHeaderBytes > 0 && (state == State.HEADER || state == State.TRAILER)) - headerBytes++; - return next(buffer); - } - - return null; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case HTAB: - case SPACE: - case OTEXT: - case COLON: - if (cr) - throw new BadMessageException("Bad EOL"); - break; - - default: - break; - } - - return t; - } - - - /* Quick lookahead for the start state looking for a request method or a HTTP version, - * otherwise skip white space until something else to parse. - */ - private boolean quickStart(ByteBuffer buffer) { - if (requestHandler != null) { - method = HttpMethod.lookAheadGet(buffer); - if (method != null) { - methodString = method.getValue(); - buffer.position(buffer.position() + methodString.length() + 1); - - setState(State.SPACE1); - return false; - } - } else if (responseHandler != null) { - version = HttpVersion.lookAheadGet(buffer); - if (version != null) { - buffer.position(buffer.position() + version.getValue().length() + 1); - setState(State.SPACE1); - return false; - } - } - - // Quick start look - while (state == State.START && buffer.hasRemaining()) { - HttpTokens.Token t = next(buffer); - if (t == null) - break; - - switch (t.getType()) { - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: { - string.setLength(0); - string.append(t.getChar()); - setState(requestHandler != null ? State.METHOD : State.RESPONSE_VERSION); - return false; - } - case OTEXT: - case SPACE: - case HTAB: - throw new IllegalCharacterException(state, t, buffer); - - default: - break; - } - - // count this white space as a header byte to avoid DOS - if (maxHeaderBytes > 0 && ++headerBytes > maxHeaderBytes) { - LOG.warn("padding is too large >" + maxHeaderBytes); - throw new BadMessageException(HttpStatus.BAD_REQUEST_400); - } - } - return false; - } - - - private void setString(String s) { - string.setLength(0); - string.append(s); - length = s.length(); - } - - - private String takeString() { - string.setLength(length); - String s = string.toString(); - string.setLength(0); - length = -1; - return s; - } - - - private boolean handleHeaderContentMessage() { - boolean handle_header = handler.headerComplete(); - headerComplete = true; - boolean handle_content = handler.contentComplete(); - boolean handle_message = handler.messageComplete(); - return handle_header || handle_content || handle_message; - } - - - private boolean handleContentMessage() { - boolean handle_content = handler.contentComplete(); - boolean handle_message = handler.messageComplete(); - return handle_content || handle_message; - } - - - /* Parse a request or response line - */ - private boolean parseLine(ByteBuffer buffer) { - boolean handle = false; - - // Process headers - while (state.ordinal() < State.HEADER.ordinal() && buffer.hasRemaining() && !handle) { - // process each character - HttpTokens.Token t = next(buffer); - if (t == null) - break; - - if (maxHeaderBytes > 0 && ++headerBytes > maxHeaderBytes) { - if (state == State.URI) { - LOG.warn("URI is too large >" + maxHeaderBytes); - throw new BadMessageException(HttpStatus.URI_TOO_LONG_414); - } else { - if (requestHandler != null) - LOG.warn("request is too large >" + maxHeaderBytes); - else - LOG.warn("response is too large >" + maxHeaderBytes); - throw new BadMessageException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431); - } - } - - switch (state) { - case METHOD: - switch (t.getType()) { - case SPACE: - length = string.length(); - methodString = takeString(); - - if (complianceSections.contains(HttpComplianceSection.METHOD_CASE_SENSITIVE)) { - HttpMethod method = HttpMethod.CACHE.get(methodString); - if (method != null) - methodString = method.getValue(); - } else { - HttpMethod method = HttpMethod.INSENSITIVE_CACHE.get(methodString); - - if (method != null) { - if (!method.getValue().equals(methodString)) - handleViolation(HttpComplianceSection.METHOD_CASE_SENSITIVE, methodString); - methodString = method.getValue(); - } - } - - setState(State.SPACE1); - break; - - case LF: - throw new BadMessageException("No URI"); - - case ALPHA: - case DIGIT: - case TCHAR: - string.append(t.getChar()); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case RESPONSE_VERSION: - switch (t.getType()) { - case SPACE: - length = string.length(); - String version = takeString(); - this.version = HttpVersion.CACHE.get(version); - checkVersion(); - setState(State.SPACE1); - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - string.append(t.getChar()); - break; - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case SPACE1: - switch (t.getType()) { - case SPACE: - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - if (responseHandler != null) { - if (t.getType() != HttpTokens.Type.DIGIT) { - throw new IllegalCharacterException(state, t, buffer); - } - setState(State.STATUS); - setResponseStatus(t.getByte() - '0'); - } else { - uri.reset(); - setState(State.URI); - // quick scan for space or EoBuffer - if (buffer.hasArray()) { - byte[] array = buffer.array(); - int p = buffer.arrayOffset() + buffer.position(); - int l = buffer.arrayOffset() + buffer.limit(); - int i = p; - while (i < l && array[i] > HttpTokens.SPACE) { - i++; - } - int len = i - p; - headerBytes += len; - - if (maxHeaderBytes > 0 && ++headerBytes > maxHeaderBytes) { - LOG.warn("URI is too large >" + maxHeaderBytes); - throw new BadMessageException(HttpStatus.URI_TOO_LONG_414); - } - uri.append(array, p - 1, len + 1); - buffer.position(i - buffer.arrayOffset()); - } else { - uri.append(t.getByte()); - } - } - break; - - default: - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, requestHandler != null ? "No URI" : "No Status"); - } - break; - - case STATUS: - switch (t.getType()) { - case SPACE: - setState(State.SPACE2); - break; - - case DIGIT: - responseStatus = responseStatus * 10 + (t.getByte() - '0'); - if (responseStatus >= 1000) { - throw new BadMessageException("Bad status"); - } - break; - - case LF: - setState(State.HEADER); - handle |= responseHandler.startResponse(version, responseStatus, null); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case URI: - switch (t.getType()) { - case SPACE: - setState(State.SPACE2); - break; - - case LF: - // HTTP/0.9 - if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9, "No request version")) { - throw new BadMessageException("HTTP/0.9 not supported"); - } - handle = requestHandler.startRequest(methodString, uri.toString(), HttpVersion.HTTP_0_9); - setState(State.END); - BufferUtils.clear(buffer); - handle |= handleHeaderContentMessage(); - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - case OTEXT: - uri.append(t.getByte()); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case SPACE2: - switch (t.getType()) { - case SPACE: - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - string.setLength(0); - string.append(t.getChar()); - if (responseHandler != null) { - length = 1; - setState(State.REASON); - } else { - setState(State.REQUEST_VERSION); - - // try quick look ahead for HTTP Version - HttpVersion version; - if (buffer.position() > 0 && buffer.hasArray()) - version = HttpVersion.lookAheadGet(buffer.array(), buffer.arrayOffset() + buffer.position() - 1, buffer.arrayOffset() + buffer.limit()); - else - version = HttpVersion.CACHE.getBest(buffer, 0, buffer.remaining()); - - if (version != null) { - int pos = buffer.position() + version.getValue().length() - 1; - if (pos < buffer.limit()) { - byte n = buffer.get(pos); - if (n == HttpTokens.CARRIAGE_RETURN) { - cr = true; - this.version = version; - checkVersion(); - string.setLength(0); - buffer.position(pos + 1); - } else if (n == HttpTokens.LINE_FEED) { - this.version = version; - checkVersion(); - string.setLength(0); - buffer.position(pos); - } - } - } - } - break; - - case LF: - if (responseHandler != null) { - setState(State.HEADER); - handle |= responseHandler.startResponse(version, responseStatus, null); - } else { - // HTTP/0.9 - if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9, "No request version")) { - throw new BadMessageException("HTTP/0.9 not supported"); - } - handle = requestHandler.startRequest(methodString, uri.toString(), HttpVersion.HTTP_0_9); - setState(State.END); - BufferUtils.clear(buffer); - handle |= handleHeaderContentMessage(); - } - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case REQUEST_VERSION: - switch (t.getType()) { - case LF: - if (version == null) { - length = string.length(); - version = HttpVersion.CACHE.get(takeString()); - } - checkVersion(); - - // Should we try to cache header fields? - if (fieldCache == null && version.getVersion() >= HttpVersion.HTTP_1_1.getVersion() && handler.getHeaderCacheSize() > 0) { - int header_cache = handler.getHeaderCacheSize(); - fieldCache = new ArrayTernaryTrie<>(header_cache); - } - - setState(State.HEADER); - - handle |= requestHandler.startRequest(methodString, uri.toString(), version); - continue; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - string.append(t.getChar()); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case REASON: - switch (t.getType()) { - case LF: - String reason = takeString(); - setState(State.HEADER); - handle |= responseHandler.startResponse(version, responseStatus, reason); - continue; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - case OTEXT: // TODO should this be UTF8 - string.append(t.getChar()); - length = string.length(); - break; - - case SPACE: - case HTAB: - string.append(t.getChar()); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - default: - throw new IllegalStateException(state.toString()); - } - } - - return handle; - } - - private void checkVersion() { - if (version == null) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version"); - } - if (version.getVersion() < 10 || version.getVersion() > 20) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Version"); - } - } - - private void parsedHeader() { - // handler last header if any. Delayed to here just in case there was a continuation line (above) - if (headerString != null || valueString != null) { - // Handle known headers - if (header != null) { - boolean add_to_connection_trie = false; - switch (header) { - case CONTENT_LENGTH: - if (hasContentLength) { - if (complianceViolation(MULTIPLE_CONTENT_LENGTHS)) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, MULTIPLE_CONTENT_LENGTHS.getDescription()); - } - if (convertContentLength(valueString) != contentLength) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, MULTIPLE_CONTENT_LENGTHS.getDescription()); - } - } - hasContentLength = true; - - if (endOfContent == EndOfContent.CHUNKED_CONTENT && complianceViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH)) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Content-Length"); - } - if (endOfContent != EndOfContent.CHUNKED_CONTENT) { - contentLength = convertContentLength(valueString); - if (contentLength <= 0) { - endOfContent = EndOfContent.NO_CONTENT; - } else { - endOfContent = EndOfContent.CONTENT_LENGTH; - } - } - break; - - case TRANSFER_ENCODING: - if (hasContentLength && complianceViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH)) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Transfer-Encoding and Content-Length"); - } - if (HttpHeaderValue.CHUNKED.is(valueString)) { - endOfContent = EndOfContent.CHUNKED_CONTENT; - contentLength = -1; - } else { - List values = new QuotedCSV(valueString).getValues(); - if (values.size() > 0 && HttpHeaderValue.CHUNKED.is(values.get(values.size() - 1))) { - endOfContent = EndOfContent.CHUNKED_CONTENT; - contentLength = -1; - } else if (values.stream().anyMatch(HttpHeaderValue.CHUNKED::is)) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad chunking"); - } - } - break; - - case HOST: - host = true; - if (!(field instanceof HostPortHttpField) && valueString != null && !valueString.isEmpty()) { - field = new HostPortHttpField(header, - complianceSections.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE) ? header.getValue() : headerString, - valueString); - add_to_connection_trie = fieldCache != null; - } - break; - - case CONNECTION: - // Don't cache headers if not persistent - if (HttpHeaderValue.CLOSE.is(valueString) || new QuotedCSV(valueString).getValues().stream().anyMatch(HttpHeaderValue.CLOSE::is)) { - fieldCache = null; - } - break; - - case AUTHORIZATION: - case ACCEPT: - case ACCEPT_CHARSET: - case ACCEPT_ENCODING: - case ACCEPT_LANGUAGE: - case COOKIE: - case CACHE_CONTROL: - case USER_AGENT: - add_to_connection_trie = fieldCache != null && field == null; - break; - - default: - break; - - } - - if (add_to_connection_trie && !fieldCache.isFull() && header != null && valueString != null) { - if (field == null) { - field = new HttpField(header, caseInsensitiveHeader(headerString, header.getValue()), valueString); - } - fieldCache.put(field); - } - } - handler.parsedHeader(field != null ? field : new HttpField(header, headerString, valueString)); - } - - headerString = valueString = null; - header = null; - field = null; - } - - private void parsedTrailer() { - // handler last header if any. Delayed to here just in case there was a continuation line (above) - if (headerString != null || valueString != null) { - handler.parsedTrailer(field != null ? field : new HttpField(header, headerString, valueString)); - } - headerString = valueString = null; - header = null; - field = null; - } - - private long convertContentLength(String valueString) { - try { - return Long.parseLong(valueString); - } catch (NumberFormatException e) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Invalid Content-Length Value", e); - } - } - - - /* - * Parse the message headers and return true if the handler has signalled for a return - */ - protected boolean parseFields(ByteBuffer buffer) { - // Process headers - while ((state == State.HEADER || state == State.TRAILER) && buffer.hasRemaining()) { - // process each character - HttpTokens.Token t = next(buffer); - if (t == null) { - break; - } - if (maxHeaderBytes > 0 && ++headerBytes > maxHeaderBytes) { - boolean header = state == State.HEADER; - LOG.warn("{} is too large {}>{}", header ? "Header" : "Trailer", headerBytes, maxHeaderBytes); - throw new BadMessageException(header ? - HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE_431 : - HttpStatus.PAYLOAD_TOO_LARGE_413); - } - - switch (fieldState) { - case FIELD: - switch (t.getType()) { - case COLON: - case SPACE: - case HTAB: { - if (complianceViolation(HttpComplianceSection.NO_FIELD_FOLDING, headerString)) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Header Folding"); - } - // header value without name - continuation? - if (valueString == null || valueString.isEmpty()) { - string.setLength(0); - length = 0; - } else { - setString(valueString); - string.append(' '); - length++; - valueString = null; - } - setState(FieldState.VALUE); - break; - } - - case LF: { - // process previous header - if (state == State.HEADER) { - parsedHeader(); - } else { - parsedTrailer(); - } - contentPosition = 0; - - // End of headers or trailers? - if (state == State.TRAILER) { - setState(State.END); - return handler.messageComplete(); - } - - // Was there a required host header? - if (!host && version == HttpVersion.HTTP_1_1 && requestHandler != null) { - throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "No Host"); - } - - // is it a response that cannot have a body? - if (responseHandler != null && // response - (responseStatus == 304 || // not-modified response - responseStatus == 204 || // no-content response - responseStatus < 200)) { // 1xx response - endOfContent = EndOfContent.NO_CONTENT; // ignore any other headers set - // else if we don't know framing - } else if (endOfContent == EndOfContent.UNKNOWN_CONTENT) { - if (responseStatus == 0 // request - || responseStatus == 304 // not-modified response - || responseStatus == 204 // no-content response - || responseStatus < 200) { // 1xx response - endOfContent = EndOfContent.NO_CONTENT; - } else { - endOfContent = EndOfContent.EOF_CONTENT; - } - } - - // How is the message ended? - switch (endOfContent) { - case EOF_CONTENT: { - setState(State.EOF_CONTENT); - boolean handle = handler.headerComplete(); - headerComplete = true; - return handle; - } - case CHUNKED_CONTENT: { - setState(State.CHUNKED_CONTENT); - boolean handle = handler.headerComplete(); - headerComplete = true; - return handle; - } - case NO_CONTENT: { - setState(State.END); - return handleHeaderContentMessage(); - } - default: { - setState(State.CONTENT); - boolean handle = handler.headerComplete(); - headerComplete = true; - return handle; - } - } - } - - case ALPHA: - case DIGIT: - case TCHAR: { - // process previous header - if (state == State.HEADER) { - parsedHeader(); - } else { - parsedTrailer(); - } - // handle new header - if (buffer.hasRemaining()) { - // Try a look ahead for the known header name and value. - HttpField cached_field = fieldCache == null ? null : fieldCache.getBest(buffer, -1, buffer.remaining()); - if (cached_field == null) { - cached_field = CACHE.getBest(buffer, -1, buffer.remaining()); - } - if (cached_field != null) { - String n = cached_field.getName(); - String v = cached_field.getValue(); - - if (!complianceSections.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)) { - // Have to get the fields exactly from the buffer to match case - String en = BufferUtils.toString(buffer, buffer.position() - 1, n.length(), StandardCharsets.US_ASCII); - if (!n.equals(en)) { - handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE, en); - n = en; - cached_field = new HttpField(cached_field.getHeader(), n, v); - } - } - - if (v != null && !complianceSections.contains(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE)) { - String ev = BufferUtils.toString(buffer, buffer.position() + n.length() + 1, v.length(), StandardCharsets.ISO_8859_1); - if (!v.equals(ev)) { - handleViolation(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE, ev + "!=" + v); - v = ev; - cached_field = new HttpField(cached_field.getHeader(), n, v); - } - } - - header = cached_field.getHeader(); - headerString = n; - - if (v == null) { - // Header only - setState(FieldState.VALUE); - string.setLength(0); - length = 0; - buffer.position(buffer.position() + n.length() + 1); - break; - } - - // Header and value - int pos = buffer.position() + n.length() + v.length() + 1; - byte peek = buffer.get(pos); - if (peek == HttpTokens.CARRIAGE_RETURN || peek == HttpTokens.LINE_FEED) { - field = cached_field; - valueString = v; - setState(FieldState.IN_VALUE); - - if (peek == HttpTokens.CARRIAGE_RETURN) { - cr = true; - buffer.position(pos + 1); - } else { - buffer.position(pos); - } - break; - } - setState(FieldState.IN_VALUE); - setString(v); - buffer.position(pos); - break; - } - } - - // New header - setState(FieldState.IN_NAME); - string.setLength(0); - string.append(t.getChar()); - length = 1; - } - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case IN_NAME: - switch (t.getType()) { - case SPACE: - case HTAB: - //Ignore trailing whitespaces ? - if (!complianceViolation(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME, null)) { - headerString = takeString(); - header = HttpHeader.CACHE.get(headerString); - length = -1; - setState(FieldState.WS_AFTER_NAME); - break; - } - throw new IllegalCharacterException(state, t, buffer); - - case COLON: - headerString = takeString(); - header = HttpHeader.CACHE.get(headerString); - length = -1; - setState(FieldState.VALUE); - break; - - case LF: - headerString = takeString(); - header = HttpHeader.CACHE.get(headerString); - string.setLength(0); - valueString = ""; - length = -1; - - if (!complianceViolation(HttpComplianceSection.FIELD_COLON, headerString)) { - setState(FieldState.FIELD); - break; - } - throw new IllegalCharacterException(state, t, buffer); - - case ALPHA: - case DIGIT: - case TCHAR: - string.append(t.getChar()); - length = string.length(); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case WS_AFTER_NAME: - - switch (t.getType()) { - case SPACE: - case HTAB: - break; - - case COLON: - setState(FieldState.VALUE); - break; - - case LF: - if (!complianceViolation(HttpComplianceSection.FIELD_COLON, headerString)) { - setState(FieldState.FIELD); - break; - } - throw new IllegalCharacterException(state, t, buffer); - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case VALUE: - switch (t.getType()) { - case LF: - string.setLength(0); - valueString = ""; - length = -1; - - setState(FieldState.FIELD); - break; - - case SPACE: - case HTAB: - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - case OTEXT: // TODO review? should this be a utf8 string? - string.append(t.getChar()); - length = string.length(); - setState(FieldState.IN_VALUE); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - case IN_VALUE: - switch (t.getType()) { - case LF: - if (length > 0) { - valueString = takeString(); - length = -1; - } - setState(FieldState.FIELD); - break; - - case SPACE: - case HTAB: - string.append(t.getChar()); - break; - - case ALPHA: - case DIGIT: - case TCHAR: - case VCHAR: - case COLON: - case OTEXT: // TODO review? should this be a utf8 string? - string.append(t.getChar()); - length = string.length(); - break; - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - - default: - throw new IllegalStateException(state.toString()); - - } - } - - return false; - } - - - /** - * Parse until next Event. - * - * @param buffer the buffer to parse - * @return True if an {@link RequestHandler} method was called and it returned true; - */ - public boolean parseNext(ByteBuffer buffer) { - if (DEBUG) - LOG.debug("parseNext s={} {}", state, BufferUtils.toDetailString(buffer)); - try { - // Start a request/response - if (state == State.START) { - version = null; - method = null; - methodString = null; - endOfContent = EndOfContent.UNKNOWN_CONTENT; - header = null; - if (quickStart(buffer)) { - return true; - } - } - - // Request/response line - if (state.ordinal() >= State.START.ordinal() && state.ordinal() < State.HEADER.ordinal()) { - if (parseLine(buffer)) { - return true; - } - } - - // parse headers - if (state == State.HEADER) { - if (parseFields(buffer)) { - return true; - } - } - - // parse content - if (state.ordinal() >= State.CONTENT.ordinal() && state.ordinal() < State.TRAILER.ordinal()) { - // Handle HEAD response - if (responseStatus > 0 && headResponse) { - setState(State.END); - return handleContentMessage(); - } else { - if (parseContent(buffer)) { - return true; - } - } - } - - // parse headers - if (state == State.TRAILER) { - if (parseFields(buffer)) { - return true; - } - } - - // handle end states - if (state == State.END) { - // eat white space - while (buffer.remaining() > 0 && buffer.get(buffer.position()) <= HttpTokens.SPACE) { - buffer.get(); - } - } else if (isClose() || isClosed()) { - BufferUtils.clear(buffer); - } - - // Handle EOF - if (eof && !buffer.hasRemaining()) { - switch (state) { - case CLOSED: - break; - - case START: - setState(State.CLOSED); - handler.earlyEOF(); - break; - - case END: - case CLOSE: - setState(State.CLOSED); - break; - - case EOF_CONTENT: - case TRAILER: - if (fieldState == FieldState.FIELD) { - // Be forgiving of missing last CRLF - setState(State.CLOSED); - return handleContentMessage(); - } - setState(State.CLOSED); - handler.earlyEOF(); - break; - - case CONTENT: - case CHUNKED_CONTENT: - case CHUNK_SIZE: - case CHUNK_PARAMS: - case CHUNK: - setState(State.CLOSED); - handler.earlyEOF(); - break; - - default: - if (DEBUG) - LOG.debug("{} EOF in {}", this, state); - setState(State.CLOSED); - handler.badMessage(new BadMessageException(HttpStatus.BAD_REQUEST_400)); - break; - } - } - } catch (BadMessageException x) { - BufferUtils.clear(buffer); - badMessage(x); - } catch (Throwable x) { - BufferUtils.clear(buffer); - badMessage(new BadMessageException(HttpStatus.BAD_REQUEST_400, requestHandler != null ? "Bad Request" : "Bad Response", x)); - } - return false; - } - - protected void badMessage(BadMessageException x) { - if (DEBUG) - LOG.debug("Parse exception: " + this + " for " + handler, x); - setState(State.CLOSE); - if (headerComplete) - handler.earlyEOF(); - else - handler.badMessage(x); - } - - protected boolean parseContent(ByteBuffer buffer) { - int remaining = buffer.remaining(); - if (remaining == 0 && state == State.CONTENT) { - long content = contentLength - contentPosition; - if (content == 0) { - setState(State.END); - return handleContentMessage(); - } - } - - // Handle _content - while (state.ordinal() < State.TRAILER.ordinal() && remaining > 0) { - switch (state) { - case EOF_CONTENT: - contentChunk = buffer.duplicate(); - contentPosition += remaining; - buffer.position(buffer.position() + remaining); - if (handler.content(contentChunk)) { - return true; - } - break; - - case CONTENT: { - long content = contentLength - contentPosition; - if (content == 0) { - setState(State.END); - return handleContentMessage(); - } else { - contentChunk = buffer.duplicate(); - - // limit content by expected size - if (remaining > content) { - // We can cast remaining to an int as we know that it is smaller than - // or equal to length which is already an int. - contentChunk.limit(contentChunk.position() + (int) content); - } - - contentPosition += contentChunk.remaining(); - buffer.position(buffer.position() + contentChunk.remaining()); - - if (handler.content(contentChunk)) { - return true; - } - if (contentPosition == contentLength) { - setState(State.END); - return handleContentMessage(); - } - } - break; - } - - case CHUNKED_CONTENT: { - HttpTokens.Token t = next(buffer); - if (t == null) - break; - switch (t.getType()) { - case LF: - break; - - case DIGIT: - chunkLength = t.getHexDigit(); - chunkPosition = 0; - setState(State.CHUNK_SIZE); - break; - - case ALPHA: - if (t.isHexDigit()) { - chunkLength = t.getHexDigit(); - chunkPosition = 0; - setState(State.CHUNK_SIZE); - break; - } - throw new IllegalCharacterException(state, t, buffer); - - default: - throw new IllegalCharacterException(state, t, buffer); - } - break; - } - - case CHUNK_SIZE: { - HttpTokens.Token t = next(buffer); - if (t == null) { - break; - } - switch (t.getType()) { - case LF: - if (chunkLength == 0) { - setState(State.TRAILER); - if (handler.contentComplete()) - return true; - } else { - setState(State.CHUNK); - } - break; - - case SPACE: - setState(State.CHUNK_PARAMS); - break; - - default: - if (t.isHexDigit()) { - if (chunkLength > MAX_CHUNK_LENGTH) { - throw new BadMessageException(HttpStatus.PAYLOAD_TOO_LARGE_413); - } - chunkLength = chunkLength * 16 + t.getHexDigit(); - } else { - setState(State.CHUNK_PARAMS); - } - } - break; - } - - case CHUNK_PARAMS: { - HttpTokens.Token t = next(buffer); - if (t == null) { - break; - } - if (t.getType() == HttpTokens.Type.LF) { - if (chunkLength == 0) { - setState(State.TRAILER); - if (handler.contentComplete()) { - return true; - } - } else { - setState(State.CHUNK); - } - } - break; - } - - case CHUNK: { - int chunk = chunkLength - chunkPosition; - if (chunk == 0) { - setState(State.CHUNKED_CONTENT); - } else { - contentChunk = buffer.duplicate(); - - if (remaining > chunk) { - contentChunk.limit(contentChunk.position() + chunk); - } - chunk = contentChunk.remaining(); - - contentPosition += chunk; - chunkPosition += chunk; - buffer.position(buffer.position() + chunk); - if (handler.content(contentChunk)) { - return true; - } - } - break; - } - - case CLOSED: { - BufferUtils.clear(buffer); - return false; - } - - default: - break; - - } - - remaining = buffer.remaining(); - } - return false; - } - - - public boolean isAtEOF() { - return eof; - } - - - /** - * Signal that the associated data source is at EOF - */ - public void atEOF() { - if (DEBUG) - LOG.debug("atEOF {}", this); - eof = true; - } - - - /** - * Request that the associated data source be closed - */ - public void close() { - if (DEBUG) - LOG.debug("close {}", this); - setState(State.CLOSE); - } - - - public void reset() { - if (DEBUG) { - LOG.debug("reset {}", this); - } - // reset state - if (state == State.CLOSE || state == State.CLOSED) { - return; - } - setState(State.START); - endOfContent = EndOfContent.UNKNOWN_CONTENT; - contentLength = -1; - hasContentLength = false; - contentPosition = 0; - responseStatus = 0; - contentChunk = null; - headerBytes = 0; - host = false; - headerComplete = false; - } - - public Trie getFieldCache() { - return fieldCache; - } - - @Override - public String toString() { - return String.format("%s{s=%s,%d of %d}", - getClass().getSimpleName(), - state, - contentPosition, - contentLength); - } - - - // States - public enum FieldState { - FIELD, - IN_NAME, - VALUE, - IN_VALUE, - WS_AFTER_NAME, - } - - - // States - public enum State { - START, - METHOD, - RESPONSE_VERSION, - SPACE1, - STATUS, - URI, - SPACE2, - REQUEST_VERSION, - REASON, - PROXY, - HEADER, - CONTENT, - EOF_CONTENT, - CHUNKED_CONTENT, - CHUNK_SIZE, - CHUNK_PARAMS, - CHUNK, - TRAILER, - END, - CLOSE, // The associated stream/endpoint should be closed - CLOSED // The associated stream/endpoint is at EOF - } - - - /* Event Handler interface - * These methods return true if the caller should process the events - * so far received (eg return from parseNext and call HttpChannel.handle). - * If multiple callbacks are called in sequence (eg - * headerComplete then messageComplete) from the same point in the parsing - * then it is sufficient for the caller to process the events only once. - */ - public interface HttpHandler { - boolean content(ByteBuffer item); - - boolean headerComplete(); - - boolean contentComplete(); - - boolean messageComplete(); - - /** - * This is the method called by parser when a HTTP Header name and value is found - * - * @param field The field parsed - */ - void parsedHeader(HttpField field); - - /** - * This is the method called by parser when a HTTP Trailer name and value is found - * - * @param field The field parsed - */ - default void parsedTrailer(HttpField field) { - } - - - /** - * Called to signal that an EOF was received unexpectedly - * during the parsing of an HTTP message - */ - void earlyEOF(); - - - /** - * Called to signal that a bad HTTP message has been received. - * - * @param failure the failure with the bad message information - */ - default void badMessage(BadMessageException failure) { - badMessage(failure.getCode(), failure.getReason()); - } - - /** - * @deprecated use {@link #badMessage(BadMessageException)} instead - */ - @Deprecated - default void badMessage(int status, String reason) { - } - - - /** - * @return the size in bytes of the per parser header cache - */ - int getHeaderCacheSize(); - } - - - public interface RequestHandler extends HttpHandler { - /** - * This is the method called by parser when the HTTP request line is parsed - * - * @param method The method - * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused. - * @param version the http version in use - * @return true if handling parsing should return. - */ - boolean startRequest(String method, String uri, HttpVersion version); - - } - - - public interface ResponseHandler extends HttpHandler { - /** - * This is the method called by parser when the HTTP request line is parsed - * - * @param version the http version in use - * @param status the response status - * @param reason the response reason phrase - * @return true if handling parsing should return - */ - boolean startResponse(HttpVersion version, int status, String reason); - } - - - public interface ComplianceHandler extends HttpHandler { - @Deprecated - default void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason) { - } - - default void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String details) { - onComplianceViolation(compliance, HttpCompliance.requiredCompliance(violation), details); - } - } - - - private static class IllegalCharacterException extends BadMessageException { - private IllegalCharacterException(State state, HttpTokens.Token token, ByteBuffer buffer) { - super(400, String.format("Illegal character %s", token)); - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Illegal character %s in state=%s for buffer %s", token, state, BufferUtils.toDetailString(buffer))); - } - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v1/encoder/HttpGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v1/encoder/HttpGenerator.java deleted file mode 100644 index b4b7c9e66..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v1/encoder/HttpGenerator.java +++ /dev/null @@ -1,801 +0,0 @@ -package com.fireflysource.net.http.common.v1.encoder; - -import com.fireflysource.common.collection.trie.ArrayTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.ProjectVersion; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.codec.PreEncodedHttpField; -import com.fireflysource.net.http.common.exception.BadMessageException; -import com.fireflysource.net.http.common.model.*; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.function.Supplier; - -import static com.fireflysource.net.http.common.model.HttpStatus.INTERNAL_SERVER_ERROR_500; -import static com.fireflysource.net.http.common.model.HttpTokens.EndOfContent; - -/** - * HttpGenerator. Builds HTTP Messages. - *

    - * If the system property "com.fireflysource.net.http.common.v1.encoder.HttpGenerator.STRICT" is set to true, - * then the generator will strictly pass on the exact strings received from methods and header - * fields. Otherwise a fast case insensitive string lookup is used that may alter the - * case and white space of some methods/headers - */ -public class HttpGenerator { - private final static LazyLogger LOG = SystemLogger.create(HttpGenerator.class); - - public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 100, null, null, -1); - public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 102, null, null, -1); - public final static MetaData.Response RESPONSE_500_INFO = - new MetaData.Response(HttpVersion.HTTP_1_1, INTERNAL_SERVER_ERROR_500, null, new HttpFields() {{ - put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); - }}, 0); - - // other statics - public static final int CHUNK_SIZE = 12; - private final static byte[] COLON_SPACE = new byte[]{':', ' '}; - private final static int SEND_SERVER = 0x01; - private final static int SEND_XPOWERED_BY = 0x02; - private final static Trie ASSUMED_CONTENT_METHODS = new ArrayTrie<>(8); - - // common _content - private static final byte[] ZERO_CHUNK = {(byte) '0', (byte) '\015', (byte) '\012'}; - private static final byte[] LAST_CHUNK = {(byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'}; - private static final byte[] CONTENT_LENGTH_0 = StringUtils.getBytes("Content-Length: 0\r\n"); - private static final byte[] CONNECTION_CLOSE = StringUtils.getBytes("Connection: close\r\n"); - private static final byte[] HTTP_1_1_SPACE = StringUtils.getBytes(HttpVersion.HTTP_1_1 + " "); - private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtils.getBytes("Transfer-Encoding: chunked\r\n"); - private static final byte[][] SEND = new byte[][]{ - new byte[0], - StringUtils.getBytes("Server: Firefly(" + ProjectVersion.getValue() + ")\r\n"), - StringUtils.getBytes("X-Powered-By: Firefly(" + ProjectVersion.getValue() + ")\r\n"), - StringUtils.getBytes( - "Server: Firefly(" + ProjectVersion.getValue() + ")\r\n" + - "X-Powered-By: Firefly(" + ProjectVersion.getValue() + ")\r\n") - }; - private static final PreparedResponse[] PREPARED_RESPONSE = new PreparedResponse[HttpStatus.MAX_CODE + 1]; - - static { - ASSUMED_CONTENT_METHODS.put(HttpMethod.POST.getValue(), Boolean.TRUE); - ASSUMED_CONTENT_METHODS.put(HttpMethod.PUT.getValue(), Boolean.TRUE); - } - - static { - int versionLength = HttpVersion.HTTP_1_1.toString().length(); - - for (int i = 0; i < PREPARED_RESPONSE.length; i++) { - HttpStatus.Code code = HttpStatus.getCode(i); - if (code == null) { - continue; - } - String reason = code.getMessage(); - byte[] line = new byte[versionLength + 5 + reason.length() + 2]; - ByteBuffer.wrap(HttpVersion.HTTP_1_1.getBytes()).get(line, 0, versionLength); - line[versionLength] = ' '; - line[versionLength + 1] = (byte) ('0' + i / 100); - line[versionLength + 2] = (byte) ('0' + (i % 100) / 10); - line[versionLength + 3] = (byte) ('0' + (i % 10)); - line[versionLength + 4] = ' '; - for (int j = 0; j < reason.length(); j++) { - line[versionLength + 5 + j] = (byte) reason.charAt(j); - } - line[versionLength + 5 + reason.length()] = HttpTokens.CARRIAGE_RETURN; - line[versionLength + 6 + reason.length()] = HttpTokens.LINE_FEED; - - PREPARED_RESPONSE[i] = new PreparedResponse(); - PREPARED_RESPONSE[i].schemeCode = Arrays.copyOfRange(line, 0, versionLength + 5); - PREPARED_RESPONSE[i].reason = Arrays.copyOfRange(line, versionLength + 5, line.length - 2); - PREPARED_RESPONSE[i].responseLine = line; - } - } - - private final int send; - private State state = State.START; - private EndOfContent endOfContent = EndOfContent.UNKNOWN_CONTENT; - private long contentPrepared = 0; - private boolean noContentResponse = false; - private Boolean persistent = null; - private Supplier trailers = null; - // data - private boolean needCRLF = false; - - - public HttpGenerator() { - this(false, false); - } - - - public HttpGenerator(boolean sendServerVersion, boolean sendXPoweredBy) { - send = (sendServerVersion ? SEND_SERVER : 0) | (sendXPoweredBy ? SEND_XPOWERED_BY : 0); - } - - public static void setServerVersion(String serverVersion) { - SEND[SEND_SERVER] = StringUtils.getBytes("Server: " + serverVersion + "\r\n"); - SEND[SEND_XPOWERED_BY] = StringUtils.getBytes("X-Powered-By: " + serverVersion + "\r\n"); - SEND[SEND_SERVER | SEND_XPOWERED_BY] = StringUtils.getBytes( - "Server: " + serverVersion + "\r\n" + - "X-Powered-By: " + serverVersion + "\r\n"); - } - - private static void putContentLength(ByteBuffer header, long contentLength) { - if (contentLength == 0) { - header.put(CONTENT_LENGTH_0); - } else { - header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); - BufferUtils.putDecLong(header, contentLength); - header.put(HttpTokens.CRLF); - } - } - - public static byte[] getReasonBuffer(int code) { - PreparedResponse status = code < PREPARED_RESPONSE.length ? PREPARED_RESPONSE[code] : null; - if (status != null) { - return status.reason; - } else { - return null; - } - } - - private static void putSanitisedName(String s, ByteBuffer buffer) { - int l = s.length(); - for (int i = 0; i < l; i++) { - char c = s.charAt(i); - - if (c < 0 || c > 0xff || c == '\r' || c == '\n' || c == ':') { - buffer.put((byte) '?'); - } else { - buffer.put((byte) (0xff & c)); - } - } - } - - private static void putSanitisedValue(String s, ByteBuffer buffer) { - int l = s.length(); - for (int i = 0; i < l; i++) { - char c = s.charAt(i); - - if (c < 0 || c > 0xff || c == '\r' || c == '\n') { - buffer.put((byte) ' '); - } else { - buffer.put((byte) (0xff & c)); - } - } - } - - public static void putTo(HttpField field, ByteBuffer bufferInFillMode) { - if (field instanceof PreEncodedHttpField) { - ((PreEncodedHttpField) field).putTo(bufferInFillMode, HttpVersion.HTTP_1_0); - } else { - HttpHeader header = field.getHeader(); - if (header != null) { - bufferInFillMode.put(header.getBytesColonSpace()); - } else { - putSanitisedName(field.getName(), bufferInFillMode); - bufferInFillMode.put(COLON_SPACE); - } - putSanitisedValue(field.getValue(), bufferInFillMode); - BufferUtils.putCRLF(bufferInFillMode); - } - } - - public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) { - for (HttpField field : fields) { - if (field != null) { - putTo(field, bufferInFillMode); - } - } - BufferUtils.putCRLF(bufferInFillMode); - } - - public void reset() { - state = State.START; - endOfContent = EndOfContent.UNKNOWN_CONTENT; - noContentResponse = false; - persistent = null; - contentPrepared = 0; - needCRLF = false; - trailers = null; - } - - @Deprecated - public boolean getSendServerVersion() { - return (send & SEND_SERVER) != 0; - } - - @Deprecated - public void setSendServerVersion(boolean sendServerVersion) { - throw new UnsupportedOperationException(); - } - - public State getState() { - return state; - } - - public boolean isState(State state) { - return this.state == state; - } - - public boolean isIdle() { - return state == State.START; - } - - public boolean isEnd() { - return state == State.END; - } - - public boolean isCommitted() { - return state.ordinal() >= State.COMMITTED.ordinal(); - } - - public boolean isChunking() { - return endOfContent == EndOfContent.CHUNKED_CONTENT; - } - - public boolean isNoContent() { - return noContentResponse; - } - - /** - * @return true, if known to be persistent - */ - public boolean isPersistent() { - return Boolean.TRUE.equals(persistent); - } - - public void setPersistent(boolean persistent) { - this.persistent = persistent; - } - - public boolean isWritten() { - return contentPrepared > 0; - } - - public long getContentPrepared() { - return contentPrepared; - } - - public void abort() { - persistent = false; - state = State.END; - endOfContent = null; - } - - public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) { - switch (state) { - case START: { - if (info == null) { - return Result.NEED_INFO; - } - if (header == null) { - return Result.NEED_HEADER; - } - // prepare the header - int pos = BufferUtils.flipToFill(header); - try { - // generate ResponseLine - generateRequestLine(info, header); - - if (info.getHttpVersion() == HttpVersion.HTTP_0_9) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "HTTP/0.9 not supported"); - } - generateHeaders(info, header, content, last); - - boolean expect100 = info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.getValue()); - - if (expect100) { - state = State.COMMITTED; - } else { - // handle the content. - int len = BufferUtils.length(content); - if (len > 0) { - contentPrepared += len; - if (isChunking()) { - prepareChunk(header, len); - } - } - state = last ? State.COMPLETING : State.COMMITTED; - } - - return Result.FLUSH; - } catch (BadMessageException e) { - throw e; - } catch (BufferOverflowException e) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Request header too large", e); - } catch (Exception e) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, e.getMessage(), e); - } finally { - BufferUtils.flipToFlush(header, pos); - } - } - - case COMMITTED: { - return committed(chunk, content, last); - } - - case COMPLETING: { - return completing(chunk, content); - } - - case END: - if (BufferUtils.hasContent(content)) { - if (LOG.isDebugEnabled()) { - LOG.debug("discarding content in COMPLETING"); - } - BufferUtils.clear(content); - } - return Result.DONE; - - default: - throw new IllegalStateException(); - } - } - - private Result committed(ByteBuffer chunk, ByteBuffer content, boolean last) { - int len = BufferUtils.length(content); - - // handle the content. - if (len > 0) { - if (isChunking()) { - if (chunk == null) { - return Result.NEED_CHUNK; - } - BufferUtils.clearToFill(chunk); - prepareChunk(chunk, len); - BufferUtils.flipToFlush(chunk, 0); - } - contentPrepared += len; - } - - if (last) { - state = State.COMPLETING; - return len > 0 ? Result.FLUSH : Result.CONTINUE; - } - return len > 0 ? Result.FLUSH : Result.DONE; - } - - private Result completing(ByteBuffer chunk, ByteBuffer content) { - if (BufferUtils.hasContent(content)) { - if (LOG.isDebugEnabled()) { - LOG.debug("discarding content in COMPLETING"); - } - BufferUtils.clear(content); - } - - if (isChunking()) { - if (trailers != null) { - // Do we need a chunk buffer? - if (chunk == null || chunk.capacity() <= CHUNK_SIZE) { - return Result.NEED_CHUNK_TRAILER; - } - HttpFields trailers = this.trailers.get(); - - if (trailers != null) { - // Write the last chunk - BufferUtils.clearToFill(chunk); - generateTrailers(chunk, trailers); - BufferUtils.flipToFlush(chunk, 0); - endOfContent = EndOfContent.UNKNOWN_CONTENT; - return Result.FLUSH; - } - } - - // Do we need a chunk buffer? - if (chunk == null) { - return Result.NEED_CHUNK; - } - // Write the last chunk - BufferUtils.clearToFill(chunk); - prepareChunk(chunk, 0); - BufferUtils.flipToFlush(chunk, 0); - endOfContent = EndOfContent.UNKNOWN_CONTENT; - return Result.FLUSH; - } - - state = State.END; - return Boolean.TRUE.equals(persistent) ? Result.DONE : Result.SHUTDOWN_OUT; - - } - - public Result generateResponse(MetaData.Response info, boolean head, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) { - switch (state) { - case START: { - if (info == null) { - return Result.NEED_INFO; - } - HttpVersion version = info.getHttpVersion(); - if (version == null) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "No version"); - } - if (version == HttpVersion.HTTP_0_9) { - persistent = false; - endOfContent = EndOfContent.EOF_CONTENT; - if (BufferUtils.hasContent(content)) { - contentPrepared += content.remaining(); - } - state = last ? State.COMPLETING : State.COMMITTED; - return Result.FLUSH; - } - - // Do we need a response header - if (header == null) { - return Result.NEED_HEADER; - } - // prepare the header - int pos = BufferUtils.flipToFill(header); - try { - // generate ResponseLine - generateResponseLine(info, header); - - // Handle 1xx and no content responses - int status = info.getStatus(); - if (status >= 100 && status < 200) { - noContentResponse = true; - - if (status != HttpStatus.SWITCHING_PROTOCOLS_101) { - header.put(HttpTokens.CRLF); - state = State.COMPLETING_1XX; - return Result.FLUSH; - } - } else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIED_304) { - noContentResponse = true; - } - - generateHeaders(info, header, content, last); - - // handle the content. - int len = BufferUtils.length(content); - if (len > 0) { - contentPrepared += len; - if (isChunking() && !head) { - prepareChunk(header, len); - } - } - state = last ? State.COMPLETING : State.COMMITTED; - } catch (BadMessageException e) { - throw e; - } catch (BufferOverflowException e) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Response header too large", e); - } catch (Exception e) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, e.getMessage(), e); - } finally { - BufferUtils.flipToFlush(header, pos); - } - - return Result.FLUSH; - } - - case COMMITTED: { - return committed(chunk, content, last); - } - - case COMPLETING_1XX: { - reset(); - return Result.DONE; - } - - case COMPLETING: { - return completing(chunk, content); - } - - case END: - if (BufferUtils.hasContent(content)) { - if (LOG.isDebugEnabled()) { - LOG.debug("discarding content in COMPLETING"); - } - BufferUtils.clear(content); - } - return Result.DONE; - - default: - throw new IllegalStateException(); - } - } - - private void prepareChunk(ByteBuffer chunk, int remaining) { - // if we need CRLF add this to header - if (needCRLF) { - BufferUtils.putCRLF(chunk); - } - // Add the chunk size to the header - if (remaining > 0) { - BufferUtils.putHexInt(chunk, remaining); - BufferUtils.putCRLF(chunk); - needCRLF = true; - } else { - chunk.put(LAST_CHUNK); - needCRLF = false; - } - } - - private void generateTrailers(ByteBuffer buffer, HttpFields trailer) { - // if we need CRLF add this to header - if (needCRLF) { - BufferUtils.putCRLF(buffer); - } - // Add the chunk size to the header - buffer.put(ZERO_CHUNK); - - int n = trailer.size(); - for (int f = 0; f < n; f++) { - HttpField field = trailer.getField(f); - putTo(field, buffer); - } - - BufferUtils.putCRLF(buffer); - } - - private void generateRequestLine(MetaData.Request request, ByteBuffer header) { - header.put(StringUtils.getBytes(request.getMethod())); - header.put((byte) ' '); - header.put(StringUtils.getBytes(request.getURIString())); - header.put((byte) ' '); - header.put(request.getHttpVersion().getBytes()); - header.put(HttpTokens.CRLF); - } - - private void generateResponseLine(MetaData.Response response, ByteBuffer header) { - // Look for prepared response line - int status = response.getStatus(); - PreparedResponse preprepared = status < PREPARED_RESPONSE.length ? PREPARED_RESPONSE[status] : null; - String reason = response.getReason(); - if (preprepared != null) { - if (reason == null) { - header.put(preprepared.responseLine); - } else { - header.put(preprepared.schemeCode); - header.put(getReasonBytes(reason)); - header.put(HttpTokens.CRLF); - } - } else { // generate response line - header.put(HTTP_1_1_SPACE); - header.put((byte) ('0' + status / 100)); - header.put((byte) ('0' + (status % 100) / 10)); - header.put((byte) ('0' + (status % 10))); - header.put((byte) ' '); - if (reason == null) { - header.put((byte) ('0' + status / 100)); - header.put((byte) ('0' + (status % 100) / 10)); - header.put((byte) ('0' + (status % 10))); - } else { - header.put(getReasonBytes(reason)); - } - header.put(HttpTokens.CRLF); - } - } - - private byte[] getReasonBytes(String reason) { - if (reason.length() > 1024) { - reason = reason.substring(0, 1024); - } - byte[] _bytes = StringUtils.getBytes(reason); - - for (int i = _bytes.length; i-- > 0; ) { - if (_bytes[i] == '\r' || _bytes[i] == '\n') { - _bytes[i] = '?'; - } - } - return _bytes; - } - - private void generateHeaders(MetaData info, ByteBuffer header, ByteBuffer content, boolean last) { - final MetaData.Request request = (info instanceof MetaData.Request) ? (MetaData.Request) info : null; - final MetaData.Response response = (info instanceof MetaData.Response) ? (MetaData.Response) info : null; - - if (LOG.isDebugEnabled()) { - LOG.debug("generateHeaders {} last={} content={}", info, last, BufferUtils.toDetailString(content)); - LOG.debug(info.getFields().toString()); - } - - // default field values - int send = this.send; - HttpField transfer_encoding = null; - boolean http11 = info.getHttpVersion() == HttpVersion.HTTP_1_1; - boolean close = false; - trailers = http11 ? info.getTrailerSupplier() : null; - boolean chunked_hint = trailers != null; - boolean content_type = false; - long content_length = info.getContentLength(); - boolean content_length_field = false; - - // Generate fields - HttpFields fields = info.getFields(); - if (fields != null) { - int n = fields.size(); - for (int f = 0; f < n; f++) { - HttpField field = fields.getField(f); - HttpHeader h = field.getHeader(); - if (h == null) { - putTo(field, header); - } else { - switch (h) { - case CONTENT_LENGTH: - if (content_length < 0) { - content_length = field.getLongValue(); - } else if (content_length != field.getLongValue()) { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", content_length, field.getLongValue())); - } - content_length_field = true; - break; - - case CONTENT_TYPE: { - // write the field to the header - content_type = true; - putTo(field, header); - break; - } - - case TRANSFER_ENCODING: { - if (http11) { - // Don't add yet, treat this only as a hint that there is content - // with a preference to chunk if we can - transfer_encoding = field; - chunked_hint = field.contains(HttpHeaderValue.CHUNKED.getValue()); - } - break; - } - - case CONNECTION: { - putTo(field, header); - if (field.contains(HttpHeaderValue.CLOSE.getValue())) { - close = true; - persistent = false; - } - - if (info.getHttpVersion() == HttpVersion.HTTP_1_0 && persistent == null && field.contains(HttpHeaderValue.KEEP_ALIVE.getValue())) { - persistent = true; - } - break; - } - - case SERVER: { - send = send & ~SEND_SERVER; - putTo(field, header); - break; - } - - default: - putTo(field, header); - } - } - } - } - - // Can we work out the content length? - if (last && content_length < 0 && trailers == null) { - content_length = contentPrepared + BufferUtils.length(content); - } - // Calculate how to end _content and connection, _content length and transfer encoding - // settings from http://tools.ietf.org/html/rfc7230#section-3.3.3 - - boolean assumed_content_request = request != null && Boolean.TRUE.equals(ASSUMED_CONTENT_METHODS.get(request.getMethod())); - boolean assumed_content = assumed_content_request || content_type || chunked_hint; - boolean nocontent_request = request != null && content_length <= 0 && !assumed_content; - - if (persistent == null) { - persistent = http11 || (request != null && HttpMethod.CONNECT.is(request.getMethod())); - } - // If the message is known not to have content - if (noContentResponse || nocontent_request) { - // We don't need to indicate a body length - endOfContent = EndOfContent.NO_CONTENT; - - // But it is an error if there actually is content - if (contentPrepared > 0 || content_length > 0) { - if (contentPrepared == 0 && last) { - // TODO discard content for backward compatibility with 9.3 releases - // TODO review if it is still needed in 9.4 or can we just throw. - content.clear(); - } else { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Content for no content response"); - } - } - } - // Else if we are HTTP/1.1, and the content length is unknown, and we are either persistent - // or it is a request with content (which cannot EOF), or the app has requested chunk - else if (http11 && (chunked_hint || content_length < 0 && (persistent || assumed_content_request))) { - // we use chunk - endOfContent = EndOfContent.CHUNKED_CONTENT; - - // try to use user supplied encoding as it may have other values. - if (transfer_encoding == null) - header.put(TRANSFER_ENCODING_CHUNKED); - else if (transfer_encoding.toString().endsWith(HttpHeaderValue.CHUNKED.toString())) { - putTo(transfer_encoding, header); - transfer_encoding = null; - } else if (!chunked_hint) { - putTo(new HttpField(HttpHeader.TRANSFER_ENCODING, transfer_encoding.getValue() + ",chunked"), header); - transfer_encoding = null; - } else { - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Bad Transfer-Encoding"); - } - } - // Else if we have known the content length and are a request or a persistent response, - else if (content_length >= 0 && (request != null || persistent)) { - // Use the content length - endOfContent = EndOfContent.CONTENT_LENGTH; - putContentLength(header, content_length); - } - // Else if we are a response - else if (response != null) { - // We must use EOF - even if we were trying to be persistent - endOfContent = EndOfContent.EOF_CONTENT; - persistent = false; - if (content_length >= 0 && (content_length > 0 || assumed_content || content_length_field)) { - putContentLength(header, content_length); - } - if (http11 && !close) { - header.put(CONNECTION_CLOSE); - } - } - // Else we must be a request - else { - // with no way to indicate body length - throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Unknown content length for request"); - } - - if (LOG.isDebugEnabled()) { - LOG.debug(endOfContent.toString()); - } - // Add transfer encoding if it is not chunking - if (transfer_encoding != null) { - if (chunked_hint) { - String v = transfer_encoding.getValue(); - int c = v.lastIndexOf(','); - if (c > 0 && v.lastIndexOf(HttpHeaderValue.CHUNKED.toString(), c) > c) { - putTo(new HttpField(HttpHeader.TRANSFER_ENCODING, v.substring(0, c).trim()), header); - } - } else { - putTo(transfer_encoding, header); - } - } - - // Send server? - int status = response != null ? response.getStatus() : -1; - if (status > 199) { - header.put(SEND[send]); - } - // end the header. - header.put(HttpTokens.CRLF); - } - - @Override - public String toString() { - return String.format("%s@%x{s=%s}", - getClass().getSimpleName(), - hashCode(), - state); - } - - // states - public enum State { - START, - COMMITTED, - COMPLETING, - COMPLETING_1XX, - END - } - - public enum Result { - NEED_CHUNK, // Need a small chunk buffer of CHUNK_SIZE - NEED_INFO, // Need the request/response metadata info - NEED_HEADER, // Need buffer to build HTTP headers into - NEED_CHUNK_TRAILER, // Need a large chunk buffer for last chunk and trailers - FLUSH, // The buffers previously generated should be flushed - CONTINUE, // Continue generating the message - SHUTDOWN_OUT, // Need EOF to be signaled - DONE // The current phase of generation is complete - } - - // Build cache of response lines for status - private static class PreparedResponse { - byte[] reason; - byte[] schemeCode; - byte[] responseLine; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/BodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/BodyParser.java deleted file mode 100644 index eed0a8817..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/BodyParser.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.v2.frame.*; - -import java.nio.ByteBuffer; - -/** - *

    The base parser for the frame body of HTTP/2 frames.

    - *

    Subclasses implement {@link #parse(ByteBuffer)} to parse - * the frame specific body.

    - * - * @see Parser - */ -public abstract class BodyParser { - public static final LazyLogger LOG = SystemLogger.create(BodyParser.class); - - private final HeaderParser headerParser; - private final Parser.Listener listener; - - protected BodyParser(HeaderParser headerParser, Parser.Listener listener) { - this.headerParser = headerParser; - this.listener = listener; - } - - /** - *

    Parses the body bytes in the given {@code buffer}; only the body - * bytes are consumed, therefore when this method returns, the buffer - * may contain unconsumed bytes.

    - * - * @param buffer the buffer to parse - * @return true if the whole body bytes were parsed, false if not enough - * body bytes were present in the buffer - */ - public abstract boolean parse(ByteBuffer buffer); - - protected void emptyBody(ByteBuffer buffer) { - connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_frame"); - } - - protected boolean hasFlag(int bit) { - return headerParser.hasFlag(bit); - } - - protected boolean isPadding() { - return headerParser.hasFlag(Flags.PADDING); - } - - protected boolean isEndStream() { - return headerParser.hasFlag(Flags.END_STREAM); - } - - protected int getStreamId() { - return headerParser.getStreamId(); - } - - protected int getBodyLength() { - return headerParser.getLength(); - } - - protected void notifyData(DataFrame frame) { - try { - listener.onData(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyHeaders(HeadersFrame frame) { - try { - listener.onHeaders(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyPriority(PriorityFrame frame) { - try { - listener.onPriority(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyReset(ResetFrame frame) { - try { - listener.onReset(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifySettings(SettingsFrame frame) { - try { - listener.onSettings(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyPushPromise(PushPromiseFrame frame) { - try { - listener.onPushPromise(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyPing(PingFrame frame) { - try { - listener.onPing(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyGoAway(GoAwayFrame frame) { - try { - listener.onGoAway(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void notifyWindowUpdate(WindowUpdateFrame frame) { - try { - listener.onWindowUpdate(frame); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected boolean connectionFailure(ByteBuffer buffer, int error, String reason) { - BufferUtils.clear(buffer); - notifyConnectionFailure(error, reason); - return false; - } - - private void notifyConnectionFailure(int error, String reason) { - try { - listener.onConnectionFailure(error, reason); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - protected void streamFailure(int streamId, int error, String reason) { - notifyStreamFailure(streamId, error, reason); - } - - private void notifyStreamFailure(int streamId, int error, String reason) { - try { - listener.onStreamFailure(streamId, error, reason); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ContinuationBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ContinuationBodyParser.java deleted file mode 100644 index ea0d294ee..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ContinuationBodyParser.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - - -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.HeadersFrame; - -import java.nio.ByteBuffer; - -public class ContinuationBodyParser extends BodyParser { - private final HeaderBlockParser headerBlockParser; - private final HeaderBlockFragments headerBlockFragments; - private State state = State.PREPARE; - private int length; - - public ContinuationBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments) { - super(headerParser, listener); - this.headerBlockParser = headerBlockParser; - this.headerBlockFragments = headerBlockFragments; - } - - @Override - protected void emptyBody(ByteBuffer buffer) { - if (hasFlag(Flags.END_HEADERS)) - onHeaders(); - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() == 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_continuation_frame"); - - if (getStreamId() != headerBlockFragments.getStreamId()) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_continuation_stream"); - - length = getBodyLength(); - state = State.FRAGMENT; - break; - } - case FRAGMENT: { - int remaining = buffer.remaining(); - if (remaining < length) { - headerBlockFragments.storeFragment(buffer, remaining, false); - length -= remaining; - break; - } else { - boolean last = hasFlag(Flags.END_HEADERS); - headerBlockFragments.storeFragment(buffer, length, last); - reset(); - if (last) - return onHeaders(); - return true; - } - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private boolean onHeaders() { - ByteBuffer headerBlock = headerBlockFragments.complete(); - MetaData metaData = headerBlockParser.parse(headerBlock, headerBlock.remaining()); - if (metaData == HeaderBlockParser.SESSION_FAILURE) - return false; - if (metaData == null || metaData == HeaderBlockParser.STREAM_FAILURE) - return true; - HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, headerBlockFragments.getPriorityFrame(), headerBlockFragments.isEndStream()); - frame.setEndHeaders(hasFlag(Flags.END_HEADERS)); - notifyHeaders(frame); - return true; - } - - private void reset() { - state = State.PREPARE; - length = 0; - } - - private enum State { - PREPARE, FRAGMENT - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/DataBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/DataBodyParser.java deleted file mode 100644 index dc39c0db4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/DataBodyParser.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.DataFrame; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; - -import java.nio.ByteBuffer; - -public class DataBodyParser extends BodyParser { - private State state = State.PREPARE; - private int padding; - private int paddingLength; - private int length; - - public DataBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - private void reset() { - state = State.PREPARE; - padding = 0; - paddingLength = 0; - length = 0; - } - - @Override - protected void emptyBody(ByteBuffer buffer) { - if (isPadding()) - connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame"); - else - onData(BufferUtils.EMPTY_BUFFER, false, 0); - } - - @Override - public boolean parse(ByteBuffer buffer) { - boolean loop = false; - while (buffer.hasRemaining() || loop) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() == 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_data_frame"); - - length = getBodyLength(); - state = isPadding() ? State.PADDING_LENGTH : State.DATA; - break; - } - case PADDING_LENGTH: { - padding = 1; // We have seen this byte. - paddingLength = buffer.get() & 0xFF; - --length; - length -= paddingLength; - state = State.DATA; - loop = length == 0; - if (length < 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_data_frame_padding"); - break; - } - case DATA: { - int size = Math.min(buffer.remaining(), length); - int position = buffer.position(); - int limit = buffer.limit(); - buffer.limit(position + size); - ByteBuffer slice = buffer.slice(); - buffer.limit(limit); - buffer.position(position + size); - - length -= size; - if (length == 0) { - state = State.PADDING; - loop = paddingLength == 0; - // Padding bytes include the bytes that define the - // padding length plus the actual padding bytes. - onData(slice, false, padding + paddingLength); - } else { - // We got partial data, simulate a smaller frame, and stay in DATA state. - // No padding for these synthetic frames (even if we have read - // the padding length already), it will be accounted at the end. - onData(slice, true, 0); - } - break; - } - case PADDING: { - int size = Math.min(buffer.remaining(), paddingLength); - buffer.position(buffer.position() + size); - paddingLength -= size; - if (paddingLength == 0) { - reset(); - return true; - } - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private void onData(ByteBuffer buffer, boolean fragment, int padding) { - DataFrame frame = new DataFrame(getStreamId(), buffer, !fragment && isEndStream(), padding); - notifyData(frame); - } - - private enum State { - PREPARE, PADDING_LENGTH, DATA, PADDING - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/GoAwayBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/GoAwayBodyParser.java deleted file mode 100644 index 875537c75..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/GoAwayBodyParser.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.GoAwayFrame; - -import java.nio.ByteBuffer; - -public class GoAwayBodyParser extends BodyParser { - private State state = State.PREPARE; - private int cursor; - private int length; - private int lastStreamId; - private int error; - private byte[] payload; - - public GoAwayBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - length = 0; - lastStreamId = 0; - error = 0; - payload = null; - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - state = State.LAST_STREAM_ID; - length = getBodyLength(); - break; - } - case LAST_STREAM_ID: { - if (buffer.remaining() >= 4) { - lastStreamId = buffer.getInt(); - lastStreamId &= 0x7F_FF_FF_FF; - state = State.ERROR; - length -= 4; - if (length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_go_away_frame"); - } else { - state = State.LAST_STREAM_ID_BYTES; - cursor = 4; - } - break; - } - case LAST_STREAM_ID_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - lastStreamId += currByte << (8 * cursor); - --length; - if (cursor > 0 && length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_go_away_frame"); - if (cursor == 0) { - lastStreamId &= 0x7F_FF_FF_FF; - state = State.ERROR; - if (length == 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_go_away_frame"); - } - break; - } - case ERROR: { - if (buffer.remaining() >= 4) { - error = buffer.getInt(); - state = State.PAYLOAD; - length -= 4; - if (length < 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_go_away_frame"); - if (length == 0) - return onGoAway(lastStreamId, error, null); - } else { - state = State.ERROR_BYTES; - cursor = 4; - } - break; - } - case ERROR_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - error += currByte << (8 * cursor); - --length; - if (cursor > 0 && length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_go_away_frame"); - if (cursor == 0) { - state = State.PAYLOAD; - if (length == 0) - return onGoAway(lastStreamId, error, null); - } - break; - } - case PAYLOAD: { - payload = new byte[length]; - if (buffer.remaining() >= length) { - buffer.get(payload); - return onGoAway(lastStreamId, error, payload); - } else { - state = State.PAYLOAD_BYTES; - cursor = length; - } - break; - } - case PAYLOAD_BYTES: { - payload[payload.length - cursor] = buffer.get(); - --cursor; - if (cursor == 0) - return onGoAway(lastStreamId, error, payload); - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private boolean onGoAway(int lastStreamId, int error, byte[] payload) { - GoAwayFrame frame = new GoAwayFrame(lastStreamId, error, payload); - reset(); - notifyGoAway(frame); - return true; - } - - private enum State { - PREPARE, LAST_STREAM_ID, LAST_STREAM_ID_BYTES, ERROR, ERROR_BYTES, PAYLOAD, PAYLOAD_BYTES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderBlockFragments.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderBlockFragments.java deleted file mode 100644 index 673d3c419..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderBlockFragments.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.PriorityFrame; - -import java.nio.ByteBuffer; - -public class HeaderBlockFragments { - private PriorityFrame priorityFrame; - private boolean endStream; - private int streamId; - private ByteBuffer storage; - - public void storeFragment(ByteBuffer fragment, int length, boolean last) { - if (storage == null) { - int space = last ? length : length * 2; - storage = ByteBuffer.allocate(space); - } - - // Grow the storage if necessary. - if (storage.remaining() < length) { - int space = last ? length : length * 2; - int capacity = storage.position() + space; - ByteBuffer newStorage = ByteBuffer.allocate(capacity); - storage.flip(); - newStorage.put(storage); - storage = newStorage; - } - - // Copy the fragment into the storage. - int limit = fragment.limit(); - fragment.limit(fragment.position() + length); - storage.put(fragment); - fragment.limit(limit); - } - - public PriorityFrame getPriorityFrame() { - return priorityFrame; - } - - public void setPriorityFrame(PriorityFrame priorityFrame) { - this.priorityFrame = priorityFrame; - } - - public boolean isEndStream() { - return endStream; - } - - public void setEndStream(boolean endStream) { - this.endStream = endStream; - } - - public ByteBuffer complete() { - ByteBuffer result = storage; - storage = null; - result.flip(); - return result; - } - - public int getStreamId() { - return streamId; - } - - public void setStreamId(int streamId) { - this.streamId = streamId; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderBlockParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderBlockParser.java deleted file mode 100644 index d042f8266..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderBlockParser.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.model.HttpVersion; -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.hpack.HpackDecoder; -import com.fireflysource.net.http.common.v2.hpack.HpackException; - -import java.nio.ByteBuffer; - -public class HeaderBlockParser { - public static final MetaData STREAM_FAILURE = new MetaData(HttpVersion.HTTP_2, null); - public static final MetaData SESSION_FAILURE = new MetaData(HttpVersion.HTTP_2, null); - public static final LazyLogger LOG = SystemLogger.create(HeaderBlockParser.class); - - private final HeaderParser headerParser; - private final HpackDecoder hpackDecoder; - private final BodyParser notifier; - private ByteBuffer blockBuffer; - - public HeaderBlockParser(HeaderParser headerParser, HpackDecoder hpackDecoder, BodyParser notifier) { - this.headerParser = headerParser; - this.hpackDecoder = hpackDecoder; - this.notifier = notifier; - } - - /** - * Parses @{code blockLength} HPACK bytes from the given {@code buffer}. - * - * @param buffer the buffer to parse - * @param blockLength the length of the HPACK block - * @return null, if the buffer contains less than {@code blockLength} bytes; - * {@link #STREAM_FAILURE} if parsing the HPACK block produced a stream failure; - * {@link #SESSION_FAILURE} if parsing the HPACK block produced a session failure; - * a valid MetaData object if the parsing was successful. - */ - public MetaData parse(ByteBuffer buffer, int blockLength) { - // We must wait for the all the bytes of the header block to arrive. - // If they are not all available, accumulate them. - // When all are available, decode them. - - int accumulated = blockBuffer == null ? 0 : blockBuffer.position(); - int remaining = blockLength - accumulated; - - if (buffer.remaining() < remaining) { - if (blockBuffer == null) { - blockBuffer = BufferUtils.allocate(blockLength); - BufferUtils.clearToFill(blockBuffer); - } - blockBuffer.put(buffer); - return null; - } else { - int limit = buffer.limit(); - buffer.limit(buffer.position() + remaining); - ByteBuffer toDecode; - if (blockBuffer != null) { - blockBuffer.put(buffer); - BufferUtils.flipToFlush(blockBuffer, 0); - toDecode = blockBuffer; - } else { - toDecode = buffer; - } - - try { - return hpackDecoder.decode(toDecode); - } catch (HpackException.StreamException x) { - if (LOG.isDebugEnabled()) - LOG.debug("hpack stream exception", x); - notifier.streamFailure(headerParser.getStreamId(), ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block"); - return STREAM_FAILURE; - } catch (HpackException.CompressionException x) { - if (LOG.isDebugEnabled()) - LOG.debug("hpack compression exception", x); - notifier.connectionFailure(buffer, ErrorCode.COMPRESSION_ERROR.code, "invalid_hpack_block"); - return SESSION_FAILURE; - } catch (HpackException.SessionException x) { - if (LOG.isDebugEnabled()) - LOG.debug("hpack session exception", x); - notifier.connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_hpack_block"); - return SESSION_FAILURE; - } finally { - buffer.limit(limit); - - if (blockBuffer != null) { - blockBuffer = null; - } - } - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderParser.java deleted file mode 100644 index 09358e776..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeaderParser.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; - -import java.nio.ByteBuffer; - -/** - *

    The parser for the frame header of HTTP/2 frames.

    - * - * @see Parser - */ -public class HeaderParser { - private State state = State.LENGTH; - private int cursor; - - private int length; - private int type; - private int flags; - private int streamId; - - protected void reset() { - state = State.LENGTH; - cursor = 0; - - length = 0; - type = 0; - flags = 0; - streamId = 0; - } - - /** - *

    Parses the header bytes in the given {@code buffer}; only the header - * bytes are consumed, therefore when this method returns, the buffer may - * contain unconsumed bytes.

    - * - * @param buffer the buffer to parse - * @return true if the whole header bytes were parsed, false if not enough - * header bytes were present in the buffer - */ - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case LENGTH: { - int octet = buffer.get() & 0xFF; - length = (length << 8) + octet; - if (++cursor == 3) { - length &= Frame.MAX_MAX_LENGTH; - state = State.TYPE; - } - break; - } - case TYPE: { - type = buffer.get() & 0xFF; - state = State.FLAGS; - break; - } - case FLAGS: { - flags = buffer.get() & 0xFF; - state = State.STREAM_ID; - break; - } - case STREAM_ID: { - if (buffer.remaining() >= 4) { - streamId = buffer.getInt(); - // Most significant bit MUST be ignored as per specification. - streamId &= 0x7F_FF_FF_FF; - return true; - } else { - state = State.STREAM_ID_BYTES; - cursor = 4; - } - break; - } - case STREAM_ID_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - streamId += currByte << (8 * cursor); - if (cursor == 0) { - // Most significant bit MUST be ignored as per specification. - streamId &= 0x7F_FF_FF_FF; - return true; - } - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - public int getLength() { - return length; - } - - public int getFrameType() { - return type; - } - - public boolean hasFlag(int bit) { - return (flags & bit) == bit; - } - - public int getStreamId() { - return streamId; - } - - @Override - public String toString() { - return String.format("[%s|%d|%d|%d]", FrameType.from(getFrameType()), getLength(), flags, getStreamId()); - } - - private enum State { - LENGTH, TYPE, FLAGS, STREAM_ID, STREAM_ID_BYTES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeadersBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeadersBodyParser.java deleted file mode 100644 index 46475e12e..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/HeadersBodyParser.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.frame.*; - -import java.nio.ByteBuffer; - -public class HeadersBodyParser extends BodyParser { - private final HeaderBlockParser headerBlockParser; - private final HeaderBlockFragments headerBlockFragments; - private State state = State.PREPARE; - private int cursor; - private int length; - private int paddingLength; - private boolean exclusive; - private int parentStreamId; - private int weight; - - public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser, HeaderBlockFragments headerBlockFragments) { - super(headerParser, listener); - this.headerBlockParser = headerBlockParser; - this.headerBlockFragments = headerBlockFragments; - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - length = 0; - paddingLength = 0; - exclusive = false; - parentStreamId = 0; - weight = 0; - } - - @Override - protected void emptyBody(ByteBuffer buffer) { - if (hasFlag(Flags.END_HEADERS)) { - MetaData metaData = headerBlockParser.parse(BufferUtils.EMPTY_BUFFER, 0); - onHeaders(0, 0, false, metaData); - } else { - headerBlockFragments.setStreamId(getStreamId()); - headerBlockFragments.setEndStream(isEndStream()); - if (hasFlag(Flags.PRIORITY)) - connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_priority_frame"); - } - } - - @Override - public boolean parse(ByteBuffer buffer) { - boolean loop = false; - while (buffer.hasRemaining() || loop) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() == 0) { - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_headers_frame"); - } - - length = getBodyLength(); - - if (isPadding()) - state = State.PADDING_LENGTH; - else if (hasFlag(Flags.PRIORITY)) - state = State.EXCLUSIVE; - else - state = State.HEADERS; - break; - } - case PADDING_LENGTH: { - paddingLength = buffer.get() & 0xFF; - --length; - length -= paddingLength; - state = hasFlag(Flags.PRIORITY) ? State.EXCLUSIVE : State.HEADERS; - loop = length == 0; - if (length < 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_headers_frame_padding"); - break; - } - case EXCLUSIVE: { - // We must only peek the first byte and not advance the buffer - // because the 31 least significant bits represent the stream id. - int currByte = buffer.get(buffer.position()); - exclusive = (currByte & 0x80) == 0x80; - state = State.PARENT_STREAM_ID; - break; - } - case PARENT_STREAM_ID: { - if (buffer.remaining() >= 4) { - parentStreamId = buffer.getInt(); - parentStreamId &= 0x7F_FF_FF_FF; - length -= 4; - state = State.WEIGHT; - if (length < 1) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_headers_frame"); - } else { - state = State.PARENT_STREAM_ID_BYTES; - cursor = 4; - } - break; - } - case PARENT_STREAM_ID_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - parentStreamId += currByte << (8 * cursor); - --length; - if (cursor > 0 && length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_headers_frame"); - if (cursor == 0) { - parentStreamId &= 0x7F_FF_FF_FF; - state = State.WEIGHT; - if (length < 1) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_headers_frame"); - } - break; - } - case WEIGHT: { - // SPEC: stream cannot depend on itself. - if (getStreamId() == parentStreamId) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame"); - weight = (buffer.get() & 0xFF) + 1; - --length; - state = State.HEADERS; - loop = length == 0; - break; - } - case HEADERS: { - if (hasFlag(Flags.END_HEADERS)) { - MetaData metaData = headerBlockParser.parse(buffer, length); - if (metaData == HeaderBlockParser.SESSION_FAILURE) - return false; - if (metaData != null) { - if (LOG.isDebugEnabled()) - LOG.debug("Parsed {} frame hpack from {}", FrameType.HEADERS, buffer); - state = State.PADDING; - loop = paddingLength == 0; - if (metaData != HeaderBlockParser.STREAM_FAILURE) - onHeaders(parentStreamId, weight, exclusive, metaData); - } - } else { - int remaining = buffer.remaining(); - if (remaining < length) { - headerBlockFragments.storeFragment(buffer, remaining, false); - length -= remaining; - } else { - headerBlockFragments.setStreamId(getStreamId()); - headerBlockFragments.setEndStream(isEndStream()); - if (hasFlag(Flags.PRIORITY)) - headerBlockFragments.setPriorityFrame(new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive)); - headerBlockFragments.storeFragment(buffer, length, false); - state = State.PADDING; - loop = paddingLength == 0; - } - } - break; - } - case PADDING: { - int size = Math.min(buffer.remaining(), paddingLength); - buffer.position(buffer.position() + size); - paddingLength -= size; - if (paddingLength == 0) { - reset(); - return true; - } - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private void onHeaders(int parentStreamId, int weight, boolean exclusive, MetaData metaData) { - PriorityFrame priorityFrame = null; - if (hasFlag(Flags.PRIORITY)) - priorityFrame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive); - HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, priorityFrame, isEndStream()); - frame.setEndHeaders(hasFlag(Flags.END_HEADERS)); - notifyHeaders(frame); - } - - private enum State { - PREPARE, PADDING_LENGTH, EXCLUSIVE, PARENT_STREAM_ID, PARENT_STREAM_ID_BYTES, WEIGHT, HEADERS, PADDING - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/Parser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/Parser.java deleted file mode 100644 index aec03da5c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/Parser.java +++ /dev/null @@ -1,325 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.v2.frame.*; -import com.fireflysource.net.http.common.v2.hpack.HpackDecoder; - -import java.nio.ByteBuffer; -import java.util.function.UnaryOperator; - -/** - *

    The HTTP/2 protocol parser.

    - *

    This parser makes use of the {@link HeaderParser} and of - * {@link BodyParser}s to parse HTTP/2 frames.

    - */ -public class Parser { - public static final LazyLogger LOG = SystemLogger.create(Parser.class); - - private final Listener listener; - private final HeaderParser headerParser; - private final HpackDecoder hpackDecoder; - private final BodyParser[] bodyParsers; - private UnknownBodyParser unknownBodyParser; - private int maxFrameLength; - private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS; - private boolean continuation; - private State state = State.HEADER; - - public Parser(Listener listener, int maxDynamicTableSize, int maxHeaderSize) { - this.listener = listener; - this.headerParser = new HeaderParser(); - this.hpackDecoder = new HpackDecoder(maxDynamicTableSize, maxHeaderSize); - this.maxFrameLength = Frame.DEFAULT_MAX_LENGTH; - this.bodyParsers = new BodyParser[FrameType.values().length]; - } - - public void init(UnaryOperator wrapper) { - Listener listener = wrapper.apply(this.listener); - unknownBodyParser = new UnknownBodyParser(headerParser, listener); - HeaderBlockParser headerBlockParser = new HeaderBlockParser(headerParser, hpackDecoder, unknownBodyParser); - HeaderBlockFragments headerBlockFragments = new HeaderBlockFragments(); - bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener); - bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments); - bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener); - bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener); - bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys()); - bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser); - bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener); - bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener); - bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener); - bodyParsers[FrameType.CONTINUATION.getType()] = new ContinuationBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments); - } - - private void reset() { - headerParser.reset(); - state = State.HEADER; - } - - /** - *

    Parses the given {@code buffer} bytes and emit events to a {@link Listener}.

    - *

    When this method returns, the buffer may not be fully consumed, so invocations - * to this method should be wrapped in a loop:

    - *
    -     * while (buffer.hasRemaining())
    -     *     parser.parse(buffer);
    -     * 
    - * - * @param buffer the buffer to parse - */ - public void parse(ByteBuffer buffer) { - try { - while (true) { - switch (state) { - case HEADER: { - if (!parseHeader(buffer)) - return; - break; - } - case BODY: { - if (!parseBody(buffer)) - return; - break; - } - default: { - throw new IllegalStateException(); - } - } - } - } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug("http2 frame parsing exception", x); - connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR, "parser_error"); - } - } - - protected boolean parseHeader(ByteBuffer buffer) { - if (!headerParser.parse(buffer)) - return false; - - if (LOG.isDebugEnabled()) - LOG.debug("Parsed {} frame header from {}", headerParser, buffer); - - if (headerParser.getLength() > getMaxFrameLength()) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR, "invalid_frame_length"); - - FrameType frameType = FrameType.from(getFrameType()); - if (continuation) { - // SPEC: CONTINUATION frames must be consecutive. - if (frameType != FrameType.CONTINUATION) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR, "expected_continuation_frame"); - if (headerParser.hasFlag(Flags.END_HEADERS)) - continuation = false; - } else { - if (frameType == FrameType.HEADERS) - continuation = !headerParser.hasFlag(Flags.END_HEADERS); - else if (frameType == FrameType.CONTINUATION) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR, "unexpected_continuation_frame"); - } - state = State.BODY; - return true; - } - - protected boolean parseBody(ByteBuffer buffer) { - int type = getFrameType(); - if (type < 0 || type >= bodyParsers.length) { - // Unknown frame types must be ignored. - if (LOG.isDebugEnabled()) - LOG.debug("Ignoring unknown frame type {}", Integer.toHexString(type)); - if (!unknownBodyParser.parse(buffer)) - return false; - reset(); - return true; - } - - BodyParser bodyParser = bodyParsers[type]; - if (headerParser.getLength() == 0) { - bodyParser.emptyBody(buffer); - } else { - if (!bodyParser.parse(buffer)) - return false; - } - if (LOG.isDebugEnabled()) - LOG.debug("Parsed {} frame body from {}", FrameType.from(type), buffer); - reset(); - return true; - } - - private boolean connectionFailure(ByteBuffer buffer, ErrorCode error, String reason) { - return unknownBodyParser.connectionFailure(buffer, error.code, reason); - } - - protected int getFrameType() { - return headerParser.getFrameType(); - } - - protected boolean hasFlag(int bit) { - return headerParser.hasFlag(bit); - } - - public int getMaxFrameLength() { - return maxFrameLength; - } - - public void setMaxFrameLength(int maxFrameLength) { - this.maxFrameLength = maxFrameLength; - } - - public int getMaxSettingsKeys() { - return maxSettingsKeys; - } - - public void setMaxSettingsKeys(int maxSettingsKeys) { - this.maxSettingsKeys = maxSettingsKeys; - } - - protected void notifyConnectionFailure(int error, String reason) { - try { - listener.onConnectionFailure(error, reason); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - public interface Listener { - void onData(DataFrame frame); - - void onHeaders(HeadersFrame frame); - - void onPriority(PriorityFrame frame); - - void onReset(ResetFrame frame); - - void onSettings(SettingsFrame frame); - - void onPushPromise(PushPromiseFrame frame); - - void onPing(PingFrame frame); - - void onGoAway(GoAwayFrame frame); - - void onWindowUpdate(WindowUpdateFrame frame); - - void onStreamFailure(int streamId, int error, String reason); - - void onConnectionFailure(int error, String reason); - - class Adapter implements Listener { - @Override - public void onData(DataFrame frame) { - } - - @Override - public void onHeaders(HeadersFrame frame) { - } - - @Override - public void onPriority(PriorityFrame frame) { - } - - @Override - public void onReset(ResetFrame frame) { - } - - @Override - public void onSettings(SettingsFrame frame) { - } - - @Override - public void onPushPromise(PushPromiseFrame frame) { - } - - @Override - public void onPing(PingFrame frame) { - } - - @Override - public void onGoAway(GoAwayFrame frame) { - } - - @Override - public void onWindowUpdate(WindowUpdateFrame frame) { - } - - @Override - public void onStreamFailure(int streamId, int error, String reason) { - } - - @Override - public void onConnectionFailure(int error, String reason) { - LOG.warn("Connection failure: {}/{}", error, reason); - } - } - - class Wrapper implements Listener { - private final Parser.Listener listener; - - public Wrapper(Parser.Listener listener) { - this.listener = listener; - } - - public Listener getParserListener() { - return listener; - } - - @Override - public void onData(DataFrame frame) { - listener.onData(frame); - } - - @Override - public void onHeaders(HeadersFrame frame) { - listener.onHeaders(frame); - } - - @Override - public void onPriority(PriorityFrame frame) { - listener.onPriority(frame); - } - - @Override - public void onReset(ResetFrame frame) { - listener.onReset(frame); - } - - @Override - public void onSettings(SettingsFrame frame) { - listener.onSettings(frame); - } - - @Override - public void onPushPromise(PushPromiseFrame frame) { - listener.onPushPromise(frame); - } - - @Override - public void onPing(PingFrame frame) { - listener.onPing(frame); - } - - @Override - public void onGoAway(GoAwayFrame frame) { - listener.onGoAway(frame); - } - - @Override - public void onWindowUpdate(WindowUpdateFrame frame) { - listener.onWindowUpdate(frame); - } - - @Override - public void onStreamFailure(int streamId, int error, String reason) { - listener.onStreamFailure(streamId, error, reason); - } - - @Override - public void onConnectionFailure(int error, String reason) { - listener.onConnectionFailure(error, reason); - } - } - } - - private enum State { - HEADER, BODY - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PingBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PingBodyParser.java deleted file mode 100644 index 17bf50226..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PingBodyParser.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.PingFrame; - -import java.nio.ByteBuffer; - -public class PingBodyParser extends BodyParser { - private State state = State.PREPARE; - private int cursor; - private byte[] payload; - - public PingBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - payload = null; - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() != 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_ping_frame"); - // SPEC: wrong body length is treated as connection error. - if (getBodyLength() != 8) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_ping_frame"); - state = State.PAYLOAD; - break; - } - case PAYLOAD: { - payload = new byte[8]; - if (buffer.remaining() >= 8) { - buffer.get(payload); - return onPing(payload); - } else { - state = State.PAYLOAD_BYTES; - cursor = 8; - } - break; - } - case PAYLOAD_BYTES: { - payload[8 - cursor] = buffer.get(); - --cursor; - if (cursor == 0) - return onPing(payload); - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private boolean onPing(byte[] payload) { - PingFrame frame = new PingFrame(payload, hasFlag(Flags.ACK)); - reset(); - notifyPing(frame); - return true; - } - - private enum State { - PREPARE, PAYLOAD, PAYLOAD_BYTES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PrefaceParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PrefaceParser.java deleted file mode 100644 index 3bf2c26aa..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PrefaceParser.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.PrefaceFrame; - -import java.nio.ByteBuffer; - -public class PrefaceParser { - public static final LazyLogger LOG = SystemLogger.create(PrefaceParser.class); - - private final Parser.Listener listener; - private int cursor; - - public PrefaceParser(Parser.Listener listener) { - this.listener = listener; - } - - /** - *

    Advances this parser after the {@link PrefaceFrame#PREFACE_PREAMBLE_BYTES}.

    - *

    This allows the HTTP/1.1 parser to parse the preamble of the preface, - * which is a legal HTTP/1.1 request, and this parser will parse the remaining - * bytes, that are not parseable by a HTTP/1.1 parser.

    - */ - protected void directUpgrade() { - if (cursor != 0) - throw new IllegalStateException(); - cursor = PrefaceFrame.PREFACE_PREAMBLE_BYTES.length; - } - - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - int currByte = buffer.get(); - if (currByte != PrefaceFrame.PREFACE_BYTES[cursor]) { - BufferUtils.clear(buffer); - notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_preface"); - return false; - } - ++cursor; - if (cursor == PrefaceFrame.PREFACE_BYTES.length) { - cursor = 0; - if (LOG.isDebugEnabled()) - LOG.debug("Parsed preface bytes from {}", buffer); - return true; - } - } - return false; - } - - protected void notifyConnectionFailure(int error, String reason) { - try { - listener.onConnectionFailure(error, reason); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PriorityBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PriorityBodyParser.java deleted file mode 100644 index aba814567..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PriorityBodyParser.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.PriorityFrame; - -import java.nio.ByteBuffer; - -public class PriorityBodyParser extends BodyParser { - private State state = State.PREPARE; - private int cursor; - private boolean exclusive; - private int parentStreamId; - - public PriorityBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - exclusive = false; - parentStreamId = 0; - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() == 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame"); - int length = getBodyLength(); - if (length != 5) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_priority_frame"); - state = State.EXCLUSIVE; - break; - } - case EXCLUSIVE: { - // We must only peek the first byte and not advance the buffer - // because the 31 least significant bits represent the stream id. - int currByte = buffer.get(buffer.position()); - exclusive = (currByte & 0x80) == 0x80; - state = State.PARENT_STREAM_ID; - break; - } - case PARENT_STREAM_ID: { - if (buffer.remaining() >= 4) { - parentStreamId = buffer.getInt(); - parentStreamId &= 0x7F_FF_FF_FF; - state = State.WEIGHT; - } else { - state = State.PARENT_STREAM_ID_BYTES; - cursor = 4; - } - break; - } - case PARENT_STREAM_ID_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - parentStreamId += currByte << (8 * cursor); - if (cursor == 0) { - parentStreamId &= 0x7F_FF_FF_FF; - state = State.WEIGHT; - } - break; - } - case WEIGHT: { - // SPEC: stream cannot depend on itself. - if (getStreamId() == parentStreamId) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_priority_frame"); - int weight = (buffer.get() & 0xFF) + 1; - return onPriority(parentStreamId, weight, exclusive); - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private boolean onPriority(int parentStreamId, int weight, boolean exclusive) { - PriorityFrame frame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive); - reset(); - notifyPriority(frame); - return true; - } - - private enum State { - PREPARE, EXCLUSIVE, PARENT_STREAM_ID, PARENT_STREAM_ID_BYTES, WEIGHT - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PushPromiseBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PushPromiseBodyParser.java deleted file mode 100644 index 682af0a8b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/PushPromiseBodyParser.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.PushPromiseFrame; - -import java.nio.ByteBuffer; - -public class PushPromiseBodyParser extends BodyParser { - private final HeaderBlockParser headerBlockParser; - private State state = State.PREPARE; - private int cursor; - private int length; - private int paddingLength; - private int streamId; - - public PushPromiseBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser) { - super(headerParser, listener); - this.headerBlockParser = headerBlockParser; - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - length = 0; - paddingLength = 0; - streamId = 0; - } - - @Override - public boolean parse(ByteBuffer buffer) { - boolean loop = false; - while (buffer.hasRemaining() || loop) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() == 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_push_promise_frame"); - - // For now we don't support PUSH_PROMISE frames that don't have END_HEADERS. - if (!hasFlag(Flags.END_HEADERS)) - return connectionFailure(buffer, ErrorCode.INTERNAL_ERROR.code, "unsupported_push_promise_frame"); - - length = getBodyLength(); - - if (isPadding()) { - state = State.PADDING_LENGTH; - } else { - state = State.STREAM_ID; - } - break; - } - case PADDING_LENGTH: { - paddingLength = buffer.get() & 0xFF; - --length; - length -= paddingLength; - state = State.STREAM_ID; - if (length < 4) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_push_promise_frame"); - break; - } - case STREAM_ID: { - if (buffer.remaining() >= 4) { - streamId = buffer.getInt(); - streamId &= 0x7F_FF_FF_FF; - length -= 4; - state = State.HEADERS; - loop = length == 0; - } else { - state = State.STREAM_ID_BYTES; - cursor = 4; - } - break; - } - case STREAM_ID_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - streamId += currByte << (8 * cursor); - --length; - if (cursor > 0 && length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_push_promise_frame"); - if (cursor == 0) { - streamId &= 0x7F_FF_FF_FF; - state = State.HEADERS; - loop = length == 0; - } - break; - } - case HEADERS: { - MetaData metaData = headerBlockParser.parse(buffer, length); - if (metaData == HeaderBlockParser.SESSION_FAILURE) - return false; - if (metaData != null) { - state = State.PADDING; - loop = paddingLength == 0; - if (metaData != HeaderBlockParser.STREAM_FAILURE) - onPushPromise(streamId, metaData); - } - break; - } - case PADDING: { - int size = Math.min(buffer.remaining(), paddingLength); - buffer.position(buffer.position() + size); - paddingLength -= size; - if (paddingLength == 0) { - reset(); - return true; - } - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private void onPushPromise(int streamId, MetaData metaData) { - PushPromiseFrame frame = new PushPromiseFrame(getStreamId(), streamId, metaData); - frame.setEndHeaders(hasFlag(Flags.END_HEADERS)); - notifyPushPromise(frame); - } - - private enum State { - PREPARE, PADDING_LENGTH, STREAM_ID, STREAM_ID_BYTES, HEADERS, PADDING - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ResetBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ResetBodyParser.java deleted file mode 100644 index 5fab9ba79..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ResetBodyParser.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.ResetFrame; - -import java.nio.ByteBuffer; - -public class ResetBodyParser extends BodyParser { - private State state = State.PREPARE; - private int cursor; - private int error; - - public ResetBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - error = 0; - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() == 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_rst_stream_frame"); - int length = getBodyLength(); - if (length != 4) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_rst_stream_frame"); - state = State.ERROR; - break; - } - case ERROR: { - if (buffer.remaining() >= 4) { - return onReset(buffer.getInt()); - } else { - state = State.ERROR_BYTES; - cursor = 4; - } - break; - } - case ERROR_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - error += currByte << (8 * cursor); - if (cursor == 0) - return onReset(error); - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private boolean onReset(int error) { - ResetFrame frame = new ResetFrame(getStreamId(), error); - reset(); - notifyReset(frame); - return true; - } - - private enum State { - PREPARE, ERROR, ERROR_BYTES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ServerParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ServerParser.java deleted file mode 100644 index eb815a57b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/ServerParser.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.FrameType; - -import java.nio.ByteBuffer; - -public class ServerParser extends Parser { - private static final LazyLogger LOG = SystemLogger.create(ServerParser.class); - - private final Listener listener; - private final PrefaceParser prefaceParser; - private State state = State.PREFACE; - private boolean notifyPreface = true; - - public ServerParser(Listener listener, int maxDynamicTableSize, int maxHeaderSize) { - super(listener, maxDynamicTableSize, maxHeaderSize); - this.listener = listener; - this.prefaceParser = new PrefaceParser(listener); - } - - /** - *

    A direct upgrade is an unofficial upgrade from HTTP/1.1 to HTTP/2.0.

    - *

    A direct upgrade has been initiated when the HTTP connection - * sees a request with these bytes:

    - *
    -     * PRI * HTTP/2.0\r\n
    -     * \r\n
    -     * 
    - *

    This request is part of the HTTP/2.0 preface, indicating that a - * HTTP/2.0 client is attempting a h2c direct connection.

    - *

    This is not a standard HTTP/1.1 Upgrade path.

    - */ - public void directUpgrade() { - if (state != State.PREFACE) - throw new IllegalStateException(); - prefaceParser.directUpgrade(); - } - - /** - *

    The standard HTTP/1.1 upgrade path.

    - */ - public void standardUpgrade() { - if (state != State.PREFACE) - throw new IllegalStateException(); - notifyPreface = false; - } - - @Override - public void parse(ByteBuffer buffer) { - try { - if (LOG.isDebugEnabled()) - LOG.debug("Parsing {}", buffer); - - while (true) { - switch (state) { - case PREFACE: { - if (!prefaceParser.parse(buffer)) - return; - if (notifyPreface) - onPreface(); - state = State.SETTINGS; - break; - } - case SETTINGS: { - if (!parseHeader(buffer)) - return; - if (getFrameType() != FrameType.SETTINGS.getType() || hasFlag(Flags.ACK)) { - BufferUtils.clear(buffer); - notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_preface"); - return; - } - if (!parseBody(buffer)) - return; - state = State.FRAMES; - break; - } - case FRAMES: { - // Stay forever in the FRAMES state. - super.parse(buffer); - return; - } - default: { - throw new IllegalStateException(); - } - } - } - } catch (Throwable x) { - LOG.debug("http2 server parser exception", x); - BufferUtils.clear(buffer); - notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "parser_error"); - } - } - - protected void onPreface() { - notifyPreface(); - } - - private void notifyPreface() { - try { - listener.onPreface(); - } catch (Throwable x) { - LOG.info("Failure while notifying listener " + listener, x); - } - } - - public interface Listener extends Parser.Listener { - void onPreface(); - - class Adapter extends Parser.Listener.Adapter implements Listener { - @Override - public void onPreface() { - } - } - - class Wrapper extends Parser.Listener.Wrapper implements Listener { - public Wrapper(ServerParser.Listener listener) { - super(listener); - } - - @Override - public ServerParser.Listener getParserListener() { - return (Listener) super.getParserListener(); - } - - @Override - public void onPreface() { - getParserListener().onPreface(); - } - } - } - - private enum State { - PREFACE, SETTINGS, FRAMES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/SettingsBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/SettingsBodyParser.java deleted file mode 100644 index c027cb4b9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/SettingsBodyParser.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.SettingsFrame; - -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -public class SettingsBodyParser extends BodyParser { - private static final LazyLogger LOG = SystemLogger.create(SettingsBodyParser.class); - - private final int maxKeys; - private State state = State.PREPARE; - private int cursor; - private int length; - private int settingId; - private int settingValue; - private int keys; - private Map settings; - - public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener) { - this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS); - } - - public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys) { - super(headerParser, listener); - this.maxKeys = maxKeys; - } - - protected void reset() { - state = State.PREPARE; - cursor = 0; - length = 0; - settingId = 0; - settingValue = 0; - settings = null; - } - - public int getMaxKeys() { - return maxKeys; - } - - @Override - protected void emptyBody(ByteBuffer buffer) { - onSettings(buffer, new HashMap<>()); - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - // SPEC: wrong streamId is treated as connection error. - if (getStreamId() != 0) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_frame"); - length = getBodyLength(); - settings = new HashMap<>(); - state = State.SETTING_ID; - break; - } - case SETTING_ID: { - if (buffer.remaining() >= 2) { - settingId = buffer.getShort() & 0xFF_FF; - state = State.SETTING_VALUE; - length -= 2; - if (length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_settings_frame"); - } else { - cursor = 2; - settingId = 0; - state = State.SETTING_ID_BYTES; - } - break; - } - case SETTING_ID_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - settingId += currByte << (8 * cursor); - --length; - if (length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_settings_frame"); - if (cursor == 0) { - state = State.SETTING_VALUE; - } - break; - } - case SETTING_VALUE: { - if (buffer.remaining() >= 4) { - settingValue = buffer.getInt(); - if (LOG.isDebugEnabled()) - LOG.debug(String.format("setting %d=%d", settingId, settingValue)); - if (!onSetting(buffer, settings, settingId, settingValue)) - return false; - state = State.SETTING_ID; - length -= 4; - if (length == 0) - return onSettings(buffer, settings); - } else { - cursor = 4; - settingValue = 0; - state = State.SETTING_VALUE_BYTES; - } - break; - } - case SETTING_VALUE_BYTES: { - int currByte = buffer.get() & 0xFF; - --cursor; - settingValue += currByte << (8 * cursor); - --length; - if (cursor > 0 && length <= 0) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_settings_frame"); - if (cursor == 0) { - if (LOG.isDebugEnabled()) - LOG.debug(String.format("setting %d=%d", settingId, settingValue)); - if (!onSetting(buffer, settings, settingId, settingValue)) - return false; - state = State.SETTING_ID; - if (length == 0) - return onSettings(buffer, settings); - } - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - protected boolean onSetting(ByteBuffer buffer, Map settings, int key, int value) { - ++keys; - if (keys > getMaxKeys()) - return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame"); - settings.put(key, value); - return true; - } - - protected boolean onSettings(ByteBuffer buffer, Map settings) { - Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH); - if (enablePush != null && enablePush != 0 && enablePush != 1) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_enable_push"); - - Integer initialWindowSize = settings.get(SettingsFrame.INITIAL_WINDOW_SIZE); - // Values greater than Integer.MAX_VALUE will overflow to negative. - if (initialWindowSize != null && initialWindowSize < 0) - return connectionFailure(buffer, ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_settings_initial_window_size"); - - Integer maxFrameLength = settings.get(SettingsFrame.MAX_FRAME_SIZE); - if (maxFrameLength != null && (maxFrameLength < Frame.DEFAULT_MAX_LENGTH || maxFrameLength > Frame.MAX_MAX_LENGTH)) - return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_settings_max_frame_size"); - - SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK)); - reset(); - notifySettings(frame); - return true; - } - - public static SettingsFrame parseBody(final ByteBuffer buffer) { - final int bodyLength = buffer.remaining(); - final AtomicReference frameRef = new AtomicReference<>(); - SettingsBodyParser parser = new SettingsBodyParser(null, null) { - @Override - protected int getStreamId() { - return 0; - } - - @Override - protected int getBodyLength() { - return bodyLength; - } - - @Override - protected boolean onSettings(ByteBuffer buffer, Map settings) { - frameRef.set(new SettingsFrame(settings, false)); - return true; - } - - @Override - protected boolean connectionFailure(ByteBuffer buffer, int error, String reason) { - frameRef.set(null); - return false; - } - }; - if (bodyLength == 0) - parser.emptyBody(buffer); - else - parser.parse(buffer); - return frameRef.get(); - } - - private enum State { - PREPARE, SETTING_ID, SETTING_ID_BYTES, SETTING_VALUE, SETTING_VALUE_BYTES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/UnknownBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/UnknownBodyParser.java deleted file mode 100644 index e047490fe..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/UnknownBodyParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import java.nio.ByteBuffer; - -public class UnknownBodyParser extends BodyParser { - private int cursor; - - public UnknownBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - @Override - public boolean parse(ByteBuffer buffer) { - int length = cursor == 0 ? getBodyLength() : cursor; - cursor = consume(buffer, length); - return cursor == 0; - } - - private int consume(ByteBuffer buffer, int length) { - int remaining = buffer.remaining(); - if (remaining >= length) { - buffer.position(buffer.position() + length); - return 0; - } else { - buffer.position(buffer.limit()); - return length - remaining; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/WindowUpdateBodyParser.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/WindowUpdateBodyParser.java deleted file mode 100644 index 6d90315bb..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/decoder/WindowUpdateBodyParser.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.http.common.v2.decoder; - -import com.fireflysource.net.http.common.v2.frame.ErrorCode; -import com.fireflysource.net.http.common.v2.frame.WindowUpdateFrame; - -import java.nio.ByteBuffer; - -public class WindowUpdateBodyParser extends BodyParser { - private State state = State.PREPARE; - private int cursor; - private int windowDelta; - - public WindowUpdateBodyParser(HeaderParser headerParser, Parser.Listener listener) { - super(headerParser, listener); - } - - private void reset() { - state = State.PREPARE; - cursor = 0; - windowDelta = 0; - } - - @Override - public boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) { - switch (state) { - case PREPARE: { - int length = getBodyLength(); - if (length != 4) - return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_window_update_frame"); - state = State.WINDOW_DELTA; - break; - } - case WINDOW_DELTA: { - if (buffer.remaining() >= 4) { - windowDelta = buffer.getInt() & 0x7F_FF_FF_FF; - return onWindowUpdate(windowDelta); - } else { - state = State.WINDOW_DELTA_BYTES; - cursor = 4; - } - break; - } - case WINDOW_DELTA_BYTES: { - byte currByte = buffer.get(); - --cursor; - windowDelta += (currByte & 0xFF) << 8 * cursor; - if (cursor == 0) { - windowDelta &= 0x7F_FF_FF_FF; - return onWindowUpdate(windowDelta); - } - break; - } - default: { - throw new IllegalStateException(); - } - } - } - return false; - } - - private boolean onWindowUpdate(int windowDelta) { - WindowUpdateFrame frame = new WindowUpdateFrame(getStreamId(), windowDelta); - reset(); - notifyWindowUpdate(frame); - return true; - } - - private enum State { - PREPARE, WINDOW_DELTA, WINDOW_DELTA_BYTES - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/DataGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/DataGenerator.java deleted file mode 100644 index b660e0cbe..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/DataGenerator.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.DataFrame; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class DataGenerator { - private final HeaderGenerator headerGenerator; - - public DataGenerator(HeaderGenerator headerGenerator) { - this.headerGenerator = headerGenerator; - } - - public FrameBytes generate(DataFrame frame, int maxLength) { - return generateData(frame.getStreamId(), frame.getData(), frame.isEndStream(), maxLength); - } - - public FrameBytes generateData(int streamId, ByteBuffer data, boolean last, int maxLength) { - if (streamId < 0) - throw new IllegalArgumentException("Invalid stream id: " + streamId); - - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - - int dataLength = data.remaining(); - int maxFrameSize = headerGenerator.getMaxFrameSize(); - int length = Math.min(dataLength, Math.min(maxFrameSize, maxLength)); - if (length == dataLength) { - generateFrame(streamId, data, last, frameBytes); - } else { - int limit = data.limit(); - int newLimit = data.position() + length; - data.limit(newLimit); - ByteBuffer slice = data.slice(); - data.position(newLimit); - data.limit(limit); - generateFrame(streamId, slice, false, frameBytes); - } - frameBytes.setLength(Frame.HEADER_LENGTH + length); - return frameBytes; - } - - private void generateFrame(int streamId, ByteBuffer data, boolean last, FrameBytes frameBytes) { - int length = data.remaining(); - - int flags = Flags.NONE; - if (last) - flags |= Flags.END_STREAM; - - ByteBuffer header = headerGenerator.generate(FrameType.DATA, Frame.HEADER_LENGTH + length, length, flags, streamId); - BufferUtils.flipToFlush(header, 0); - frameBytes.getByteBuffers().add(header); - // Skip empty data buffers. - if (data.remaining() > 0) - frameBytes.getByteBuffers().add(data); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/DisconnectGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/DisconnectGenerator.java deleted file mode 100644 index 319213ca3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/DisconnectGenerator.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.net.http.common.v2.frame.Frame; - -public class DisconnectGenerator extends FrameGenerator { - public DisconnectGenerator() { - super(null); - } - - @Override - public FrameBytes generate(Frame frame) { - return FrameBytes.EMPTY; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/FrameBytes.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/FrameBytes.java deleted file mode 100644 index 23e66fb1e..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/FrameBytes.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.net.http.common.v2.frame.Frame; - -import java.nio.ByteBuffer; -import java.util.List; - -public class FrameBytes { - - public static final FrameBytes EMPTY = new FrameBytes(); - - private int length; - private List byteBuffers; - - public int getLength() { - return length; - } - - public void setLength(int length) { - this.length = length; - } - - public List getByteBuffers() { - return byteBuffers; - } - - public void setByteBuffers(List byteBuffers) { - this.byteBuffers = byteBuffers; - } - - public int getDataLength() { - if (length > 0) - return length - Frame.HEADER_LENGTH; - else - return 0; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/FrameGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/FrameGenerator.java deleted file mode 100644 index d09afbefb..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/FrameGenerator.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; - -import java.nio.ByteBuffer; - -public abstract class FrameGenerator { - private final HeaderGenerator headerGenerator; - - protected FrameGenerator(HeaderGenerator headerGenerator) { - this.headerGenerator = headerGenerator; - } - - public abstract FrameBytes generate(Frame frame); - - protected ByteBuffer generateHeader(FrameType frameType, int length, int flags, int streamId) { - return headerGenerator.generate(frameType, Frame.HEADER_LENGTH + length, length, flags, streamId); - } - - public int getMaxFrameSize() { - return headerGenerator.getMaxFrameSize(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/Generator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/Generator.java deleted file mode 100644 index 7e652e0d5..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/Generator.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.net.http.common.v2.frame.DataFrame; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.hpack.HpackEncoder; - -public class Generator { - private final HeaderGenerator headerGenerator; - private final HpackEncoder hpackEncoder; - private final FrameGenerator[] generators; - private final DataGenerator dataGenerator; - - public Generator() { - this(4096, 0); - } - - public Generator(int maxDynamicTableSize, int maxHeaderBlockFragment) { - - headerGenerator = new HeaderGenerator(); - hpackEncoder = new HpackEncoder(maxDynamicTableSize); - - this.generators = new FrameGenerator[FrameType.values().length]; - this.generators[FrameType.HEADERS.getType()] = new HeadersGenerator(headerGenerator, hpackEncoder, maxHeaderBlockFragment); - this.generators[FrameType.PRIORITY.getType()] = new PriorityGenerator(headerGenerator); - this.generators[FrameType.RST_STREAM.getType()] = new ResetGenerator(headerGenerator); - this.generators[FrameType.SETTINGS.getType()] = new SettingsGenerator(headerGenerator); - this.generators[FrameType.PUSH_PROMISE.getType()] = new PushPromiseGenerator(headerGenerator, hpackEncoder); - this.generators[FrameType.PING.getType()] = new PingGenerator(headerGenerator); - this.generators[FrameType.GO_AWAY.getType()] = new GoAwayGenerator(headerGenerator); - this.generators[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateGenerator(headerGenerator); - this.generators[FrameType.CONTINUATION.getType()] = null; // Never generated explicitly. - this.generators[FrameType.PREFACE.getType()] = new PrefaceGenerator(); - this.generators[FrameType.DISCONNECT.getType()] = new DisconnectGenerator(); - - this.dataGenerator = new DataGenerator(headerGenerator); - } - - public void setHeaderTableSize(int headerTableSize) { - hpackEncoder.setRemoteMaxDynamicTableSize(headerTableSize); - } - - public void setMaxFrameSize(int maxFrameSize) { - headerGenerator.setMaxFrameSize(maxFrameSize); - } - - public FrameBytes control(Frame frame) { - return generators[frame.getType().getType()].generate(frame); - } - - public FrameBytes data(DataFrame frame, int maxLength) { - return dataGenerator.generate(frame, maxLength); - } - - public void setMaxHeaderListSize(int value) { - hpackEncoder.setMaxHeaderListSize(value); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/GoAwayGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/GoAwayGenerator.java deleted file mode 100644 index 0cf5f8181..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/GoAwayGenerator.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.GoAwayFrame; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.LinkedList; - -public class GoAwayGenerator extends FrameGenerator { - - public GoAwayGenerator(HeaderGenerator headerGenerator) { - super(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - GoAwayFrame goAwayFrame = (GoAwayFrame) frame; - return generateGoAway(goAwayFrame.getLastStreamId(), goAwayFrame.getError(), goAwayFrame.getPayload()); - } - - public FrameBytes generateGoAway(int lastStreamId, int error, byte[] payload) { - if (lastStreamId < 0) { - lastStreamId = 0; - } - - // The last streamId + the error code. - int fixedLength = 4 + 4; - - // Make sure we don't exceed the default frame max length. - int maxPayloadLength = Frame.DEFAULT_MAX_LENGTH - fixedLength; - if (payload != null && payload.length > maxPayloadLength) - payload = Arrays.copyOfRange(payload, 0, maxPayloadLength); - - int length = fixedLength + (payload != null ? payload.length : 0); - ByteBuffer header = generateHeader(FrameType.GO_AWAY, length, Flags.NONE, 0); - - header.putInt(lastStreamId); - header.putInt(error); - - if (payload != null) { - header.put(payload); - } - - BufferUtils.flipToFlush(header, 0); - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setLength(Frame.HEADER_LENGTH + length); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - return frameBytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/HeaderGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/HeaderGenerator.java deleted file mode 100644 index fd9bbb0f4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/HeaderGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; - -import java.nio.ByteBuffer; - -public class HeaderGenerator { - private int maxFrameSize = Frame.DEFAULT_MAX_LENGTH; - - public ByteBuffer generate(FrameType frameType, int capacity, int length, int flags, int streamId) { - ByteBuffer header = BufferUtils.allocate(capacity); - BufferUtils.flipToFill(header); - header.put((byte) ((length & 0x00_FF_00_00) >>> 16)); - header.put((byte) ((length & 0x00_00_FF_00) >>> 8)); - header.put((byte) ((length & 0x00_00_00_FF))); - header.put((byte) frameType.getType()); - header.put((byte) flags); - header.putInt(streamId); - return header; - } - - public int getMaxFrameSize() { - return maxFrameSize; - } - - public void setMaxFrameSize(int maxFrameSize) { - this.maxFrameSize = maxFrameSize; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/HeadersGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/HeadersGenerator.java deleted file mode 100644 index e8e2ec5ee..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/HeadersGenerator.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.frame.*; -import com.fireflysource.net.http.common.v2.hpack.HpackEncoder; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class HeadersGenerator extends FrameGenerator { - - private final HpackEncoder encoder; - private final int maxHeaderBlockFragment; - private final PriorityGenerator priorityGenerator; - - public HeadersGenerator(HeaderGenerator headerGenerator, HpackEncoder encoder) { - this(headerGenerator, encoder, 0); - } - - public HeadersGenerator(HeaderGenerator headerGenerator, HpackEncoder encoder, int maxHeaderBlockFragment) { - super(headerGenerator); - this.encoder = encoder; - this.maxHeaderBlockFragment = maxHeaderBlockFragment; - this.priorityGenerator = new PriorityGenerator(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - HeadersFrame headersFrame = (HeadersFrame) frame; - return generateHeaders(headersFrame.getStreamId(), headersFrame.getMetaData(), headersFrame.getPriority(), headersFrame.isEndStream()); - } - - public FrameBytes generateHeaders(int streamId, MetaData metaData, PriorityFrame priority, boolean endStream) { - if (streamId < 0) - throw new IllegalArgumentException("Invalid stream id: " + streamId); - - int flags = Flags.NONE; - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - - if (priority != null) - flags = Flags.PRIORITY; - - int maxFrameSize = getMaxFrameSize(); - - ByteBuffer hpacked = BufferUtils.allocate(maxFrameSize); - BufferUtils.clearToFill(hpacked); - encoder.encode(hpacked, metaData); - int hpackedLength = hpacked.position(); - BufferUtils.flipToFlush(hpacked, 0); - - // Split into CONTINUATION frames if necessary. - if (maxHeaderBlockFragment > 0 && hpackedLength > maxHeaderBlockFragment) { - if (endStream) - flags |= Flags.END_STREAM; - - int length = maxHeaderBlockFragment; - if (priority != null) - length += PriorityFrame.PRIORITY_LENGTH; - - ByteBuffer header = generateHeader(FrameType.HEADERS, length, flags, streamId); - generatePriority(header, priority); - BufferUtils.flipToFlush(header, 0); - frameBytes.getByteBuffers().add(header); - hpacked.limit(maxHeaderBlockFragment); - frameBytes.getByteBuffers().add(hpacked.slice()); - - int totalLength = Frame.HEADER_LENGTH + length; - - int position = maxHeaderBlockFragment; - int limit = position + maxHeaderBlockFragment; - while (limit < hpackedLength) { - hpacked.position(position).limit(limit); - header = generateHeader(FrameType.CONTINUATION, maxHeaderBlockFragment, Flags.NONE, streamId); - BufferUtils.flipToFlush(header, 0); - frameBytes.getByteBuffers().add(header); - frameBytes.getByteBuffers().add(hpacked.slice()); - position += maxHeaderBlockFragment; - limit += maxHeaderBlockFragment; - totalLength += Frame.HEADER_LENGTH + maxHeaderBlockFragment; - } - - hpacked.position(position).limit(hpackedLength); - header = generateHeader(FrameType.CONTINUATION, hpacked.remaining(), Flags.END_HEADERS, streamId); - BufferUtils.flipToFlush(header, 0); - frameBytes.getByteBuffers().add(header); - frameBytes.getByteBuffers().add(hpacked); - totalLength += Frame.HEADER_LENGTH + hpacked.remaining(); - frameBytes.setLength(totalLength); - return frameBytes; - } else { - flags |= Flags.END_HEADERS; - if (endStream) - flags |= Flags.END_STREAM; - - int length = hpackedLength; - if (priority != null) - length += PriorityFrame.PRIORITY_LENGTH; - - ByteBuffer header = generateHeader(FrameType.HEADERS, length, flags, streamId); - generatePriority(header, priority); - BufferUtils.flipToFlush(header, 0); - frameBytes.getByteBuffers().add(header); - frameBytes.getByteBuffers().add(hpacked); - frameBytes.setLength(Frame.HEADER_LENGTH + length); - return frameBytes; - } - } - - private void generatePriority(ByteBuffer header, PriorityFrame priority) { - if (priority != null) { - priorityGenerator.generatePriorityBody(header, priority.getStreamId(), - priority.getParentStreamId(), priority.getWeight(), priority.isExclusive()); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PingGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PingGenerator.java deleted file mode 100644 index 0a46ad38d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PingGenerator.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.PingFrame; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class PingGenerator extends FrameGenerator { - - public PingGenerator(HeaderGenerator headerGenerator) { - super(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - PingFrame pingFrame = (PingFrame) frame; - return generatePing(pingFrame.getPayload(), pingFrame.isReply()); - } - - public FrameBytes generatePing(byte[] payload, boolean reply) { - if (payload.length != PingFrame.PING_LENGTH) { - throw new IllegalArgumentException("Invalid payload length: " + payload.length); - } - - ByteBuffer header = generateHeader(FrameType.PING, PingFrame.PING_LENGTH, reply ? Flags.ACK : Flags.NONE, 0); - - header.put(payload); - - BufferUtils.flipToFlush(header, 0); - - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - frameBytes.setLength(Frame.HEADER_LENGTH + PingFrame.PING_LENGTH); - return frameBytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PrefaceGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PrefaceGenerator.java deleted file mode 100644 index 1542497d2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PrefaceGenerator.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.PrefaceFrame; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class PrefaceGenerator extends FrameGenerator { - public PrefaceGenerator() { - super(null); - } - - @Override - public FrameBytes generate(Frame frame) { - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(ByteBuffer.wrap(PrefaceFrame.PREFACE_BYTES)); - frameBytes.setLength(PrefaceFrame.PREFACE_BYTES.length); - return frameBytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PriorityGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PriorityGenerator.java deleted file mode 100644 index ebd221821..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PriorityGenerator.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.PriorityFrame; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class PriorityGenerator extends FrameGenerator { - - public PriorityGenerator(HeaderGenerator headerGenerator) { - super(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - PriorityFrame priorityFrame = (PriorityFrame) frame; - return generatePriority(priorityFrame.getStreamId(), priorityFrame.getParentStreamId(), priorityFrame.getWeight(), priorityFrame.isExclusive()); - } - - public FrameBytes generatePriority(int streamId, int parentStreamId, int weight, boolean exclusive) { - ByteBuffer header = generateHeader(FrameType.PRIORITY, PriorityFrame.PRIORITY_LENGTH, Flags.NONE, streamId); - generatePriorityBody(header, streamId, parentStreamId, weight, exclusive); - BufferUtils.flipToFlush(header, 0); - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - frameBytes.setLength(Frame.HEADER_LENGTH + PriorityFrame.PRIORITY_LENGTH); - return frameBytes; - } - - public void generatePriorityBody(ByteBuffer header, int streamId, int parentStreamId, int weight, boolean exclusive) { - if (streamId < 0) - throw new IllegalArgumentException("Invalid stream id: " + streamId); - if (parentStreamId < 0) - throw new IllegalArgumentException("Invalid parent stream id: " + parentStreamId); - if (parentStreamId == streamId) - throw new IllegalArgumentException("Stream " + streamId + " cannot depend on stream " + parentStreamId); - if (weight < 1 || weight > 256) - throw new IllegalArgumentException("Invalid weight: " + weight); - - if (exclusive) - parentStreamId |= 0x80_00_00_00; - header.putInt(parentStreamId); - header.put((byte) (weight - 1)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PushPromiseGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PushPromiseGenerator.java deleted file mode 100644 index dedcbe454..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/PushPromiseGenerator.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.PushPromiseFrame; -import com.fireflysource.net.http.common.v2.hpack.HpackEncoder; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class PushPromiseGenerator extends FrameGenerator { - - private final HpackEncoder encoder; - - public PushPromiseGenerator(HeaderGenerator headerGenerator, HpackEncoder encoder) { - super(headerGenerator); - this.encoder = encoder; - } - - @Override - public FrameBytes generate(Frame frame) { - PushPromiseFrame pushPromiseFrame = (PushPromiseFrame) frame; - return generatePushPromise(pushPromiseFrame.getStreamId(), pushPromiseFrame.getPromisedStreamId(), pushPromiseFrame.getMetaData()); - } - - public FrameBytes generatePushPromise(int streamId, int promisedStreamId, MetaData metaData) { - if (streamId < 0) - throw new IllegalArgumentException("Invalid stream id: " + streamId); - if (promisedStreamId < 0) - throw new IllegalArgumentException("Invalid promised stream id: " + promisedStreamId); - - int maxFrameSize = getMaxFrameSize(); - // The promised streamId space. - int extraSpace = 4; - maxFrameSize -= extraSpace; - - ByteBuffer hpacked = BufferUtils.allocate(maxFrameSize); - BufferUtils.clearToFill(hpacked); - encoder.encode(hpacked, metaData); - int hpackedLength = hpacked.position(); - BufferUtils.flipToFlush(hpacked, 0); - - int length = hpackedLength + extraSpace; - int flags = Flags.END_HEADERS; - - ByteBuffer header = generateHeader(FrameType.PUSH_PROMISE, length, flags, streamId); - header.putInt(promisedStreamId); - BufferUtils.flipToFlush(header, 0); - - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - frameBytes.getByteBuffers().add(hpacked); - frameBytes.setLength(Frame.HEADER_LENGTH + length); - return frameBytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/ResetGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/ResetGenerator.java deleted file mode 100644 index 1c87ec873..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/ResetGenerator.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.ResetFrame; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class ResetGenerator extends FrameGenerator { - public ResetGenerator(HeaderGenerator headerGenerator) { - super(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - ResetFrame resetFrame = (ResetFrame) frame; - return generateReset(resetFrame.getStreamId(), resetFrame.getError()); - } - - public FrameBytes generateReset(int streamId, int error) { - if (streamId < 0) - throw new IllegalArgumentException("Invalid stream id: " + streamId); - - ByteBuffer header = generateHeader(FrameType.RST_STREAM, ResetFrame.RESET_LENGTH, Flags.NONE, streamId); - header.putInt(error); - BufferUtils.flipToFlush(header, 0); - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - frameBytes.setLength(Frame.HEADER_LENGTH + ResetFrame.RESET_LENGTH); - return frameBytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/SettingsGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/SettingsGenerator.java deleted file mode 100644 index 9391c4ed5..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/SettingsGenerator.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.SettingsFrame; - -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.Map; - -public class SettingsGenerator extends FrameGenerator { - public SettingsGenerator(HeaderGenerator headerGenerator) { - super(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - SettingsFrame settingsFrame = (SettingsFrame) frame; - return generateSettings(settingsFrame.getSettings(), settingsFrame.isReply()); - } - - public FrameBytes generateSettings(Map settings, boolean reply) { - // Two bytes for the identifier, four bytes for the value. - int entryLength = 2 + 4; - int length = entryLength * settings.size(); - if (length > getMaxFrameSize()) - throw new IllegalArgumentException("Invalid settings, too big"); - - ByteBuffer header = generateHeader(FrameType.SETTINGS, length, reply ? Flags.ACK : Flags.NONE, 0); - - for (Map.Entry entry : settings.entrySet()) { - header.putShort(entry.getKey().shortValue()); - header.putInt(entry.getValue()); - } - - BufferUtils.flipToFlush(header, 0); - - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - frameBytes.setLength(Frame.HEADER_LENGTH + length); - return frameBytes; - } - - public static ByteBuffer generateSettingsBody(Map settings) { - int size = settings.size() * (2 + 4); - ByteBuffer buffer = BufferUtils.allocate(size); - final int pos = BufferUtils.flipToFill(buffer); - for (Map.Entry entry : settings.entrySet()) { - buffer.putShort(entry.getKey().shortValue()); - buffer.putInt(entry.getValue()); - } - BufferUtils.flipToFlush(buffer, pos); - return buffer; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/WindowUpdateGenerator.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/WindowUpdateGenerator.java deleted file mode 100644 index 877a568eb..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/encoder/WindowUpdateGenerator.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.http.common.v2.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.frame.Flags; -import com.fireflysource.net.http.common.v2.frame.Frame; -import com.fireflysource.net.http.common.v2.frame.FrameType; -import com.fireflysource.net.http.common.v2.frame.WindowUpdateFrame; - -import java.nio.ByteBuffer; -import java.util.LinkedList; - -public class WindowUpdateGenerator extends FrameGenerator { - public WindowUpdateGenerator(HeaderGenerator headerGenerator) { - super(headerGenerator); - } - - @Override - public FrameBytes generate(Frame frame) { - WindowUpdateFrame windowUpdateFrame = (WindowUpdateFrame) frame; - return generateWindowUpdate(windowUpdateFrame.getStreamId(), windowUpdateFrame.getWindowDelta()); - } - - public FrameBytes generateWindowUpdate(int streamId, int windowUpdate) { - if (windowUpdate < 0) - throw new IllegalArgumentException("Invalid window update: " + windowUpdate); - - ByteBuffer header = generateHeader(FrameType.WINDOW_UPDATE, WindowUpdateFrame.WINDOW_UPDATE_LENGTH, Flags.NONE, streamId); - header.putInt(windowUpdate); - BufferUtils.flipToFlush(header, 0); - - FrameBytes frameBytes = new FrameBytes(); - frameBytes.setByteBuffers(new LinkedList<>()); - frameBytes.getByteBuffers().add(header); - frameBytes.setLength(Frame.HEADER_LENGTH + WindowUpdateFrame.WINDOW_UPDATE_LENGTH); - return frameBytes; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/CloseState.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/CloseState.java deleted file mode 100644 index 4758565fe..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/CloseState.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -/** - * The set of close states for a stream or a session. - *
    - *                rcv hc
    - * NOT_CLOSED ---------------> REMOTELY_CLOSED
    - *      |                             |
    - *   gen|                             |gen
    - *    hc|                             |hc
    - *      |                             |
    - *      v              rcv hc         v
    - * LOCALLY_CLOSING --------------> CLOSING
    - *      |                             |
    - *   snd|                             |gen
    - *    hc|                             |hc
    - *      |                             |
    - *      v              rcv hc         v
    - * LOCALLY_CLOSED ----------------> CLOSED
    - * 
    - */ -public enum CloseState { - /** - * Fully open. - */ - NOT_CLOSED, - /** - * A half-close frame has been generated. - */ - LOCALLY_CLOSING, - /** - * A half-close frame has been generated and sent. - */ - LOCALLY_CLOSED, - /** - * A half-close frame has been received. - */ - REMOTELY_CLOSED, - /** - * A half-close frame has been received, and a half-close frame has been generated, but not yet sent. - */ - CLOSING, - /** - * Fully closed. - */ - CLOSED; - - public enum Event { - RECEIVED, - BEFORE_SEND, - AFTER_SEND - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/DataFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/DataFrame.java deleted file mode 100644 index 273c0767c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/DataFrame.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.nio.ByteBuffer; - -public class DataFrame extends Frame { - private final int streamId; - private final ByteBuffer data; - private final boolean endStream; - private final int padding; - - public DataFrame(int streamId, ByteBuffer data, boolean endStream) { - this(streamId, data, endStream, 0); - } - - public DataFrame(int streamId, ByteBuffer data, boolean endStream, int padding) { - super(FrameType.DATA); - this.streamId = streamId; - this.data = data; - this.endStream = endStream; - this.padding = padding; - } - - public int getStreamId() { - return streamId; - } - - public ByteBuffer getData() { - return data; - } - - public boolean isEndStream() { - return endStream; - } - - /** - * @return the number of data bytes remaining. - */ - public int remaining() { - return data.remaining(); - } - - /** - * @return the number of bytes used for padding that count towards flow control. - */ - public int padding() { - return padding; - } - - @Override - public String toString() { - return String.format("%s#%d{length:%d,end=%b}", super.toString(), streamId, data.remaining(), endStream); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/DisconnectFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/DisconnectFrame.java deleted file mode 100644 index 4c7ecdb6a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/DisconnectFrame.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public class DisconnectFrame extends Frame { - public DisconnectFrame() { - super(FrameType.DISCONNECT); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/ErrorCode.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/ErrorCode.java deleted file mode 100644 index bb92ccfc3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/ErrorCode.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/** - * Standard HTTP/2 error codes. - */ -public enum ErrorCode { - /** - * Indicates no errors. - */ - NO_ERROR(0), - /** - * Indicates a generic HTTP/2 protocol violation. - */ - PROTOCOL_ERROR(1), - /** - * Indicates an internal error. - */ - INTERNAL_ERROR(2), - /** - * Indicates an HTTP/2 flow control violation. - */ - FLOW_CONTROL_ERROR(3), - /** - * Indicates that a SETTINGS frame did not receive a reply in a timely manner. - */ - SETTINGS_TIMEOUT_ERROR(4), - /** - * Indicates that a stream frame has been received after the stream closed. - */ - STREAM_CLOSED_ERROR(5), - /** - * Indicates that a frame has an invalid length. - */ - FRAME_SIZE_ERROR(6), - /** - * Indicates that a stream rejected before application processing. - */ - REFUSED_STREAM_ERROR(7), - /** - * Indicates that a stream is no longer needed. - */ - CANCEL_STREAM_ERROR(8), - /** - * Indicates inability to maintain the HPACK compression context. - */ - COMPRESSION_ERROR(9), - /** - * Indicates that the connection established by an HTTP CONNECT was abnormally closed. - */ - HTTP_CONNECT_ERROR(10), - /** - * Indicates that the other peer might be generating excessive load. - */ - ENHANCE_YOUR_CALM_ERROR(11), - /** - * Indicates that the transport properties do not meet minimum security requirements. - */ - INADEQUATE_SECURITY_ERROR(12), - /** - * Indicates that HTTP/1.1 must be used rather than HTTP/2. - */ - HTTP_1_1_REQUIRED_ERROR(13); - - public final int code; - - ErrorCode(int code) { - this.code = code; - Codes.codes.put(code, this); - } - - public static ErrorCode from(int error) { - return Codes.codes.get(error); - } - - public static String toString(int error, String defaultError) { - ErrorCode errorCode = from(error); - String result; - if (errorCode != null) { - result = errorCode.name().toLowerCase(Locale.ENGLISH); - } else if (defaultError == null) { - result = String.valueOf(error); - } else { - result = defaultError; - } - return result; - } - - private static class Codes { - private static final Map codes = new HashMap<>(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/FailureFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/FailureFrame.java deleted file mode 100644 index 8fb0337ba..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/FailureFrame.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public class FailureFrame extends Frame { - private final int error; - private final String reason; - - public FailureFrame(int error, String reason) { - super(FrameType.FAILURE); - this.error = error; - this.reason = reason; - } - - public int getError() { - return error; - } - - public String getReason() { - return reason; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/Flags.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/Flags.java deleted file mode 100644 index 29a5e5188..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/Flags.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public interface Flags { - int NONE = 0x00; - int END_STREAM = 0x01; - int ACK = 0x01; - int END_HEADERS = 0x04; - int PADDING = 0x08; - int PRIORITY = 0x20; -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/Frame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/Frame.java deleted file mode 100644 index 7f2d7e68b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/Frame.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public abstract class Frame { - public static final int HEADER_LENGTH = 9; - public static final int DEFAULT_MAX_LENGTH = 0x40_00; - public static final int MAX_MAX_LENGTH = 0xFF_FF_FF; - public static final Frame[] EMPTY_ARRAY = new Frame[0]; - - private final FrameType type; - - protected Frame(FrameType type) { - this.type = type; - } - - public FrameType getType() { - return type; - } - - @Override - public String toString() { - return String.format("%s@%x", getClass().getSimpleName(), hashCode()); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/FrameType.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/FrameType.java deleted file mode 100644 index 9f9454043..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/FrameType.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.util.HashMap; -import java.util.Map; - -public enum FrameType { - DATA(0), - HEADERS(1), - PRIORITY(2), - RST_STREAM(3), - SETTINGS(4), - PUSH_PROMISE(5), - PING(6), - GO_AWAY(7), - WINDOW_UPDATE(8), - CONTINUATION(9), - // Synthetic frames only needed by the implementation. - PREFACE(10), - DISCONNECT(11), - FAILURE(12); - - private final int type; - - FrameType(int type) { - this.type = type; - Types.types.put(type, this); - } - - public static FrameType from(int type) { - return Types.types.get(type); - } - - public int getType() { - return type; - } - - private static class Types { - private static final Map types = new HashMap<>(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/GoAwayFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/GoAwayFrame.java deleted file mode 100644 index b995b26d4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/GoAwayFrame.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.nio.charset.StandardCharsets; - -public class GoAwayFrame extends Frame { - private final CloseState closeState; - private final int lastStreamId; - private final int error; - private final byte[] payload; - - public GoAwayFrame(int lastStreamId, int error, byte[] payload) { - this(CloseState.REMOTELY_CLOSED, lastStreamId, error, payload); - } - - public GoAwayFrame(CloseState closeState, int lastStreamId, int error, byte[] payload) { - super(FrameType.GO_AWAY); - this.closeState = closeState; - this.lastStreamId = lastStreamId; - this.error = error; - this.payload = payload; - } - - public int getLastStreamId() { - return lastStreamId; - } - - public int getError() { - return error; - } - - public byte[] getPayload() { - return payload; - } - - public String tryConvertPayload() { - if (payload == null || payload.length == 0) - return ""; - try { - return new String(payload, StandardCharsets.UTF_8); - } catch (Throwable x) { - return ""; - } - } - - @Override - public String toString() { - return String.format("%s,%d/%s/%s/%s", - super.toString(), - lastStreamId, - ErrorCode.toString(error, null), - tryConvertPayload(), - closeState); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/HeadersFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/HeadersFrame.java deleted file mode 100644 index 605eac60c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/HeadersFrame.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.model.MetaData; - -public class HeadersFrame extends Frame { - private final int streamId; - private final MetaData metaData; - private final PriorityFrame priority; - private final boolean endStream; - private boolean endHeaders; - - /** - *

    Creates a new {@code HEADERS} frame with an unspecified stream {@code id}.

    - *

    The stream {@code id} will be generated by the implementation while sending - * this frame to the other peer.

    - * - * @param metaData the metadata containing HTTP request information - * @param priority the PRIORITY frame associated with this HEADERS frame - * @param endStream whether this frame ends the stream - */ - public HeadersFrame(MetaData metaData, PriorityFrame priority, boolean endStream) { - this(0, metaData, priority, endStream); - } - - /** - *

    Creates a new {@code HEADERS} frame with the specified stream {@code id}.

    - *

    {@code HEADERS} frames with a specific stream {@code id} are typically used - * in responses to request {@code HEADERS} frames.

    - * - * @param streamId the stream id - * @param metaData the metadata containing HTTP request/response information - * @param priority the PRIORITY frame associated with this HEADERS frame - * @param endStream whether this frame ends the stream - */ - public HeadersFrame(int streamId, MetaData metaData, PriorityFrame priority, boolean endStream) { - super(FrameType.HEADERS); - this.streamId = streamId; - this.metaData = metaData; - this.priority = priority; - this.endStream = endStream; - } - - public int getStreamId() { - return streamId; - } - - public MetaData getMetaData() { - return metaData; - } - - public PriorityFrame getPriority() { - return priority; - } - - public boolean isEndStream() { - return endStream; - } - - public boolean isEndHeaders() { - return endHeaders; - } - - public void setEndHeaders(boolean endHeaders) { - this.endHeaders = endHeaders; - } - - @Override - public String toString() { - return String.format("%s#%d{end=%b}%s", super.toString(), streamId, endStream, - priority == null ? "" : String.format("+%s", priority)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PingFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PingFrame.java deleted file mode 100644 index 009568f15..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PingFrame.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.util.Objects; - -public class PingFrame extends Frame { - public static final int PING_LENGTH = 8; - private static final byte[] EMPTY_PAYLOAD = new byte[8]; - - private final byte[] payload; - private final boolean reply; - - /** - * Creates a PING frame with an empty payload. - * - * @param reply whether this PING frame is a reply - */ - public PingFrame(boolean reply) { - this(EMPTY_PAYLOAD, reply); - } - - /** - * Creates a PING frame with the given {@code long} {@code value} as payload. - * - * @param value the value to use as a payload for this PING frame - * @param reply whether this PING frame is a reply - */ - public PingFrame(long value, boolean reply) { - this(toBytes(value), reply); - } - - /** - * Creates a PING frame with the given {@code payload}. - * - * @param payload the payload for this PING frame - * @param reply whether this PING frame is a reply - */ - public PingFrame(byte[] payload, boolean reply) { - super(FrameType.PING); - this.payload = Objects.requireNonNull(payload); - if (payload.length != PING_LENGTH) - throw new IllegalArgumentException("PING payload must be 8 bytes"); - this.reply = reply; - } - - private static byte[] toBytes(long value) { - byte[] result = new byte[8]; - for (int i = result.length - 1; i >= 0; --i) { - result[i] = (byte) (value & 0xFF); - value >>= 8; - } - return result; - } - - private static long toLong(byte[] payload) { - long result = 0; - for (int i = 0; i < 8; ++i) { - result <<= 8; - result |= (payload[i] & 0xFF); - } - return result; - } - - public byte[] getPayload() { - return payload; - } - - public long getPayloadAsLong() { - return toLong(payload); - } - - public boolean isReply() { - return reply; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PrefaceFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PrefaceFrame.java deleted file mode 100644 index ecf9b7b04..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PrefaceFrame.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.nio.charset.StandardCharsets; - -public class PrefaceFrame extends Frame { - /** - * The bytes of the HTTP/2 preface that form a legal HTTP/1.1 - * request, used in the direct upgrade. - */ - public static final byte[] PREFACE_PREAMBLE_BYTES = ( - "PRI * HTTP/2.0\r\n" + - "\r\n" - ).getBytes(StandardCharsets.US_ASCII); - - /** - * The HTTP/2 preface bytes. - */ - public static final byte[] PREFACE_BYTES = ( - "PRI * HTTP/2.0\r\n" + - "\r\n" + - "SM\r\n" + - "\r\n" - ).getBytes(StandardCharsets.US_ASCII); - - public PrefaceFrame() { - super(FrameType.PREFACE); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PriorityFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PriorityFrame.java deleted file mode 100644 index f36c16c3c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PriorityFrame.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public class PriorityFrame extends Frame { - public static final int PRIORITY_LENGTH = 5; - - private final int streamId; - private final int parentStreamId; - private final int weight; - private final boolean exclusive; - - public PriorityFrame(int parentStreamId, int weight, boolean exclusive) { - this(0, parentStreamId, weight, exclusive); - } - - public PriorityFrame(int streamId, int parentStreamId, int weight, boolean exclusive) { - super(FrameType.PRIORITY); - this.streamId = streamId; - this.parentStreamId = parentStreamId; - this.weight = weight; - this.exclusive = exclusive; - } - - public int getStreamId() { - return streamId; - } - - /** - * @return int of the Parent Stream - * @deprecated use {@link #getParentStreamId()} instead. - */ - @Deprecated - public int getDependentStreamId() { - return getParentStreamId(); - } - - public int getParentStreamId() { - return parentStreamId; - } - - public int getWeight() { - return weight; - } - - public boolean isExclusive() { - return exclusive; - } - - @Override - public String toString() { - return String.format("%s#%d/#%d{weight=%d,exclusive=%b}", super.toString(), streamId, parentStreamId, weight, exclusive); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PushPromiseFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PushPromiseFrame.java deleted file mode 100644 index 080e96b53..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/PushPromiseFrame.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.model.MetaData; - -public class PushPromiseFrame extends Frame { - private final int streamId; - private final int promisedStreamId; - private final MetaData metaData; - private boolean endHeaders; - - public PushPromiseFrame(int streamId, int promisedStreamId, MetaData metaData) { - super(FrameType.PUSH_PROMISE); - this.streamId = streamId; - this.promisedStreamId = promisedStreamId; - this.metaData = metaData; - } - - public int getStreamId() { - return streamId; - } - - public int getPromisedStreamId() { - return promisedStreamId; - } - - public MetaData getMetaData() { - return metaData; - } - - public boolean isEndHeaders() { - return endHeaders; - } - - public void setEndHeaders(boolean endHeaders) { - this.endHeaders = endHeaders; - } - - @Override - public String toString() { - return String.format("%s#%d/#%d", super.toString(), streamId, promisedStreamId); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/ResetFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/ResetFrame.java deleted file mode 100644 index 764a9292a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/ResetFrame.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public class ResetFrame extends Frame { - public static final int RESET_LENGTH = 4; - - private final int streamId; - private final int error; - - public ResetFrame(int streamId, int error) { - super(FrameType.RST_STREAM); - this.streamId = streamId; - this.error = error; - } - - public int getStreamId() { - return streamId; - } - - public int getError() { - return error; - } - - @Override - public String toString() { - return String.format("%s#%d{%s}", super.toString(), streamId, ErrorCode.toString(error, null)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/SettingsFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/SettingsFrame.java deleted file mode 100644 index 3de5960ba..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/SettingsFrame.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import java.util.HashMap; -import java.util.Map; - -public class SettingsFrame extends Frame { - - public static final int DEFAULT_MAX_KEYS = 64; - - public static final int HEADER_TABLE_SIZE = 1; - public static final int ENABLE_PUSH = 2; - public static final int MAX_CONCURRENT_STREAMS = 3; - public static final int INITIAL_WINDOW_SIZE = 4; - public static final int MAX_FRAME_SIZE = 5; - public static final int MAX_HEADER_LIST_SIZE = 6; - - public static final SettingsFrame DEFAULT_SETTINGS_FRAME; - - static { - Map settings = new HashMap<>(); - settings.put(HEADER_TABLE_SIZE, 4096); - settings.put(ENABLE_PUSH, 1); - settings.put(INITIAL_WINDOW_SIZE, 65535); - settings.put(MAX_FRAME_SIZE, 16384); - DEFAULT_SETTINGS_FRAME = new SettingsFrame(settings, false); - } - - private final Map settings; - private final boolean reply; - - public SettingsFrame(Map settings, boolean reply) { - super(FrameType.SETTINGS); - this.settings = settings; - this.reply = reply; - } - - public Map getSettings() { - return settings; - } - - public boolean isReply() { - return reply; - } - - @Override - public String toString() { - return String.format("%s,reply=%b:%s", super.toString(), reply, settings); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/WindowUpdateFrame.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/WindowUpdateFrame.java deleted file mode 100644 index 15615eefa..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/frame/WindowUpdateFrame.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -public class WindowUpdateFrame extends Frame { - public static final int WINDOW_UPDATE_LENGTH = 4; - - private final int streamId; - private final int windowDelta; - - public WindowUpdateFrame(int streamId, int windowDelta) { - super(FrameType.WINDOW_UPDATE); - this.streamId = streamId; - this.windowDelta = windowDelta; - } - - public int getStreamId() { - return streamId; - } - - public int getWindowDelta() { - return windowDelta; - } - - public boolean isStreamWindowUpdate() { - return streamId != 0; - } - - public boolean isSessionWindowUpdate() { - return streamId == 0; - } - - @Override - public String toString() { - return String.format("%s#%d,delta=%d", super.toString(), streamId, windowDelta); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/AuthorityHttpField.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/AuthorityHttpField.java deleted file mode 100644 index 2d8c9fc82..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/AuthorityHttpField.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - - -import com.fireflysource.net.http.common.model.HostPortHttpField; -import com.fireflysource.net.http.common.model.HttpHeader; - -public class AuthorityHttpField extends HostPortHttpField { - public final static String AUTHORITY = HpackContext.STATIC_TABLE[1][0]; - - public AuthorityHttpField(String authority) { - super(HttpHeader.C_AUTHORITY, AUTHORITY, authority); - } - - @Override - public String toString() { - return String.format("%s(preparsed h=%s p=%d)", super.toString(), getHost(), getPort()); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackContext.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackContext.java deleted file mode 100644 index 58be93402..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackContext.java +++ /dev/null @@ -1,431 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.common.collection.trie.ArrayTernaryTrie; -import com.fireflysource.common.collection.trie.Trie; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpMethod; -import com.fireflysource.net.http.common.model.HttpScheme; - -import java.nio.ByteBuffer; -import java.util.*; - -/** - * HPACK - Header Compression for HTTP/2 - *

    - * This class maintains the compression context for a single HTTP/2 connection. - * Specifically it holds the static and dynamic Header Field Tables and the - * associated sizes and limits. - *

    - *

    - * It is compliant with draft 11 of the specification - *

    - */ -public class HpackContext { - - private static final LazyLogger LOG = SystemLogger.create(HpackContext.class); - - private static final String EMPTY = ""; - public static final String[][] STATIC_TABLE = { - {null, null}, - /* 1 */ {":authority", EMPTY}, - /* 2 */ {":method", "GET"}, - /* 3 */ {":method", "POST"}, - /* 4 */ {":path", "/"}, - /* 5 */ {":path", "/index.html"}, - /* 6 */ {":scheme", "http"}, - /* 7 */ {":scheme", "https"}, - /* 8 */ {":status", "200"}, - /* 9 */ {":status", "204"}, - /* 10 */ {":status", "206"}, - /* 11 */ {":status", "304"}, - /* 12 */ {":status", "400"}, - /* 13 */ {":status", "404"}, - /* 14 */ {":status", "500"}, - /* 15 */ {"accept-charset", EMPTY}, - /* 16 */ {"accept-encoding", "gzip, deflate"}, - /* 17 */ {"accept-language", EMPTY}, - /* 18 */ {"accept-ranges", EMPTY}, - /* 19 */ {"accept", EMPTY}, - /* 20 */ {"access-control-allow-origin", EMPTY}, - /* 21 */ {"age", EMPTY}, - /* 22 */ {"allow", EMPTY}, - /* 23 */ {"authorization", EMPTY}, - /* 24 */ {"cache-control", EMPTY}, - /* 25 */ {"content-disposition", EMPTY}, - /* 26 */ {"content-encoding", EMPTY}, - /* 27 */ {"content-language", EMPTY}, - /* 28 */ {"content-length", EMPTY}, - /* 29 */ {"content-location", EMPTY}, - /* 30 */ {"content-range", EMPTY}, - /* 31 */ {"content-type", EMPTY}, - /* 32 */ {"cookie", EMPTY}, - /* 33 */ {"date", EMPTY}, - /* 34 */ {"etag", EMPTY}, - /* 35 */ {"expect", EMPTY}, - /* 36 */ {"expires", EMPTY}, - /* 37 */ {"from", EMPTY}, - /* 38 */ {"host", EMPTY}, - /* 39 */ {"if-match", EMPTY}, - /* 40 */ {"if-modified-since", EMPTY}, - /* 41 */ {"if-none-match", EMPTY}, - /* 42 */ {"if-range", EMPTY}, - /* 43 */ {"if-unmodified-since", EMPTY}, - /* 44 */ {"last-modified", EMPTY}, - /* 45 */ {"link", EMPTY}, - /* 46 */ {"location", EMPTY}, - /* 47 */ {"max-forwards", EMPTY}, - /* 48 */ {"proxy-authenticate", EMPTY}, - /* 49 */ {"proxy-authorization", EMPTY}, - /* 50 */ {"range", EMPTY}, - /* 51 */ {"referer", EMPTY}, - /* 52 */ {"refresh", EMPTY}, - /* 53 */ {"retry-after", EMPTY}, - /* 54 */ {"server", EMPTY}, - /* 55 */ {"set-cookie", EMPTY}, - /* 56 */ {"strict-transport-security", EMPTY}, - /* 57 */ {"transfer-encoding", EMPTY}, - /* 58 */ {"user-agent", EMPTY}, - /* 59 */ {"vary", EMPTY}, - /* 60 */ {"via", EMPTY}, - /* 61 */ {"www-authenticate", EMPTY} - }; - - private static final Map STATIC_FIELD_MAP = new HashMap<>(); - private static final Trie STATIC_NAME_MAP = new ArrayTernaryTrie<>(true, 512); - private static final StaticEntry[] STATIC_TABLE_BY_HEADER = new StaticEntry[HttpHeader.UNKNOWN.ordinal()]; - private static final StaticEntry[] STATIC_TABLE_ENTRIES = new StaticEntry[STATIC_TABLE.length]; - public static final int STATIC_SIZE = STATIC_TABLE.length - 1; - - static { - Set added = new HashSet<>(); - for (int i = 1; i < STATIC_TABLE.length; i++) { - StaticEntry entry = null; - - String name = STATIC_TABLE[i][0]; - String value = STATIC_TABLE[i][1]; - HttpHeader header = HttpHeader.CACHE.get(name); - if (header != null && value != null) { - switch (header) { - case C_METHOD: { - - HttpMethod method = HttpMethod.CACHE.get(value); - if (method != null) - entry = new StaticEntry(i, new StaticTableHttpField(header, name, value, method)); - break; - } - - case C_SCHEME: { - - HttpScheme scheme = HttpScheme.from(value); - if (scheme != null) - entry = new StaticEntry(i, new StaticTableHttpField(header, name, value, scheme)); - break; - } - - case C_STATUS: { - entry = new StaticEntry(i, new StaticTableHttpField(header, name, value, Integer.valueOf(value))); - break; - } - - default: - break; - } - } - - if (entry == null) - entry = new StaticEntry(i, header == null ? new HttpField(STATIC_TABLE[i][0], value) : new HttpField(header, name, value)); - - STATIC_TABLE_ENTRIES[i] = entry; - - if (entry.field.getValue() != null) - STATIC_FIELD_MAP.put(entry.field, entry); - - if (!added.contains(entry.field.getName())) { - added.add(entry.field.getName()); - STATIC_NAME_MAP.put(entry.field.getName(), entry); - if (STATIC_NAME_MAP.get(entry.field.getName()) == null) - throw new IllegalStateException("name trie too small"); - } - } - - for (HttpHeader h : HttpHeader.values()) { - StaticEntry entry = STATIC_NAME_MAP.get(h.getValue()); - if (entry != null) - STATIC_TABLE_BY_HEADER[h.ordinal()] = entry; - } - } - - private int maxDynamicTableSizeInBytes; - private int dynamicTableSizeInBytes; - private final DynamicTable dynamicTable; - private final Map fieldMap = new HashMap<>(); - private final Map nameMap = new HashMap<>(); - - public HpackContext(int maxDynamicTableSize) { - maxDynamicTableSizeInBytes = maxDynamicTableSize; - int guesstimateEntries = 10 + maxDynamicTableSize / (32 + 10 + 10); - dynamicTable = new DynamicTable(guesstimateEntries); - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] created max=%d", hashCode(), maxDynamicTableSize)); - } - - public void resize(int newMaxDynamicTableSize) { - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), maxDynamicTableSizeInBytes, newMaxDynamicTableSize)); - maxDynamicTableSizeInBytes = newMaxDynamicTableSize; - dynamicTable.evict(); - } - - public Entry get(HttpField field) { - Entry entry = fieldMap.get(field); - if (entry == null) - entry = STATIC_FIELD_MAP.get(field); - return entry; - } - - public Entry get(String name) { - Entry entry = STATIC_NAME_MAP.get(name); - if (entry != null) - return entry; - return nameMap.get(StringUtils.asciiToLowerCase(name)); - } - - public Entry get(int index) { - if (index <= STATIC_SIZE) - return STATIC_TABLE_ENTRIES[index]; - - return dynamicTable.get(index); - } - - public Entry get(HttpHeader header) { - Entry e = STATIC_TABLE_BY_HEADER[header.ordinal()]; - if (e == null) - return get(header.getValue()); - return e; - } - - public static Entry getStatic(HttpHeader header) { - return STATIC_TABLE_BY_HEADER[header.ordinal()]; - } - - public Entry add(HttpField field) { - Entry entry = new Entry(field); - int size = entry.getSize(); - if (size > maxDynamicTableSizeInBytes) { - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, maxDynamicTableSizeInBytes)); - dynamicTable.evictAll(); - return null; - } - dynamicTableSizeInBytes += size; - dynamicTable.add(entry); - fieldMap.put(field, entry); - nameMap.put(StringUtils.asciiToLowerCase(field.getName()), entry); - - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] added %s", hashCode(), entry)); - dynamicTable.evict(); - return entry; - } - - /** - * @return Current dynamic table size in entries - */ - public int size() { - return dynamicTable.size(); - } - - /** - * @return Current Dynamic table size in Octets - */ - public int getDynamicTableSize() { - return dynamicTableSizeInBytes; - } - - /** - * @return Max Dynamic table size in Octets - */ - public int getMaxDynamicTableSize() { - return maxDynamicTableSizeInBytes; - } - - public int index(Entry entry) { - if (entry.slot < 0) - return 0; - if (entry.isStatic()) - return entry.slot; - - return dynamicTable.index(entry); - } - - public static int staticIndex(HttpHeader header) { - if (header == null) - return 0; - Entry entry = STATIC_NAME_MAP.get(header.getValue()); - if (entry == null) - return 0; - return entry.slot; - } - - @Override - public String toString() { - return String.format("HpackContext@%x{entries=%d,size=%d,max=%d}", hashCode(), dynamicTable.size(), dynamicTableSizeInBytes, maxDynamicTableSizeInBytes); - } - - private class DynamicTable { - Entry[] entries; - int size; - int offset; - int growby; - - private DynamicTable(int initCapacity) { - entries = new Entry[initCapacity]; - growby = initCapacity; - } - - public void add(Entry entry) { - if (size == entries.length) { - Entry[] entries = new Entry[this.entries.length + growby]; - for (int i = 0; i < size; i++) { - int slot = (offset + i) % this.entries.length; - entries[i] = this.entries[slot]; - entries[i].slot = i; - } - this.entries = entries; - offset = 0; - } - int slot = (size++ + offset) % entries.length; - entries[slot] = entry; - entry.slot = slot; - } - - public int index(Entry entry) { - return STATIC_SIZE + size - (entry.slot - offset + entries.length) % entries.length; - } - - public Entry get(int index) { - int d = index - STATIC_SIZE - 1; - if (d < 0 || d >= size) - return null; - int slot = (offset + size - d - 1) % entries.length; - return entries[slot]; - } - - public int size() { - return size; - } - - private void evict() { - while (dynamicTableSizeInBytes > maxDynamicTableSizeInBytes) { - Entry entry = entries[offset]; - entries[offset] = null; - offset = (offset + 1) % entries.length; - size--; - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] evict %s", HpackContext.this.hashCode(), entry)); - dynamicTableSizeInBytes -= entry.getSize(); - entry.slot = -1; - fieldMap.remove(entry.getHttpField()); - String lc = StringUtils.asciiToLowerCase(entry.getHttpField().getName()); - if (entry == nameMap.get(lc)) - nameMap.remove(lc); - } - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", HpackContext.this.hashCode(), dynamicTable.size(), dynamicTableSizeInBytes, maxDynamicTableSizeInBytes)); - } - - private void evictAll() { - if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] evictAll", HpackContext.this.hashCode())); - fieldMap.clear(); - nameMap.clear(); - offset = 0; - size = 0; - dynamicTableSizeInBytes = 0; - Arrays.fill(entries, null); - } - } - - public static class Entry { - final HttpField field; - int slot; // The index within it's an array - - Entry() { - slot = -1; - field = null; - } - - Entry(HttpField field) { - this.field = field; - } - - public int getSize() { - String value = field.getValue(); - return 32 + field.getName().length() + (value == null ? 0 : value.length()); - } - - public HttpField getHttpField() { - return field; - } - - public boolean isStatic() { - return false; - } - - public byte[] getStaticHuffmanValue() { - return null; - } - - @Override - public String toString() { - return String.format("{%s,%d,%s,%x}", isStatic() ? "S" : "D", slot, field, hashCode()); - } - } - - public static class StaticEntry extends Entry { - private final byte[] huffmanValue; - private final byte encodedField; - - StaticEntry(int index, HttpField field) { - super(field); - slot = index; - String value = field.getValue(); - if (value != null && value.length() > 0) { - int huffmanLen = Huffman.octetsNeeded(value); - if (huffmanLen < 0) - throw new IllegalStateException("bad value"); - int lenLen = NBitInteger.octectsNeeded(7, huffmanLen); - huffmanValue = new byte[1 + lenLen + huffmanLen]; - ByteBuffer buffer = ByteBuffer.wrap(huffmanValue); - - // Indicate Huffman - buffer.put((byte) 0x80); - // Add huffman length - NBitInteger.encode(buffer, 7, huffmanLen); - // Encode value - Huffman.encode(buffer, value); - } else - huffmanValue = null; - - encodedField = (byte) (0x80 | index); - } - - @Override - public boolean isStatic() { - return true; - } - - @Override - public byte[] getStaticHuffmanValue() { - return huffmanValue; - } - - public byte getEncodedField() { - return encodedField; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackDecoder.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackDecoder.java deleted file mode 100644 index 387486fd8..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackDecoder.java +++ /dev/null @@ -1,256 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpTokens; -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v2.hpack.HpackContext.Entry; -import org.slf4j.Logger; - -import java.nio.ByteBuffer; - -/** - * Hpack Decoder - *

    - * This is not thread safe and may only be called by 1 thread at a time. - *

    - */ -public class HpackDecoder { - public static final Logger LOG = SystemLogger.create(HpackDecoder.class); - - public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 = - new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, 0L); - - private final HpackContext context; - private final MetaDataBuilder builder; - private int localMaxDynamicTableSize; - - /** - * @param localMaxDynamicTableSize The maximum allowed size of the local dynamic header field table. - * @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters, plus 32 per field - */ - public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize) { - context = new HpackContext(localMaxDynamicTableSize); - this.localMaxDynamicTableSize = localMaxDynamicTableSize; - builder = new MetaDataBuilder(maxHeaderSize); - } - - public HpackContext getHpackContext() { - return context; - } - - public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize) { - localMaxDynamicTableSize = localMaxdynamciTableSize; - } - - public MetaData decode(ByteBuffer buffer) throws HpackException.SessionException, HpackException.StreamException { - if (LOG.isDebugEnabled()) - LOG.debug(String.format("CtxTbl[%x] decoding %d octets", context.hashCode(), buffer.remaining())); - - // If the buffer is big, don't even think about decoding it - if (buffer.remaining() > builder.getMaxSize()) - throw new HpackException.SessionException("431 Request Header Fields too large"); - - boolean emitted = false; - - while (buffer.hasRemaining()) { - if (LOG.isDebugEnabled() && buffer.hasArray()) { - int l = Math.min(buffer.remaining(), 32); - LOG.debug("decode {}{}", - TypeUtils.toHexString(buffer.array(), buffer.arrayOffset() + buffer.position(), l), - l < buffer.remaining() ? "..." : ""); - } - - byte b = buffer.get(); - if (b < 0) { - // 7.1 indexed if the high bit is set - int index = NBitInteger.decode(buffer, 7); - Entry entry = context.get(index); - if (entry == null) - throw new HpackException.SessionException("Unknown index %d", index); - - if (entry.isStatic()) { - if (LOG.isDebugEnabled()) - LOG.debug("decode IdxStatic {}", entry); - // emit field - emitted = true; - builder.emit(entry.getHttpField()); - - // TODO copy and add to reference set if there is room - // _context.add(entry.getHttpField()); - } else { - if (LOG.isDebugEnabled()) - LOG.debug("decode Idx {}", entry); - // emit - emitted = true; - builder.emit(entry.getHttpField()); - } - } else { - // look at the first nibble in detail - byte f = (byte) ((b & 0xF0) >> 4); - String name; - HttpHeader header; - String value; - - boolean indexed; - int nameIndex; - - switch (f) { - case 2: // 7.3 - case 3: // 7.3 - // change table size - int size = NBitInteger.decode(buffer, 5); - if (LOG.isDebugEnabled()) - LOG.debug("decode resize=" + size); - if (size > localMaxDynamicTableSize) - throw new IllegalArgumentException(); - if (emitted) - throw new HpackException.CompressionException("Dynamic table resize after fields"); - context.resize(size); - continue; - - case 0: // 7.2.2 - case 1: // 7.2.3 - indexed = false; - nameIndex = NBitInteger.decode(buffer, 4); - break; - - case 4: // 7.2.1 - case 5: // 7.2.1 - case 6: // 7.2.1 - case 7: // 7.2.1 - indexed = true; - nameIndex = NBitInteger.decode(buffer, 6); - break; - - default: - throw new IllegalStateException(); - } - - boolean huffmanName = false; - - // decode the name - if (nameIndex > 0) { - Entry nameEntry = context.get(nameIndex); - name = nameEntry.getHttpField().getName(); - header = nameEntry.getHttpField().getHeader(); - } else { - huffmanName = (buffer.get() & 0x80) == 0x80; - int length = NBitInteger.decode(buffer, 7); - builder.checkSize(length, huffmanName); - if (huffmanName) - name = Huffman.decode(buffer, length); - else - name = toASCIIString(buffer, length); - check: - for (int i = name.length(); i-- > 0; ) { - char c = name.charAt(i); - if (c > 0xff) { - builder.streamException("Illegal header name %s", name); - break; - } - HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c]; - switch (token.getType()) { - case ALPHA: - if (c >= 'A' && c <= 'Z') { - builder.streamException("Uppercase header name %s", name); - break check; - } - break; - - case COLON: - case TCHAR: - case DIGIT: - break; - - default: - builder.streamException("Illegal header name %s", name); - break check; - } - } - header = HttpHeader.CACHE.get(name); - } - - // decode the value - boolean huffmanValue = (buffer.get() & 0x80) == 0x80; - int length = NBitInteger.decode(buffer, 7); - builder.checkSize(length, huffmanValue); - if (huffmanValue) - value = Huffman.decode(buffer, length); - else - value = toASCIIString(buffer, length); - - // Make the new field - HttpField field; - if (header == null) { - // just make a normal field and bypass header name lookup - field = new HttpField(null, name, value); - } else { - // might be worthwhile to create a value HttpField if it is indexed - // and/or of a type that may be looked up multiple times. - switch (header) { - case C_STATUS: - if (indexed) - field = new HttpField.IntValueHttpField(header, name, value); - else - field = new HttpField(header, name, value); - break; - - case C_AUTHORITY: - field = new AuthorityHttpField(value); - break; - - case CONTENT_LENGTH: - if ("0".equals(value)) - field = CONTENT_LENGTH_0; - else - field = new HttpField.LongValueHttpField(header, name, value); - break; - - default: - field = new HttpField(header, name, value); - break; - } - } - - if (LOG.isDebugEnabled()) { - LOG.debug("decoded '{}' by {}/{}/{}", - field, - nameIndex > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"), - huffmanValue ? "HuffVal" : "LitVal", - indexed ? "Idx" : ""); - } - - // emit the field - emitted = true; - builder.emit(field); - - // if indexed add to dynamic table - if (indexed) - context.add(field); - } - } - - return builder.build(); - } - - public static String toASCIIString(ByteBuffer buffer, int length) { - StringBuilder builder = new StringBuilder(length); - int position = buffer.position(); - int start = buffer.arrayOffset() + position; - int end = start + length; - buffer.position(position + length); - byte[] array = buffer.array(); - for (int i = start; i < end; i++) { - builder.append((char) (0x7f & array[i])); - } - return builder.toString(); - } - - @Override - public String toString() { - return String.format("HpackDecoder@%x{%s}", hashCode(), context); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackEncoder.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackEncoder.java deleted file mode 100644 index 66a91558f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackEncoder.java +++ /dev/null @@ -1,389 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.http.common.codec.PreEncodedHttpField; -import com.fireflysource.net.http.common.model.*; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Set; - -import static com.fireflysource.net.http.common.v2.hpack.HpackContext.Entry; -import static com.fireflysource.net.http.common.v2.hpack.HpackContext.StaticEntry; - -public class HpackEncoder { - private static final LazyLogger LOG = SystemLogger.create(HpackEncoder.class); - - private static final HttpField[] STATUSES = new HttpField[599]; - static final EnumSet DO_NOT_HUFFMAN = EnumSet.of( - HttpHeader.AUTHORIZATION, - HttpHeader.CONTENT_MD5, - HttpHeader.PROXY_AUTHENTICATE, - HttpHeader.PROXY_AUTHORIZATION); - static final EnumSet DO_NOT_INDEX = EnumSet.of( - // HttpHeader.C_PATH, // TODO more data needed - // HttpHeader.DATE, // TODO more data needed - HttpHeader.AUTHORIZATION, - HttpHeader.CONTENT_MD5, - HttpHeader.CONTENT_RANGE, - HttpHeader.ETAG, - HttpHeader.IF_MODIFIED_SINCE, - HttpHeader.IF_UNMODIFIED_SINCE, - HttpHeader.IF_NONE_MATCH, - HttpHeader.IF_RANGE, - HttpHeader.IF_MATCH, - HttpHeader.LOCATION, - HttpHeader.RANGE, - HttpHeader.RETRY_AFTER, - // HttpHeader.EXPIRES, - HttpHeader.LAST_MODIFIED, - HttpHeader.SET_COOKIE, - HttpHeader.SET_COOKIE2); - static final EnumSet NEVER_INDEX = EnumSet.of( - HttpHeader.AUTHORIZATION, - HttpHeader.SET_COOKIE, - HttpHeader.SET_COOKIE2); - private static final EnumSet IGNORED_HEADERS = EnumSet.of(HttpHeader.CONNECTION, HttpHeader.KEEP_ALIVE, - HttpHeader.PROXY_CONNECTION, HttpHeader.TRANSFER_ENCODING, HttpHeader.UPGRADE); - private static final PreEncodedHttpField TE_TRAILERS = new PreEncodedHttpField(HttpHeader.TE, "trailers"); - private static final PreEncodedHttpField C_SCHEME_HTTP = new PreEncodedHttpField(HttpHeader.C_SCHEME, "http"); - private static final PreEncodedHttpField C_SCHEME_HTTPS = new PreEncodedHttpField(HttpHeader.C_SCHEME, "https"); - private static final EnumMap C_METHODS = new EnumMap<>(HttpMethod.class); - - static { - for (HttpStatus.Code code : HttpStatus.Code.values()) { - STATUSES[code.getCode()] = new PreEncodedHttpField(HttpHeader.C_STATUS, Integer.toString(code.getCode())); - } - for (HttpMethod method : HttpMethod.values()) { - C_METHODS.put(method, new PreEncodedHttpField(HttpHeader.C_METHOD, method.getValue())); - } - } - - private final HpackContext context; - private final boolean debug; - private int remoteMaxDynamicTableSize; - private int localMaxDynamicTableSize; - private int maxHeaderListSize; - private int headerListSize; - private boolean validateEncoding = true; - - public HpackEncoder() { - this(4096, 4096, -1); - } - - public HpackEncoder(int localMaxDynamicTableSize) { - this(localMaxDynamicTableSize, 4096, -1); - } - - public HpackEncoder(int localMaxDynamicTableSize, int remoteMaxDynamicTableSize) { - this(localMaxDynamicTableSize, remoteMaxDynamicTableSize, -1); - } - - public HpackEncoder(int localMaxDynamicTableSize, int remoteMaxDynamicTableSize, int maxHeaderListSize) { - context = new HpackContext(remoteMaxDynamicTableSize); - this.remoteMaxDynamicTableSize = remoteMaxDynamicTableSize; - this.localMaxDynamicTableSize = localMaxDynamicTableSize; - this.maxHeaderListSize = maxHeaderListSize; - debug = LOG.isDebugEnabled(); - } - - public int getMaxHeaderListSize() { - return maxHeaderListSize; - } - - public void setMaxHeaderListSize(int maxHeaderListSize) { - this.maxHeaderListSize = maxHeaderListSize; - } - - public HpackContext getHpackContext() { - return context; - } - - public void setRemoteMaxDynamicTableSize(int remoteMaxDynamicTableSize) { - this.remoteMaxDynamicTableSize = remoteMaxDynamicTableSize; - } - - public void setLocalMaxDynamicTableSize(int localMaxDynamicTableSize) { - this.localMaxDynamicTableSize = localMaxDynamicTableSize; - } - - public boolean isValidateEncoding() { - return validateEncoding; - } - - public void setValidateEncoding(boolean validateEncoding) { - this.validateEncoding = validateEncoding; - } - - public void encode(ByteBuffer buffer, MetaData metadata) throws HpackException { - try { - if (LOG.isDebugEnabled()) - LOG.debug(String.format("CtxTbl[%x] encoding", context.hashCode())); - - HttpFields fields = metadata.getFields(); - // Verify that we can encode without errors. - if (isValidateEncoding() && fields != null) { - for (HttpField field : fields) { - String name = field.getName(); - char firstChar = name.charAt(0); - if (firstChar <= ' ' || firstChar == ':') - throw new HpackException.StreamException("Invalid header name: '%s'", name); - } - } - - headerListSize = 0; - int pos = buffer.position(); - - // Check the dynamic table sizes! - int maxDynamicTableSize = Math.min(remoteMaxDynamicTableSize, localMaxDynamicTableSize); - if (maxDynamicTableSize != context.getMaxDynamicTableSize()) - encodeMaxDynamicTableSize(buffer, maxDynamicTableSize); - - // Add Request/response meta fields - if (!metadata.isOnlyTrailer()) { - if (metadata.isRequest()) { - MetaData.Request request = (MetaData.Request) metadata; - - String scheme = request.getURI().getScheme(); - encode(buffer, HttpScheme.HTTPS.is(scheme) ? C_SCHEME_HTTPS : C_SCHEME_HTTP); - String method = request.getMethod(); - HttpMethod httpMethod = method == null ? null : HttpMethod.from(method); - HttpField methodField = C_METHODS.get(httpMethod); - encode(buffer, methodField == null ? new HttpField(HttpHeader.C_METHOD, method) : methodField); - encode(buffer, new HttpField(HttpHeader.C_AUTHORITY, request.getURI().getAuthority())); - encode(buffer, new HttpField(HttpHeader.C_PATH, request.getURI().getPathQuery())); - } else if (metadata.isResponse()) { - MetaData.Response response = (MetaData.Response) metadata; - int code = response.getStatus(); - HttpField status = code < STATUSES.length ? STATUSES[code] : null; - if (status == null) - status = new HttpField.IntValueHttpField(HttpHeader.C_STATUS, code); - encode(buffer, status); - } - } - - // Remove fields as specified in RFC 7540, 8.1.2.2. - if (fields != null) { - // For example: Connection: Close, TE, Upgrade, Custom. - Set hopHeaders = null; - for (String value : fields.getCSV(HttpHeader.CONNECTION, false)) { - if (hopHeaders == null) - hopHeaders = new HashSet<>(); - hopHeaders.add(StringUtils.asciiToLowerCase(value)); - } - for (HttpField field : fields) { - HttpHeader header = field.getHeader(); - if (header != null && IGNORED_HEADERS.contains(header)) - continue; - if (header == HttpHeader.TE) { - if (field.contains("trailers")) - encode(buffer, TE_TRAILERS); - continue; - } - String name = field.getLowerCaseName(); - if (hopHeaders != null && hopHeaders.contains(name)) - continue; - encode(buffer, field); - } - } - - // Check size - if (maxHeaderListSize > 0 && headerListSize > maxHeaderListSize) { - LOG.warn("Header list size too large {} > {}", headerListSize, maxHeaderListSize); - if (LOG.isDebugEnabled()) - LOG.debug("metadata={}", metadata); - } - - if (LOG.isDebugEnabled()) - LOG.debug(String.format("CtxTbl[%x] encoded %d octets", context.hashCode(), buffer.position() - pos)); - } catch (HpackException x) { - throw x; - } catch (Throwable x) { - HpackException.SessionException failure = new HpackException.SessionException("Could not hpack encode %s", metadata); - failure.initCause(x); - throw failure; - } - } - - public void encodeMaxDynamicTableSize(ByteBuffer buffer, int maxDynamicTableSize) { - if (maxDynamicTableSize > remoteMaxDynamicTableSize) - throw new IllegalArgumentException(); - buffer.put((byte) 0x20); - NBitInteger.encode(buffer, 5, maxDynamicTableSize); - context.resize(maxDynamicTableSize); - } - - public void encode(ByteBuffer buffer, HttpField field) { - if (field.getValue() == null) - field = new HttpField(field.getHeader(), field.getName(), ""); - - int fieldSize = field.getName().length() + field.getValue().length(); - headerListSize += fieldSize + 32; - - final int p = debug ? buffer.position() : -1; - - String encoding = null; - - // Is there an entry for the field? - Entry entry = context.get(field); - if (entry != null) { - // Known field entry, so encode it as indexed - if (entry.isStatic()) { - buffer.put(((StaticEntry) entry).getEncodedField()); - if (debug) - encoding = "IdxFieldS1"; - } else { - int index = context.index(entry); - buffer.put((byte) 0x80); - NBitInteger.encode(buffer, 7, index); - if (debug) - encoding = "IdxField" + (entry.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(7, index)); - } - } else { - // Unknown field entry, so we will have to send literally. - final boolean indexed; - - // But do we know it's name? - HttpHeader header = field.getHeader(); - - // Select encoding strategy - if (header == null) { - // Select encoding strategy for unknown header names - Entry name = context.get(field.getName()); - - if (field instanceof PreEncodedHttpField) { - int i = buffer.position(); - ((PreEncodedHttpField) field).putTo(buffer, HttpVersion.HTTP_2); - byte b = buffer.get(i); - indexed = b < 0 || b >= 0x40; - if (debug) - encoding = indexed ? "PreEncodedIdx" : "PreEncoded"; - } - // has the custom header name been seen before? - else if (name == null) { - // unknown name and value, so let's index this just in case it is - // the first time we have seen a custom name or a custom field. - // unless the name is changing, this is worthwhile - indexed = true; - encodeName(buffer, (byte) 0x40, 6, field.getName(), null); - encodeValue(buffer, true, field.getValue()); - if (debug) - encoding = "LitHuffNHuffVIdx"; - } else { - // known custom name, but unknown value. - // This is probably a custom field with changing value, so don't index. - indexed = false; - encodeName(buffer, (byte) 0x00, 4, field.getName(), null); - encodeValue(buffer, true, field.getValue()); - if (debug) - encoding = "LitHuffNHuffV!Idx"; - } - } else { - // Select encoding strategy for known header names - Entry name = context.get(header); - - if (field instanceof PreEncodedHttpField) { - // Preencoded field - int i = buffer.position(); - ((PreEncodedHttpField) field).putTo(buffer, HttpVersion.HTTP_2); - byte b = buffer.get(i); - indexed = b < 0 || b >= 0x40; - if (debug) - encoding = indexed ? "PreEncodedIdx" : "PreEncoded"; - } else if (DO_NOT_INDEX.contains(header)) { - // Non indexed field - indexed = false; - boolean neverIndex = NEVER_INDEX.contains(header); - boolean huffman = !DO_NOT_HUFFMAN.contains(header); - encodeName(buffer, neverIndex ? (byte) 0x10 : (byte) 0x00, 4, header.getValue(), name); - encodeValue(buffer, huffman, field.getValue()); - - if (debug) - encoding = "Lit" + - ((name == null) ? "HuffN" : ("IdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(4, context.index(name))))) + - (huffman ? "HuffV" : "LitV") + - (neverIndex ? "!!Idx" : "!Idx"); - } else if (fieldSize >= context.getMaxDynamicTableSize() || header == HttpHeader.CONTENT_LENGTH && field.getValue().length() > 2) { - // Non indexed if field too large or a content length for 3 digits or more - indexed = false; - encodeName(buffer, (byte) 0x00, 4, header.getValue(), name); - encodeValue(buffer, true, field.getValue()); - if (debug) - encoding = "LitIdxNS" + (1 + NBitInteger.octectsNeeded(4, context.index(name))) + "HuffV!Idx"; - } else { - // indexed - indexed = true; - boolean huffman = !DO_NOT_HUFFMAN.contains(header); - encodeName(buffer, (byte) 0x40, 6, header.getValue(), name); - encodeValue(buffer, huffman, field.getValue()); - if (debug) - encoding = ((name == null) ? "LitHuffN" : ("LitIdxN" + (name.isStatic() ? "S" : "") + (1 + NBitInteger.octectsNeeded(6, context.index(name))))) + - (huffman ? "HuffVIdx" : "LitVIdx"); - } - } - - // If we want the field referenced, then we add it to our table and reference set. - if (indexed) - context.add(field); - } - - if (debug) { - int e = buffer.position(); - if (LOG.isDebugEnabled()) - LOG.debug("encode {}:'{}' to '{}'", encoding, field, TypeUtils.toHexString(buffer.array(), buffer.arrayOffset() + p, e - p)); - } - } - - private void encodeName(ByteBuffer buffer, byte mask, int bits, String name, Entry entry) { - buffer.put(mask); - if (entry == null) { - // leave name index bits as 0 - // Encode the name always with lowercase huffman - buffer.put((byte) 0x80); - NBitInteger.encode(buffer, 7, Huffman.octetsNeededLC(name)); - Huffman.encodeLC(buffer, name); - } else { - NBitInteger.encode(buffer, bits, context.index(entry)); - } - } - - static void encodeValue(ByteBuffer buffer, boolean huffman, String value) { - if (huffman) { - // huffman literal value - buffer.put((byte) 0x80); - - int needed = Huffman.octetsNeeded(value); - if (needed >= 0) { - NBitInteger.encode(buffer, 7, needed); - Huffman.encode(buffer, value); - } else { - // Not iso_8859_1 - byte[] bytes = value.getBytes(StandardCharsets.UTF_8); - NBitInteger.encode(buffer, 7, Huffman.octetsNeeded(bytes)); - Huffman.encode(buffer, bytes); - } - } else { - // add literal assuming iso_8859_1 - buffer.put((byte) 0x00).mark(); - NBitInteger.encode(buffer, 7, value.length()); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c < ' ' || c > 127) { - // Not iso_8859_1, so re-encode as UTF-8 - buffer.reset(); - byte[] bytes = value.getBytes(StandardCharsets.UTF_8); - NBitInteger.encode(buffer, 7, bytes.length); - buffer.put(bytes, 0, bytes.length); - return; - } - buffer.put((byte) c); - } - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackException.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackException.java deleted file mode 100644 index 1504fc8bf..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackException.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -public abstract class HpackException extends RuntimeException { - HpackException(String messageFormat, Object... args) { - super(String.format(messageFormat, args)); - } - - /** - * A Stream HPACK exception. - *

    Stream exceptions are not fatal to the connection, and the - * hpack state is complete and able to continue handling other - * decoding/encoding for the session. - *

    - */ - public static class StreamException extends HpackException { - StreamException(String messageFormat, Object... args) { - super(messageFormat, args); - } - } - - /** - * A Session HPACK Exception. - *

    Session exceptions are fatal for the stream, and the HPACK - * state is unable to decode/encode further.

    - */ - public static class SessionException extends HpackException { - SessionException(String messageFormat, Object... args) { - super(messageFormat, args); - } - } - - public static class CompressionException extends SessionException { - public CompressionException(String messageFormat, Object... args) { - super(messageFormat, args); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackFieldPreEncoder.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackFieldPreEncoder.java deleted file mode 100644 index 4ac83305a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/HpackFieldPreEncoder.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.codec.HttpFieldPreEncoder; -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpVersion; - -import java.nio.ByteBuffer; - -public class HpackFieldPreEncoder implements HttpFieldPreEncoder { - - @Override - public HttpVersion getHttpVersion() { - return HttpVersion.HTTP_2; - } - - @Override - public byte[] getEncodedField(HttpHeader header, String name, String value) { - boolean not_indexed = HpackEncoder.DO_NOT_INDEX.contains(header); - - ByteBuffer buffer = ByteBuffer.allocate(name.length() + value.length() + 10); - boolean huffman; - int bits; - - if (not_indexed) { - // Non indexed field - boolean never_index = HpackEncoder.NEVER_INDEX.contains(header); - huffman = !HpackEncoder.DO_NOT_HUFFMAN.contains(header); - buffer.put(never_index ? (byte) 0x10 : (byte) 0x00); - bits = 4; - } else if (header == HttpHeader.CONTENT_LENGTH && value.length() > 1) { - // Non indexed content length for 2 digits or more - buffer.put((byte) 0x00); - huffman = true; - bits = 4; - } else { - // indexed - buffer.put((byte) 0x40); - huffman = !HpackEncoder.DO_NOT_HUFFMAN.contains(header); - bits = 6; - } - - int name_idx = HpackContext.staticIndex(header); - if (name_idx > 0) - NBitInteger.encode(buffer, bits, name_idx); - else { - buffer.put((byte) 0x80); - NBitInteger.encode(buffer, 7, Huffman.octetsNeededLC(name)); - Huffman.encodeLC(buffer, name); - } - - HpackEncoder.encodeValue(buffer, huffman, value); - - buffer.flip(); - return BufferUtils.toArray(buffer); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/Huffman.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/Huffman.java deleted file mode 100644 index 473e6fdb6..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/Huffman.java +++ /dev/null @@ -1,497 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.common.string.Utf8StringBuilder; - -import java.nio.ByteBuffer; - -public class Huffman { - - // Appendix C: Huffman Codes - // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C - static final int[][] CODES = { - /* ( 0) |11111111|11000 */ {0x1ff8, 13}, - /* ( 1) |11111111|11111111|1011000 */ {0x7fffd8, 23}, - /* ( 2) |11111111|11111111|11111110|0010 */ {0xfffffe2, 28}, - /* ( 3) |11111111|11111111|11111110|0011 */ {0xfffffe3, 28}, - /* ( 4) |11111111|11111111|11111110|0100 */ {0xfffffe4, 28}, - /* ( 5) |11111111|11111111|11111110|0101 */ {0xfffffe5, 28}, - /* ( 6) |11111111|11111111|11111110|0110 */ {0xfffffe6, 28}, - /* ( 7) |11111111|11111111|11111110|0111 */ {0xfffffe7, 28}, - /* ( 8) |11111111|11111111|11111110|1000 */ {0xfffffe8, 28}, - /* ( 9) |11111111|11111111|11101010 */ {0xffffea, 24}, - /* ( 10) |11111111|11111111|11111111|111100 */ {0x3ffffffc, 30}, - /* ( 11) |11111111|11111111|11111110|1001 */ {0xfffffe9, 28}, - /* ( 12) |11111111|11111111|11111110|1010 */ {0xfffffea, 28}, - /* ( 13) |11111111|11111111|11111111|111101 */ {0x3ffffffd, 30}, - /* ( 14) |11111111|11111111|11111110|1011 */ {0xfffffeb, 28}, - /* ( 15) |11111111|11111111|11111110|1100 */ {0xfffffec, 28}, - /* ( 16) |11111111|11111111|11111110|1101 */ {0xfffffed, 28}, - /* ( 17) |11111111|11111111|11111110|1110 */ {0xfffffee, 28}, - /* ( 18) |11111111|11111111|11111110|1111 */ {0xfffffef, 28}, - /* ( 19) |11111111|11111111|11111111|0000 */ {0xffffff0, 28}, - /* ( 20) |11111111|11111111|11111111|0001 */ {0xffffff1, 28}, - /* ( 21) |11111111|11111111|11111111|0010 */ {0xffffff2, 28}, - /* ( 22) |11111111|11111111|11111111|111110 */ {0x3ffffffe, 30}, - /* ( 23) |11111111|11111111|11111111|0011 */ {0xffffff3, 28}, - /* ( 24) |11111111|11111111|11111111|0100 */ {0xffffff4, 28}, - /* ( 25) |11111111|11111111|11111111|0101 */ {0xffffff5, 28}, - /* ( 26) |11111111|11111111|11111111|0110 */ {0xffffff6, 28}, - /* ( 27) |11111111|11111111|11111111|0111 */ {0xffffff7, 28}, - /* ( 28) |11111111|11111111|11111111|1000 */ {0xffffff8, 28}, - /* ( 29) |11111111|11111111|11111111|1001 */ {0xffffff9, 28}, - /* ( 30) |11111111|11111111|11111111|1010 */ {0xffffffa, 28}, - /* ( 31) |11111111|11111111|11111111|1011 */ {0xffffffb, 28}, - /*' ' ( 32) |010100 */ {0x14, 6}, - /*'!' ( 33) |11111110|00 */ {0x3f8, 10}, - /*'"' ( 34) |11111110|01 */ {0x3f9, 10}, - /*'#' ( 35) |11111111|1010 */ {0xffa, 12}, - /*'$' ( 36) |11111111|11001 */ {0x1ff9, 13}, - /*'%' ( 37) |010101 */ {0x15, 6}, - /*'&' ( 38) |11111000 */ {0xf8, 8}, - /*''' ( 39) |11111111|010 */ {0x7fa, 11}, - /*'(' ( 40) |11111110|10 */ {0x3fa, 10}, - /*')' ( 41) |11111110|11 */ {0x3fb, 10}, - /*'*' ( 42) |11111001 */ {0xf9, 8}, - /*'+' ( 43) |11111111|011 */ {0x7fb, 11}, - /*',' ( 44) |11111010 */ {0xfa, 8}, - /*'-' ( 45) |010110 */ {0x16, 6}, - /*'.' ( 46) |010111 */ {0x17, 6}, - /*'/' ( 47) |011000 */ {0x18, 6}, - /*'0' ( 48) |00000 */ {0x0, 5}, - /*'1' ( 49) |00001 */ {0x1, 5}, - /*'2' ( 50) |00010 */ {0x2, 5}, - /*'3' ( 51) |011001 */ {0x19, 6}, - /*'4' ( 52) |011010 */ {0x1a, 6}, - /*'5' ( 53) |011011 */ {0x1b, 6}, - /*'6' ( 54) |011100 */ {0x1c, 6}, - /*'7' ( 55) |011101 */ {0x1d, 6}, - /*'8' ( 56) |011110 */ {0x1e, 6}, - /*'9' ( 57) |011111 */ {0x1f, 6}, - /*':' ( 58) |1011100 */ {0x5c, 7}, - /*';' ( 59) |11111011 */ {0xfb, 8}, - /*'<' ( 60) |11111111|1111100 */ {0x7ffc, 15}, - /*'=' ( 61) |100000 */ {0x20, 6}, - /*'>' ( 62) |11111111|1011 */ {0xffb, 12}, - /*'?' ( 63) |11111111|00 */ {0x3fc, 10}, - /*'@' ( 64) |11111111|11010 */ {0x1ffa, 13}, - /*'A' ( 65) |100001 */ {0x21, 6}, - /*'B' ( 66) |1011101 */ {0x5d, 7}, - /*'C' ( 67) |1011110 */ {0x5e, 7}, - /*'D' ( 68) |1011111 */ {0x5f, 7}, - /*'E' ( 69) |1100000 */ {0x60, 7}, - /*'F' ( 70) |1100001 */ {0x61, 7}, - /*'G' ( 71) |1100010 */ {0x62, 7}, - /*'H' ( 72) |1100011 */ {0x63, 7}, - /*'I' ( 73) |1100100 */ {0x64, 7}, - /*'J' ( 74) |1100101 */ {0x65, 7}, - /*'K' ( 75) |1100110 */ {0x66, 7}, - /*'L' ( 76) |1100111 */ {0x67, 7}, - /*'M' ( 77) |1101000 */ {0x68, 7}, - /*'N' ( 78) |1101001 */ {0x69, 7}, - /*'O' ( 79) |1101010 */ {0x6a, 7}, - /*'P' ( 80) |1101011 */ {0x6b, 7}, - /*'Q' ( 81) |1101100 */ {0x6c, 7}, - /*'R' ( 82) |1101101 */ {0x6d, 7}, - /*'S' ( 83) |1101110 */ {0x6e, 7}, - /*'T' ( 84) |1101111 */ {0x6f, 7}, - /*'U' ( 85) |1110000 */ {0x70, 7}, - /*'V' ( 86) |1110001 */ {0x71, 7}, - /*'W' ( 87) |1110010 */ {0x72, 7}, - /*'X' ( 88) |11111100 */ {0xfc, 8}, - /*'Y' ( 89) |1110011 */ {0x73, 7}, - /*'Z' ( 90) |11111101 */ {0xfd, 8}, - /*'[' ( 91) |11111111|11011 */ {0x1ffb, 13}, - /*'\' ( 92) |11111111|11111110|000 */ {0x7fff0, 19}, - /*']' ( 93) |11111111|11100 */ {0x1ffc, 13}, - /*'^' ( 94) |11111111|111100 */ {0x3ffc, 14}, - /*'_' ( 95) |100010 */ {0x22, 6}, - /*'`' ( 96) |11111111|1111101 */ {0x7ffd, 15}, - /*'a' ( 97) |00011 */ {0x3, 5}, - /*'b' ( 98) |100011 */ {0x23, 6}, - /*'c' ( 99) |00100 */ {0x4, 5}, - /*'d' (100) |100100 */ {0x24, 6}, - /*'e' (101) |00101 */ {0x5, 5}, - /*'f' (102) |100101 */ {0x25, 6}, - /*'g' (103) |100110 */ {0x26, 6}, - /*'h' (104) |100111 */ {0x27, 6}, - /*'i' (105) |00110 */ {0x6, 5}, - /*'j' (106) |1110100 */ {0x74, 7}, - /*'k' (107) |1110101 */ {0x75, 7}, - /*'l' (108) |101000 */ {0x28, 6}, - /*'m' (109) |101001 */ {0x29, 6}, - /*'n' (110) |101010 */ {0x2a, 6}, - /*'o' (111) |00111 */ {0x7, 5}, - /*'p' (112) |101011 */ {0x2b, 6}, - /*'q' (113) |1110110 */ {0x76, 7}, - /*'r' (114) |101100 */ {0x2c, 6}, - /*'s' (115) |01000 */ {0x8, 5}, - /*'t' (116) |01001 */ {0x9, 5}, - /*'u' (117) |101101 */ {0x2d, 6}, - /*'v' (118) |1110111 */ {0x77, 7}, - /*'w' (119) |1111000 */ {0x78, 7}, - /*'x' (120) |1111001 */ {0x79, 7}, - /*'y' (121) |1111010 */ {0x7a, 7}, - /*'z' (122) |1111011 */ {0x7b, 7}, - /*'{' (123) |11111111|1111110 */ {0x7ffe, 15}, - /*'|' (124) |11111111|100 */ {0x7fc, 11}, - /*'}' (125) |11111111|111101 */ {0x3ffd, 14}, - /*'~' (126) |11111111|11101 */ {0x1ffd, 13}, - /* (127) |11111111|11111111|11111111|1100 */ {0xffffffc, 28}, - /* (128) |11111111|11111110|0110 */ {0xfffe6, 20}, - /* (129) |11111111|11111111|010010 */ {0x3fffd2, 22}, - /* (130) |11111111|11111110|0111 */ {0xfffe7, 20}, - /* (131) |11111111|11111110|1000 */ {0xfffe8, 20}, - /* (132) |11111111|11111111|010011 */ {0x3fffd3, 22}, - /* (133) |11111111|11111111|010100 */ {0x3fffd4, 22}, - /* (134) |11111111|11111111|010101 */ {0x3fffd5, 22}, - /* (135) |11111111|11111111|1011001 */ {0x7fffd9, 23}, - /* (136) |11111111|11111111|010110 */ {0x3fffd6, 22}, - /* (137) |11111111|11111111|1011010 */ {0x7fffda, 23}, - /* (138) |11111111|11111111|1011011 */ {0x7fffdb, 23}, - /* (139) |11111111|11111111|1011100 */ {0x7fffdc, 23}, - /* (140) |11111111|11111111|1011101 */ {0x7fffdd, 23}, - /* (141) |11111111|11111111|1011110 */ {0x7fffde, 23}, - /* (142) |11111111|11111111|11101011 */ {0xffffeb, 24}, - /* (143) |11111111|11111111|1011111 */ {0x7fffdf, 23}, - /* (144) |11111111|11111111|11101100 */ {0xffffec, 24}, - /* (145) |11111111|11111111|11101101 */ {0xffffed, 24}, - /* (146) |11111111|11111111|010111 */ {0x3fffd7, 22}, - /* (147) |11111111|11111111|1100000 */ {0x7fffe0, 23}, - /* (148) |11111111|11111111|11101110 */ {0xffffee, 24}, - /* (149) |11111111|11111111|1100001 */ {0x7fffe1, 23}, - /* (150) |11111111|11111111|1100010 */ {0x7fffe2, 23}, - /* (151) |11111111|11111111|1100011 */ {0x7fffe3, 23}, - /* (152) |11111111|11111111|1100100 */ {0x7fffe4, 23}, - /* (153) |11111111|11111110|11100 */ {0x1fffdc, 21}, - /* (154) |11111111|11111111|011000 */ {0x3fffd8, 22}, - /* (155) |11111111|11111111|1100101 */ {0x7fffe5, 23}, - /* (156) |11111111|11111111|011001 */ {0x3fffd9, 22}, - /* (157) |11111111|11111111|1100110 */ {0x7fffe6, 23}, - /* (158) |11111111|11111111|1100111 */ {0x7fffe7, 23}, - /* (159) |11111111|11111111|11101111 */ {0xffffef, 24}, - /* (160) |11111111|11111111|011010 */ {0x3fffda, 22}, - /* (161) |11111111|11111110|11101 */ {0x1fffdd, 21}, - /* (162) |11111111|11111110|1001 */ {0xfffe9, 20}, - /* (163) |11111111|11111111|011011 */ {0x3fffdb, 22}, - /* (164) |11111111|11111111|011100 */ {0x3fffdc, 22}, - /* (165) |11111111|11111111|1101000 */ {0x7fffe8, 23}, - /* (166) |11111111|11111111|1101001 */ {0x7fffe9, 23}, - /* (167) |11111111|11111110|11110 */ {0x1fffde, 21}, - /* (168) |11111111|11111111|1101010 */ {0x7fffea, 23}, - /* (169) |11111111|11111111|011101 */ {0x3fffdd, 22}, - /* (170) |11111111|11111111|011110 */ {0x3fffde, 22}, - /* (171) |11111111|11111111|11110000 */ {0xfffff0, 24}, - /* (172) |11111111|11111110|11111 */ {0x1fffdf, 21}, - /* (173) |11111111|11111111|011111 */ {0x3fffdf, 22}, - /* (174) |11111111|11111111|1101011 */ {0x7fffeb, 23}, - /* (175) |11111111|11111111|1101100 */ {0x7fffec, 23}, - /* (176) |11111111|11111111|00000 */ {0x1fffe0, 21}, - /* (177) |11111111|11111111|00001 */ {0x1fffe1, 21}, - /* (178) |11111111|11111111|100000 */ {0x3fffe0, 22}, - /* (179) |11111111|11111111|00010 */ {0x1fffe2, 21}, - /* (180) |11111111|11111111|1101101 */ {0x7fffed, 23}, - /* (181) |11111111|11111111|100001 */ {0x3fffe1, 22}, - /* (182) |11111111|11111111|1101110 */ {0x7fffee, 23}, - /* (183) |11111111|11111111|1101111 */ {0x7fffef, 23}, - /* (184) |11111111|11111110|1010 */ {0xfffea, 20}, - /* (185) |11111111|11111111|100010 */ {0x3fffe2, 22}, - /* (186) |11111111|11111111|100011 */ {0x3fffe3, 22}, - /* (187) |11111111|11111111|100100 */ {0x3fffe4, 22}, - /* (188) |11111111|11111111|1110000 */ {0x7ffff0, 23}, - /* (189) |11111111|11111111|100101 */ {0x3fffe5, 22}, - /* (190) |11111111|11111111|100110 */ {0x3fffe6, 22}, - /* (191) |11111111|11111111|1110001 */ {0x7ffff1, 23}, - /* (192) |11111111|11111111|11111000|00 */ {0x3ffffe0, 26}, - /* (193) |11111111|11111111|11111000|01 */ {0x3ffffe1, 26}, - /* (194) |11111111|11111110|1011 */ {0xfffeb, 20}, - /* (195) |11111111|11111110|001 */ {0x7fff1, 19}, - /* (196) |11111111|11111111|100111 */ {0x3fffe7, 22}, - /* (197) |11111111|11111111|1110010 */ {0x7ffff2, 23}, - /* (198) |11111111|11111111|101000 */ {0x3fffe8, 22}, - /* (199) |11111111|11111111|11110110|0 */ {0x1ffffec, 25}, - /* (200) |11111111|11111111|11111000|10 */ {0x3ffffe2, 26}, - /* (201) |11111111|11111111|11111000|11 */ {0x3ffffe3, 26}, - /* (202) |11111111|11111111|11111001|00 */ {0x3ffffe4, 26}, - /* (203) |11111111|11111111|11111011|110 */ {0x7ffffde, 27}, - /* (204) |11111111|11111111|11111011|111 */ {0x7ffffdf, 27}, - /* (205) |11111111|11111111|11111001|01 */ {0x3ffffe5, 26}, - /* (206) |11111111|11111111|11110001 */ {0xfffff1, 24}, - /* (207) |11111111|11111111|11110110|1 */ {0x1ffffed, 25}, - /* (208) |11111111|11111110|010 */ {0x7fff2, 19}, - /* (209) |11111111|11111111|00011 */ {0x1fffe3, 21}, - /* (210) |11111111|11111111|11111001|10 */ {0x3ffffe6, 26}, - /* (211) |11111111|11111111|11111100|000 */ {0x7ffffe0, 27}, - /* (212) |11111111|11111111|11111100|001 */ {0x7ffffe1, 27}, - /* (213) |11111111|11111111|11111001|11 */ {0x3ffffe7, 26}, - /* (214) |11111111|11111111|11111100|010 */ {0x7ffffe2, 27}, - /* (215) |11111111|11111111|11110010 */ {0xfffff2, 24}, - /* (216) |11111111|11111111|00100 */ {0x1fffe4, 21}, - /* (217) |11111111|11111111|00101 */ {0x1fffe5, 21}, - /* (218) |11111111|11111111|11111010|00 */ {0x3ffffe8, 26}, - /* (219) |11111111|11111111|11111010|01 */ {0x3ffffe9, 26}, - /* (220) |11111111|11111111|11111111|1101 */ {0xffffffd, 28}, - /* (221) |11111111|11111111|11111100|011 */ {0x7ffffe3, 27}, - /* (222) |11111111|11111111|11111100|100 */ {0x7ffffe4, 27}, - /* (223) |11111111|11111111|11111100|101 */ {0x7ffffe5, 27}, - /* (224) |11111111|11111110|1100 */ {0xfffec, 20}, - /* (225) |11111111|11111111|11110011 */ {0xfffff3, 24}, - /* (226) |11111111|11111110|1101 */ {0xfffed, 20}, - /* (227) |11111111|11111111|00110 */ {0x1fffe6, 21}, - /* (228) |11111111|11111111|101001 */ {0x3fffe9, 22}, - /* (229) |11111111|11111111|00111 */ {0x1fffe7, 21}, - /* (230) |11111111|11111111|01000 */ {0x1fffe8, 21}, - /* (231) |11111111|11111111|1110011 */ {0x7ffff3, 23}, - /* (232) |11111111|11111111|101010 */ {0x3fffea, 22}, - /* (233) |11111111|11111111|101011 */ {0x3fffeb, 22}, - /* (234) |11111111|11111111|11110111|0 */ {0x1ffffee, 25}, - /* (235) |11111111|11111111|11110111|1 */ {0x1ffffef, 25}, - /* (236) |11111111|11111111|11110100 */ {0xfffff4, 24}, - /* (237) |11111111|11111111|11110101 */ {0xfffff5, 24}, - /* (238) |11111111|11111111|11111010|10 */ {0x3ffffea, 26}, - /* (239) |11111111|11111111|1110100 */ {0x7ffff4, 23}, - /* (240) |11111111|11111111|11111010|11 */ {0x3ffffeb, 26}, - /* (241) |11111111|11111111|11111100|110 */ {0x7ffffe6, 27}, - /* (242) |11111111|11111111|11111011|00 */ {0x3ffffec, 26}, - /* (243) |11111111|11111111|11111011|01 */ {0x3ffffed, 26}, - /* (244) |11111111|11111111|11111100|111 */ {0x7ffffe7, 27}, - /* (245) |11111111|11111111|11111101|000 */ {0x7ffffe8, 27}, - /* (246) |11111111|11111111|11111101|001 */ {0x7ffffe9, 27}, - /* (247) |11111111|11111111|11111101|010 */ {0x7ffffea, 27}, - /* (248) |11111111|11111111|11111101|011 */ {0x7ffffeb, 27}, - /* (249) |11111111|11111111|11111111|1110 */ {0xffffffe, 28}, - /* (250) |11111111|11111111|11111101|100 */ {0x7ffffec, 27}, - /* (251) |11111111|11111111|11111101|101 */ {0x7ffffed, 27}, - /* (252) |11111111|11111111|11111101|110 */ {0x7ffffee, 27}, - /* (253) |11111111|11111111|11111101|111 */ {0x7ffffef, 27}, - /* (254) |11111111|11111111|11111110|000 */ {0x7fffff0, 27}, - /* (255) |11111111|11111111|11111011|10 */ {0x3ffffee, 26}, - /*EOS (256) |11111111|11111111|11111111|111111 */ {0x3fffffff, 30} - }; - - static final int[][] LCCODES = new int[CODES.length][]; - static final char EOS = 256; - - // Huffman decode tree stored in a flattened char array for good - // locality of reference. - static final char[] tree; - static final char[] rowsym; - static final byte[] rowbits; - - // Build the Huffman lookup tree and LC TABLE - static { - System.arraycopy(CODES, 0, LCCODES, 0, CODES.length); - for (int i = 'A'; i <= 'Z'; i++) { - LCCODES[i] = LCCODES['a' + i - 'A']; - } - - int r = 0; - for (int i = 0; i < CODES.length; i++) { - r += (CODES[i][1] + 7) / 8; - } - tree = new char[r * 256]; - rowsym = new char[r]; - rowbits = new byte[r]; - - r = 0; - for (int sym = 0; sym < CODES.length; sym++) { - int code = CODES[sym][0]; - int len = CODES[sym][1]; - - int current = 0; - - while (len > 8) { - len -= 8; - int i = ((code >>> len) & 0xFF); - - int t = current * 256 + i; - current = tree[t]; - if (current == 0) { - tree[t] = (char) ++r; - current = r; - } - } - - int terminal = ++r; - rowsym[r] = (char) sym; - int b = len & 0x07; - int terminalBits = b == 0 ? 8 : b; - - rowbits[r] = (byte) terminalBits; - int shift = 8 - len; - int start = current * 256 + ((code << shift) & 0xFF); - int end = start + (1 << shift); - for (int i = start; i < end; i++) { - tree[i] = (char) terminal; - } - } - } - - public static String decode(ByteBuffer buffer) throws HpackException.CompressionException { - return decode(buffer, buffer.remaining()); - } - - public static String decode(ByteBuffer buffer, int length) throws HpackException.CompressionException { - Utf8StringBuilder utf8 = new Utf8StringBuilder(length * 2); - int node = 0; - int current = 0; - int bits = 0; - - for (int i = 0; i < length; i++) { - int b = buffer.get() & 0xFF; - current = (current << 8) | b; - bits += 8; - while (bits >= 8) { - int c = (current >>> (bits - 8)) & 0xFF; - node = tree[node * 256 + c]; - if (rowbits[node] != 0) { - if (rowsym[node] == EOS) - throw new HpackException.CompressionException("EOS in content"); - - // terminal node - utf8.append((byte) (0xFF & rowsym[node])); - bits -= rowbits[node]; - node = 0; - } else { - // non-terminal node - bits -= 8; - } - } - } - - while (bits > 0) { - int c = (current << (8 - bits)) & 0xFF; - int lastNode = node; - node = tree[node * 256 + c]; - - if (rowbits[node] == 0 || rowbits[node] > bits) { - int requiredPadding = 0; - for (int i = 0; i < bits; i++) { - requiredPadding = (requiredPadding << 1) | 1; - } - - if ((c >> (8 - bits)) != requiredPadding) - throw new HpackException.CompressionException("Incorrect padding"); - - node = lastNode; - break; - } - - utf8.append((byte) (0xFF & rowsym[node])); - bits -= rowbits[node]; - node = 0; - } - - if (node != 0) - throw new HpackException.CompressionException("Bad termination"); - - return utf8.toString(); - } - - public static int octetsNeeded(String s) { - return octetsNeeded(CODES, s); - } - - public static int octetsNeeded(byte[] b) { - return octetsNeeded(CODES, b); - } - - public static void encode(ByteBuffer buffer, String s) { - encode(CODES, buffer, s); - } - - public static void encode(ByteBuffer buffer, byte[] b) { - encode(CODES, buffer, b); - } - - public static int octetsNeededLC(String s) { - return octetsNeeded(LCCODES, s); - } - - public static void encodeLC(ByteBuffer buffer, String s) { - encode(LCCODES, buffer, s); - } - - private static int octetsNeeded(final int[][] table, String s) { - int needed = 0; - int len = s.length(); - for (int i = 0; i < len; i++) { - char c = s.charAt(i); - if (c >= 128 || c < ' ') - return -1; - needed += table[c][1]; - } - - return (needed + 7) / 8; - } - - private static int octetsNeeded(final int[][] table, byte[] b) { - int needed = 0; - int len = b.length; - for (int i = 0; i < len; i++) { - int c = 0xFF & b[i]; - needed += table[c][1]; - } - return (needed + 7) / 8; - } - - /** - * @param table The table to encode by - * @param buffer The buffer to encode to - * @param s The string to encode - */ - private static void encode(final int[][] table, ByteBuffer buffer, String s) { - long current = 0; - int n = 0; - int len = s.length(); - for (int i = 0; i < len; i++) { - char c = s.charAt(i); - if (c >= 128 || c < ' ') - throw new IllegalArgumentException(); - int code = table[c][0]; - int bits = table[c][1]; - - current <<= bits; - current |= code; - n += bits; - - while (n >= 8) { - n -= 8; - buffer.put((byte) (current >> n)); - } - } - - if (n > 0) { - current <<= (8 - n); - current |= (0xFF >>> n); - buffer.put((byte) (current)); - } - } - - private static void encode(final int[][] table, ByteBuffer buffer, byte[] b) { - long current = 0; - int n = 0; - - int len = b.length; - for (int i = 0; i < len; i++) { - int c = 0xFF & b[i]; - int code = table[c][0]; - int bits = table[c][1]; - - current <<= bits; - current |= code; - n += bits; - - while (n >= 8) { - n -= 8; - buffer.put((byte) (current >> n)); - } - } - - if (n > 0) { - current <<= (8 - n); - current |= (0xFF >>> n); - buffer.put((byte) (current)); - } - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/MetaDataBuilder.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/MetaDataBuilder.java deleted file mode 100644 index 563c2d590..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/MetaDataBuilder.java +++ /dev/null @@ -1,238 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.net.http.common.model.*; - -public class MetaDataBuilder { - private final int maxSize; - private int size; - private Integer status; - private String method; - private HttpScheme scheme; - private HostPortHttpField authority; - private String path; - private long contentLength = Long.MIN_VALUE; - private HttpFields fields = new HttpFields(); - private HpackException.StreamException streamException; - private boolean request; - private boolean response; - - /** - * @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters. - */ - protected MetaDataBuilder(int maxHeadersSize) { - maxSize = maxHeadersSize; - } - - /** - * Get the maxSize. - * - * @return the maxSize - */ - public int getMaxSize() { - return maxSize; - } - - /** - * Get the size. - * - * @return the current size in bytes - */ - public int getSize() { - return size; - } - - public void emit(HttpField field) throws HpackException.SessionException { - HttpHeader header = field.getHeader(); - String name = field.getName(); - if (name == null || name.length() == 0) - throw new HpackException.SessionException("Header size 0"); - String value = field.getValue(); - int fieldSize = name.length() + (value == null ? 0 : value.length()); - size += fieldSize + 32; - if (size > maxSize) - throw new HpackException.SessionException("Header size %d > %d", size, maxSize); - - if (field instanceof StaticTableHttpField) { - StaticTableHttpField staticField = (StaticTableHttpField) field; - switch (header) { - case C_STATUS: - if (checkPseudoHeader(header, status)) - status = (Integer) staticField.getStaticValue(); - response = true; - break; - - case C_METHOD: - if (checkPseudoHeader(header, method)) - method = value; - request = true; - break; - - case C_SCHEME: - if (checkPseudoHeader(header, scheme)) - scheme = (HttpScheme) staticField.getStaticValue(); - request = true; - break; - - default: - throw new IllegalArgumentException(name); - } - } else if (header != null) { - switch (header) { - case C_STATUS: - if (checkPseudoHeader(header, status)) - status = field.getIntValue(); - response = true; - break; - - case C_METHOD: - if (checkPseudoHeader(header, method)) - method = value; - request = true; - break; - - case C_SCHEME: - if (checkPseudoHeader(header, scheme) && value != null) - scheme = HttpScheme.from(value); - request = true; - break; - - case C_AUTHORITY: - if (checkPseudoHeader(header, authority)) { - if (field instanceof HostPortHttpField) - authority = (HostPortHttpField) field; - else if (value != null) - authority = new AuthorityHttpField(value); - } - request = true; - break; - - case HOST: - // :authority fields must come first. If we have one, ignore the host header as far as authority goes. - if (authority == null) { - if (field instanceof HostPortHttpField) - authority = (HostPortHttpField) field; - else if (value != null) - authority = new AuthorityHttpField(value); - } - fields.add(field); - break; - - case C_PATH: - if (checkPseudoHeader(header, path)) { - if (value != null && value.length() > 0) - path = value; - else - streamException("No Path"); - } - request = true; - break; - - case CONTENT_LENGTH: - contentLength = field.getLongValue(); - fields.add(field); - break; - - case TE: - if ("trailers".equalsIgnoreCase(value)) - fields.add(field); - else - streamException("Unsupported TE value '%s'", value); - break; - - case CONNECTION: - if ("TE".equalsIgnoreCase(value)) - fields.add(field); - else - streamException("Connection specific field '%s'", header); - break; - - default: - if (name.charAt(0) == ':') - streamException("Unknown pseudo header '%s'", name); - else - fields.add(field); - break; - } - } else { - if (name.charAt(0) == ':') - streamException("Unknown pseudo header '%s'", name); - else - fields.add(field); - } - } - - protected void streamException(String messageFormat, Object... args) { - HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args); - if (streamException == null) - streamException = stream; - else - streamException.addSuppressed(stream); - } - - protected boolean checkPseudoHeader(HttpHeader header, Object value) { - if (fields.size() > 0) { - streamException("Pseudo header %s after fields", header.getValue()); - return false; - } - if (value == null) - return true; - streamException("Duplicate pseudo header %s", header.getValue()); - return false; - } - - public MetaData build() throws HpackException.StreamException { - if (streamException != null) { - streamException.addSuppressed(new Throwable()); - throw streamException; - } - - if (request && response) - throw new HpackException.StreamException("Request and Response headers"); - - HttpFields fields = this.fields; - try { - if (request) { - if (method == null) - throw new HpackException.StreamException("No Method"); - if (scheme == null) - throw new HpackException.StreamException("No Scheme"); - if (path == null) - throw new HpackException.StreamException("No Path"); - return new MetaData.Request(method, scheme, authority, path, HttpVersion.HTTP_2, fields, contentLength); - } - if (response) { - if (status == null) - throw new HpackException.StreamException("No Status"); - return new MetaData.Response(HttpVersion.HTTP_2, status, fields, contentLength); - } - - return new MetaData(HttpVersion.HTTP_2, fields, contentLength); - } finally { - this.fields = new HttpFields(Math.max(16, fields.size() + 5)); - request = false; - response = false; - status = null; - method = null; - scheme = null; - authority = null; - path = null; - size = 0; - contentLength = Long.MIN_VALUE; - } - } - - /** - * Check that the max size will not be exceeded. - * - * @param length the length - * @param huffman the huffman name - * @throws HpackException.SessionException in case of size errors - */ - public void checkSize(int length, boolean huffman) throws HpackException.SessionException { - // Apply a huffman fudge factor - if (huffman) - length = (length * 4) / 3; - if ((size + length) > maxSize) - throw new HpackException.SessionException("Header too large %d > %d", size + length, maxSize); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/NBitInteger.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/NBitInteger.java deleted file mode 100644 index 085be099d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/NBitInteger.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import java.nio.ByteBuffer; - -public class NBitInteger { - public static int octectsNeeded(int n, int i) { - if (n == 8) { - int nbits = 0xFF; - i = i - nbits; - if (i < 0) - return 1; - if (i == 0) - return 2; - int lz = Integer.numberOfLeadingZeros(i); - int log = 32 - lz; - return 1 + (log + 6) / 7; - } - - int nbits = 0xFF >>> (8 - n); - i = i - nbits; - if (i < 0) - return 0; - if (i == 0) - return 1; - int lz = Integer.numberOfLeadingZeros(i); - int log = 32 - lz; - return (log + 6) / 7; - } - - public static void encode(ByteBuffer buf, int n, int i) { - if (n == 8) { - if (i < 0xFF) { - buf.put((byte) i); - } else { - buf.put((byte) 0xFF); - - int length = i - 0xFF; - while (true) { - if ((length & ~0x7F) == 0) { - buf.put((byte) length); - return; - } else { - buf.put((byte) ((length & 0x7F) | 0x80)); - length >>>= 7; - } - } - } - } else { - int p = buf.position() - 1; - int bits = 0xFF >>> (8 - n); - - if (i < bits) { - buf.put(p, (byte) ((buf.get(p) & ~bits) | i)); - } else { - buf.put(p, (byte) (buf.get(p) | bits)); - - int length = i - bits; - while (true) { - if ((length & ~0x7F) == 0) { - buf.put((byte) length); - return; - } else { - buf.put((byte) ((length & 0x7F) | 0x80)); - length >>>= 7; - } - } - } - } - } - - public static int decode(ByteBuffer buffer, int n) { - if (n == 8) { - int nbits = 0xFF; - - int i = buffer.get() & 0xff; - - if (i == nbits) { - int m = 1; - int b; - do { - b = 0xff & buffer.get(); - i = i + (b & 127) * m; - m = m * 128; - } while ((b & 128) == 128); - } - return i; - } - - int nbits = 0xFF >>> (8 - n); - - int i = buffer.get(buffer.position() - 1) & nbits; - - if (i == nbits) { - int m = 1; - int b; - do { - b = 0xff & buffer.get(); - i = i + (b & 127) * m; - m = m * 128; - } while ((b & 128) == 128); - } - return i; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/StaticTableHttpField.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/StaticTableHttpField.java deleted file mode 100644 index c50b8bb47..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/hpack/StaticTableHttpField.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpHeader; - -public class StaticTableHttpField extends HttpField { - private final Object value; - - public StaticTableHttpField(HttpHeader header, String name, - String valueString, Object value) { - super(header, name, valueString); - if (value == null) - throw new IllegalArgumentException(); - this.value = value; - } - - public StaticTableHttpField(HttpHeader header, String valueString, - Object value) { - this(header, header.getValue(), valueString, value); - } - - public StaticTableHttpField(String name, String valueString, Object value) { - super(name, valueString); - if (value == null) - throw new IllegalArgumentException(); - this.value = value; - } - - public Object getStaticValue() { - return value; - } - - @Override - public String toString() { - return super.toString() + "(evaluated)"; - } -} \ No newline at end of file diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/FlowControl.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/FlowControl.java deleted file mode 100644 index e3971d7ba..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/FlowControl.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream; - -import com.fireflysource.net.http.common.v2.frame.WindowUpdateFrame; - -public interface FlowControl { - - void onStreamCreated(Stream stream); - - void onStreamDestroyed(Stream stream); - - void updateInitialStreamWindow(Http2Connection http2Connection, int initialStreamWindow, boolean local); - - void onWindowUpdate(Http2Connection http2Connection, Stream stream, WindowUpdateFrame frame); - - void onDataReceived(Http2Connection http2Connection, Stream stream, int length); - - void onDataConsumed(Http2Connection http2Connection, Stream stream, int length); - - void windowUpdate(Http2Connection http2Connection, Stream stream, WindowUpdateFrame frame); - - void onDataSending(Stream stream, int length); - - void onDataSent(Stream stream, int length); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/Http2Connection.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/Http2Connection.java deleted file mode 100644 index e1ec618d2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/Http2Connection.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream; - -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.http.common.HttpConnection; -import com.fireflysource.net.http.common.v2.frame.*; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - *

    A {@link Http2Connection} represents the client-side endpoint of an HTTP/2 connection to a single origin server.

    - *

    Once a {@link Http2Connection} has been obtained, it can be used to open HTTP/2 streams:

    - *

    A {@link Http2Connection} is the active part of the endpoint, and by calling its API applications can generate - * events on the connection; conversely {@link Http2Connection.Listener} is the passive part of the endpoint, and - * has results that are invoked when events happen on the connection.

    - * - * @see Http2Connection.Listener - */ -public interface Http2Connection extends HttpConnection { - - /** - *

    Sends the given HEADERS {@code frame} to create a new {@link Stream}.

    - * - * @param frame The HEADERS frame containing the HTTP headers - * @param promise The promise that gets notified of the stream creation - * @param listener The listener that gets notified of stream events - */ - void newStream(HeadersFrame frame, Consumer> promise, Stream.Listener listener); - - /** - *

    Sends the given HEADERS {@code frame} to create a new {@link Stream}.

    - * - * @param frame The HEADERS frame containing the HTTP headers. - * @param listener The listener that gets notified of stream events. - * @return The future which gets notified of the stream creation. - */ - default CompletableFuture newStream(HeadersFrame frame, Stream.Listener listener) { - CompletableFuture future = new CompletableFuture<>(); - newStream(frame, Result.futureToConsumer(future), listener); - return future; - } - - /** - *

    Sends the given PRIORITY {@code frame}.

    - *

    If the {@code frame} references a {@code streamId} that does not exist - * (for example {@code 0}), then a new {@code streamId} will be allocated, to - * support unused anchor streams that act as parent for other streams.

    - * - * @param frame The PRIORITY frame to send - * @param result The result that gets notified when the frame has been sent - * @return The new stream id generated by the PRIORITY frame, or the stream id - * that it is already referencing - */ - int priority(PriorityFrame frame, Consumer> result); - - /** - *

    Sends the given SETTINGS {@code frame} to configure the http2Connection.

    - * - * @param frame The SETTINGS frame to send - * @param result The result that gets notified when the frame has been sent - */ - void settings(SettingsFrame frame, Consumer> result); - - /** - *

    Sends the given PING {@code frame}.

    - *

    PING frames may use to test the connection integrity and to measure - * round-trip time.

    - * - * @param frame The PING frame to send - * @param result The result that gets notified when the frame has been sent - */ - void ping(PingFrame frame, Consumer> result); - - /** - *

    Closes the http2Connection by sending a GOAWAY frame with the given error code - * and payload.

    - *

    The GOAWAY frame is sent only once; subsequent or concurrent attempts to - * close the http2Connection will have no effect.

    - * - * @param error The error code - * @param payload An optional payload (maybe null) - * @param result The result that gets notified when the frame has been sent - * @return True if the frame sent, false if the http2Connection was already closed - */ - boolean close(int error, String payload, Consumer> result); - - /** - * @return Whether the http2Connection is not open - */ - boolean isClosed(); - - /** - * @return A snapshot of all the streams currently belonging to this http2Connection - */ - Collection getStreams(); - - /** - *

    Retrieves the stream with the given {@code streamId}.

    - * - * @param streamId The stream id of the stream looked for - * @return The stream with the given id, or null if no such stream exist - */ - Stream getStream(int streamId); - - /** - *

    A {@link Listener} is the passive counterpart of a {@link Http2Connection} and - * receives events happening on an HTTP/2 connection.

    - * - * @see Http2Connection - */ - interface Listener { - /** - *

    Consumer> method invoked:

    - *
      - *
    • for clients, just before the preface sent, to gather the - * SETTINGS configuration options the client wants to send to the server;
    • - *
    • for servers, just after having received the preface, to gather - * the SETTINGS configuration options the server wants to send to the - * client.
    • - *
    - * - * @param http2Connection The http2Connection - * @return A (possibly empty or null) map containing SETTINGS configuration - * options to send. - */ - Map onPreface(Http2Connection http2Connection); - - /** - *

    Consumer> method invoked when a new stream created upon - * receiving a HEADERS frame representing an HTTP request.

    - *

    Applications should implement this method to process HTTP requests, - * typically providing an HTTP response via - * {@link Stream#headers(HeadersFrame, Consumer>)}.

    - *

    Applications can detect whether request DATA frames will be arriving - * by testing {@link HeadersFrame#isEndStream()}. If the application is - * interested in processing the DATA frames, it must return a - * {@link Stream.Listener} implementation that overrides - * {@link Stream.Listener#onData(Stream, DataFrame, Consumer>)}.

    - * - * @param stream The newly created stream - * @param frame The HEADERS frame received - * @return A {@link Stream.Listener} that will be notified of stream events - */ - Stream.Listener onNewStream(Stream stream, HeadersFrame frame); - - /** - *

    Consumer> method invoked when a SETTINGS frame has been received.

    - * - * @param http2Connection The http2Connection - * @param frame The SETTINGS frame received - */ - void onSettings(Http2Connection http2Connection, SettingsFrame frame); - - /** - *

    Consumer> method invoked when a PING frame has been received.

    - * - * @param http2Connection The http2Connection - * @param frame The PING frame received - */ - void onPing(Http2Connection http2Connection, PingFrame frame); - - /** - *

    Consumer> method invoked when an RST_STREAM frame has been received for an unknown stream.

    - * - * @param http2Connection The http2Connection - * @param frame The RST_STREAM frame received - * @see Stream.Listener#onReset(Stream, ResetFrame) - */ - void onReset(Http2Connection http2Connection, ResetFrame frame); - - /** - *

    Consumer> method invoked when a GOAWAY frame has been received.

    - * - * @param http2Connection The http2Connection - * @param frame The GOAWAY frame received - * @param result The result to notify of the GOAWAY processing - */ - default void onClose(Http2Connection http2Connection, GoAwayFrame frame, Consumer> result) { - try { - onClose(http2Connection, frame); - result.accept(Result.SUCCESS); - } catch (Throwable x) { - result.accept(Result.createFailedResult(x)); - } - } - - void onClose(Http2Connection http2Connection, GoAwayFrame frame); - - /** - *

    Consumer> method invoked when a failure has been detected for this http2Connection.

    - * - * @param http2Connection The http2Connection - * @param failure The failure - * @param result The result to notify of failure processing - */ - default void onFailure(Http2Connection http2Connection, Throwable failure, Consumer> result) { - try { - onFailure(http2Connection, failure); - result.accept(Result.SUCCESS); - } catch (Throwable x) { - result.accept(Result.createFailedResult(x)); - } - } - - void onFailure(Http2Connection http2Connection, Throwable failure); - - /** - *

    Empty implementation of {@link Stream.Listener}.

    - */ - class Adapter implements Http2Connection.Listener { - @Override - public Map onPreface(Http2Connection http2Connection) { - return null; - } - - @Override - public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - return null; - } - - @Override - public void onSettings(Http2Connection http2Connection, SettingsFrame frame) { - } - - @Override - public void onPing(Http2Connection http2Connection, PingFrame frame) { - } - - @Override - public void onReset(Http2Connection http2Connection, ResetFrame frame) { - } - - @Override - public void onClose(Http2Connection http2Connection, GoAwayFrame frame) { - } - - @Override - public void onFailure(Http2Connection http2Connection, Throwable failure) { - } - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/Stream.java b/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/Stream.java deleted file mode 100644 index 6759eeb6a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/common/v2/stream/Stream.java +++ /dev/null @@ -1,282 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream; - -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.http.common.v2.frame.DataFrame; -import com.fireflysource.net.http.common.v2.frame.HeadersFrame; -import com.fireflysource.net.http.common.v2.frame.PushPromiseFrame; -import com.fireflysource.net.http.common.v2.frame.ResetFrame; - -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - *

    A {@link Stream} represents a bidirectional exchange of data on top of a {@link Http2Connection}.

    - *

    Differently from socket streams, where the input and output streams permanently associated - * with the socket (and hence with the connection that the socket represents), there can be multiple - * HTTP/2 streams present concurrent for an HTTP/2 session.

    - *

    A {@link Stream} maps to an HTTP request/response cycle, and after the request/response cycle completed, - * the stream closed and removed from the session.

    - *

    Like {@link Http2Connection}, {@link Stream} is the active part and by calling its API applications - * can generate events on the stream; conversely, {@link Stream.Listener} is the passive part, and - * its results invoked when events happen on the stream.

    - * - * @see Stream.Listener - */ -public interface Stream { - /** - * @return the stream's unique id - */ - int getId(); - - /** - * @return the HTTP 2 connection this stream associated to. - */ - Http2Connection getHttp2Connection(); - - /** - *

    Sends the given HEADERS {@code frame} representing an HTTP response.

    - * - * @param frame The HEADERS frame to send. - * @param result The result that gets notified when the frame has been sent. - */ - void headers(HeadersFrame frame, Consumer> result); - - /** - *

    Sends the given HEADERS {@code frame} representing an HTTP response.

    - * - * @param frame The HEADERS frame to send. - * @return The result that gets notified when the frame has been sent. - */ - default CompletableFuture headers(HeadersFrame frame) { - CompletableFuture future = new CompletableFuture<>(); - headers(frame, Result.futureToConsumer(future)); - return future; - } - - /** - *

    Sends the given PUSH_PROMISE {@code frame}.

    - * - * @param frame The PUSH_PROMISE frame to send. - * @param promise The promise that gets notified of the pushed stream creation. - * @param listener The listener that gets notified of stream events. - */ - void push(PushPromiseFrame frame, Consumer> promise, Listener listener); - - /** - *

    Sends the given PUSH_PROMISE {@code frame}.

    - * - * @param frame The PUSH_PROMISE frame to send. - * @param listener The listener that gets notified of stream events. - * @return The future which gets notified of the pushed stream creation. - */ - default CompletableFuture push(PushPromiseFrame frame, Listener listener) { - CompletableFuture future = new CompletableFuture<>(); - push(frame, Result.futureToConsumer(future), listener); - return future; - } - - /** - *

    Sends the given DATA {@code frame}.

    - * - * @param frame The DATA frame to send. - * @param result The result that gets notified when the frame has been sent. - */ - void data(DataFrame frame, Consumer> result); - - /** - *

    Sends the given DATA {@code frame}.

    - * - * @param frame The DATA frame to send. - * @return The result that gets notified when the frame has been sent. - */ - default CompletableFuture data(DataFrame frame) { - CompletableFuture future = new CompletableFuture<>(); - data(frame, Result.futureToConsumer(future)); - return future; - } - - /** - *

    Sends the given RST_STREAM {@code frame}.

    - * - * @param frame The RST_FRAME to send. - * @param result The result that gets notified when the frame has been sent. - */ - void reset(ResetFrame frame, Consumer> result); - - /** - * @param key the attribute key. - * @return An object associated with the given key to this stream. - * or null if no object can be found for the given key. - * @see #setAttribute(String, Object) - */ - Object getAttribute(String key); - - /** - * @param key The attribute key. - * @param value An arbitrary object to associate with the given key to this stream. - * @see #getAttribute(String) - * @see #removeAttribute(String) - */ - void setAttribute(String key, Object value); - - /** - * @param key The attribute key. - * @return The object associated with the given key to this stream. - * @see #setAttribute(String, Object) - */ - Object removeAttribute(String key); - - /** - * @return If true this stream has been reset. - */ - boolean isReset(); - - /** - * @return If true this stream closed, both locally and remotely. - */ - boolean isClosed(); - - /** - * @return The stream idle timeout. - * @see #setIdleTimeout(long) - */ - long getIdleTimeout(); - - /** - * @param idleTimeout The stream idle timeout. - * @see #getIdleTimeout() - * @see Stream.Listener#onIdleTimeout(Stream, Throwable) - */ - void setIdleTimeout(long idleTimeout); - - /** - *

    A {@link Stream.Listener} is the passive counterpart of a {@link Stream} and receives - * events happening on an HTTP/2 stream.

    - * - * @see Stream - */ - interface Listener { - /** - *

    Callback method invoked when a HEADERS frame representing the HTTP response has been received.

    - * - * @param stream The stream. - * @param frame The HEADERS frame received. - */ - void onHeaders(Stream stream, HeadersFrame frame); - - /** - *

    Callback method invoked when a PUSH_PROMISE frame has been received.

    - * - * @param stream The stream. - * @param frame The PUSH_PROMISE frame received. - * @return A Stream.Listener that will be notified of pushed stream events. - */ - Listener onPush(Stream stream, PushPromiseFrame frame); - - /** - *

    Callback method invoked when a DATA frame has been received.

    - * - * @param stream The stream. - * @param frame The DATA frame received. - * @param result The result to complete when the bytes of the DATA frame have been consumed. - */ - void onData(Stream stream, DataFrame frame, Consumer> result); - - /** - *

    Callback method invoked when an RST_STREAM frame has been received for this stream.

    - * - * @param stream The stream. - * @param frame The RST_FRAME received. - * @param result The result to complete when the reset has been handled. - */ - default void onReset(Stream stream, ResetFrame frame, Consumer> result) { - try { - onReset(stream, frame); - result.accept(Result.SUCCESS); - } catch (Throwable x) { - result.accept(Result.createFailedResult(x)); - } - } - - /** - *

    Callback method invoked when an RST_STREAM frame has been received for this stream.

    - * - * @param stream The stream. - * @param frame The RST_FRAME received. - * @see Http2Connection.Listener#onReset(Http2Connection, ResetFrame) - */ - default void onReset(Stream stream, ResetFrame frame) { - } - - - /** - *

    Callback method invoked when the stream exceeds its idle timeout.

    - * - * @param stream The stream. - * @param x The timeout failure. - * @return If true to reset the stream, false to ignore the idle timeout. - * @see #getIdleTimeout() - */ - default boolean onIdleTimeout(Stream stream, Throwable x) { - return true; - } - - /** - *

    Callback method invoked when the stream failed.

    - * - * @param stream The stream. - * @param error An error code. - * @param reason An error reason, or null. - * @param result The result to complete when the failure has been handled. - */ - default void onFailure(Stream stream, int error, String reason, Consumer> result) { - result.accept(Result.SUCCESS); - } - - /** - *

    Callback method invoked after the stream has been closed.

    - * - * @param stream The stream. - */ - default void onClosed(Stream stream) { - } - - /** - *

    Callback method invoked after the connection is terminal.

    - * - * @param stream The stream. - */ - default void onTerminal(Stream stream) { - - } - - /** - *

    Empty implementation of {@link Listener}

    - */ - class Adapter implements Listener { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) { - } - - @Override - public Listener onPush(Stream stream, PushPromiseFrame frame) { - return new Adapter(); - } - - @Override - public void onData(Stream stream, DataFrame frame, Consumer> result) { - result.accept(Result.SUCCESS); - } - - @Override - public void onReset(Stream stream, ResetFrame frame) { - } - - @Override - public boolean onIdleTimeout(Stream stream, Throwable x) { - return true; - } - } - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpProxy.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpProxy.java deleted file mode 100644 index cf2af5be2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpProxy.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.lifecycle.LifeCycle; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -public interface HttpProxy extends LifeCycle { - - /** - * Bind a server TCP address - * - * @param address The server TCP address. - */ - void listen(SocketAddress address); - - /** - * Bind the server host and port. - * - * @param host The server host. - * @param port The server port. - */ - default void listen(String host, int port) { - listen(new InetSocketAddress(host, port)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServer.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServer.java deleted file mode 100644 index 30e5acf50..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServer.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.lifecycle.LifeCycle; -import com.fireflysource.net.tcp.TcpConnection; -import com.fireflysource.net.tcp.secure.SecureEngineFactory; -import com.fireflysource.net.websocket.server.WebSocketServerConnectionBuilder; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * @author Pengtao Qiu - */ -public interface HttpServer extends LifeCycle, Cloneable { - - /** - * Register a new router. - * - * @return The router. - */ - Router router(); - - /** - * Register a new router. - * - * @param id The router id. - * @return The router. - */ - Router router(int id); - - /** - * Create a new websocket connection builder. - * - * @return The websocket connection builder. - */ - WebSocketServerConnectionBuilder websocket(); - - /** - * Create a new websocket connection builder. - * - * @param path The websocket url. - * @return The websocket connection builder. - */ - WebSocketServerConnectionBuilder websocket(String path); - - /** - * HTTP headers received callback. - * - * @param function The HTTP headers received callback. - * @return The HTTP server. - */ - HttpServer onHeaderComplete(Function> function); - - /** - * HTTP server exception callback. - * - * @param biFunction The HTTP server exception callback. - * @return The HTTP server. - */ - HttpServer onException(BiFunction> biFunction); - - /** - * The router not found callback. - * - * @param function Invoke this function when the HTTP server does not find the router. - * @return The HTTP server. - */ - HttpServer onRouterNotFound(Function> function); - - /** - * The router complete callback. - * - * @param function Invoke this function when the last router executes successfully. - * @return The HTTP server. - */ - HttpServer onRouterComplete(Function> function); - - /** - * The accept HTTP tunnel callback. - * - * @param function Invoke this function when the server accepts a HTTP tunnel request. - * @return The HTTP server. - */ - HttpServer onAcceptHttpTunnel(Function> function); - - /** - * Accept HTTP tunnel handshake response callback. The default response: HTTP/1.1 200 Connection Established. - * - * @param function Invoke this function after the server accepts a HTTP tunnel request and then the server will response the HTTP tunnel response. - * @return The HTTP server. - */ - HttpServer onAcceptHttpTunnelHandshakeResponse(Function> function); - - /** - * Refuse HTTP tunnel handshake response callback. The default response: HTTP/1.1 407 Proxy Authentication Required. - * - * @param function Invoke this function after the server refuses a HTTP tunnel request and then the server will response the HTTP tunnel response. - * @return The HTTP server. - */ - HttpServer onRefuseHttpTunnelHandshakeResponse(Function> function); - - /** - * The HTTP tunnel handshake complete callback. - * - * @param function Invoke this function when the HTTP tunnel handshake is complete. - * @return The HTTP server. - */ - HttpServer onHttpTunnelHandshakeComplete(BiFunction> function); - - /** - * Set the TLS engine factory. - * - * @param secureEngineFactory The TLS engine factory. - * @return The HTTP server. - */ - HttpServer secureEngineFactory(SecureEngineFactory secureEngineFactory); - - /** - * The supported application layer protocols. - * - * @param supportedProtocols The supported application layer protocols. - * @return The HTTP server. - */ - HttpServer supportedProtocols(List supportedProtocols); - - /** - * Create a TLS engine using advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param peerHost the non-authoritative name of the host. - * @return The HTTP server. - */ - HttpServer peerHost(String peerHost); - - /** - * Create a TLS engine using advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param peerPort the non-authoritative port. - * @return The HTTP server. - */ - HttpServer peerPort(int peerPort); - - /** - * Enable the TLS protocol over the TCP connection. - * - * @return The HTTP server. - */ - HttpServer enableSecureConnection(); - - /** - * Set the TCP idle timeout. The unit is second. - * - * @param timeout The TCP idle timeout. Time unit is second. - * @return The HTTP server. - */ - HttpServer timeout(Long timeout); - - /** - * Bind a server TCP address - * - * @param address The server TCP address. - */ - void listen(SocketAddress address); - - /** - * Bind the server host and port. - * - * @param host The server host. - * @param port The server port. - */ - default void listen(String host, int port) { - listen(new InetSocketAddress(host, port)); - } - - /** - * Copy the HTTP server instance. - * - * @return The new HTTP server instance. - */ - HttpServer copy(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerConnection.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerConnection.java deleted file mode 100644 index a2e2338d4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerConnection.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.http.common.HttpConnection; -import com.fireflysource.net.tcp.TcpConnection; -import com.fireflysource.net.websocket.server.WebSocketServerConnectionHandler; - -import java.net.InetSocketAddress; -import java.util.concurrent.CompletableFuture; - -/** - * The HTTP server connection. - * - * @author Pengtao Qiu - */ -public interface HttpServerConnection extends HttpConnection { - - Listener EMPTY_LISTENER = new Listener.Adapter(); - - /** - * Set HTTP server connection event listener. It receives the HTTP request or exception events. - * - * @param listener The HTTP server connection event listener. - * @return The HTTP server connection. - */ - HttpServerConnection setListener(Listener listener); - - /** - * Begin to receive HTTP request. - */ - void begin(); - - /** - * The HTTP server connection event listener. - */ - interface Listener { - - /** - * When the all HTTP headers receive, invokes this method. - * - * @param context The routing context. In this stage, the context cannot get the HTTP body. - * @return The future result. - */ - CompletableFuture onHeaderComplete(RoutingContext context); - - /** - * When the HTTP request is complete, invokes this method. it contains headers and body, - * - * @param context The routing context. - * @return The future result. - */ - CompletableFuture onHttpRequestComplete(RoutingContext context); - - /** - * When the connection parses the error HTTP message, invokes this method. - * - * @param context The routing context. The context may be null. - * @param throwable An exception. - * @return The future result. - */ - CompletableFuture onException(RoutingContext context, Throwable throwable); - - /** - * When the server accepts a Websocket handshake request, invokes this method. - * - * @param context The routing context. - * @return The Websocket connection handler. - */ - CompletableFuture onWebSocketHandshake(RoutingContext context); - - /** - * When the server accepts an HTTP tunnel request, invokes this method. - * - * @param request The HTTP request. - * @return If true, create an HTTP tunnel connection. - */ - CompletableFuture onAcceptHttpTunnel(HttpServerRequest request); - - /** - * After the server accepts a HTTP tunnel request and then the server will response the HTTP tunnel response, invokes this method. - * - * @param context The routing context. - * @return The future result. - */ - CompletableFuture onAcceptHttpTunnelHandshakeResponse(RoutingContext context); - - /** - * After the server refuses a HTTP tunnel request and then the server will response the HTTP tunnel response, invokes this method. - * - * @param context The routing context. - * @return The future result. - */ - CompletableFuture onRefuseHttpTunnelHandshakeResponse(RoutingContext context); - - /** - * When the HTTP tunnel handshake is complete, invokes this method. - * - * @param connection The client TCP connection. - * @param address The target address. - * @return The future result. - */ - CompletableFuture onHttpTunnelHandshakeComplete(TcpConnection connection, InetSocketAddress address); - - /** - * The empty listener implement. - */ - class Adapter implements Listener { - - @Override - public CompletableFuture onHeaderComplete(RoutingContext context) { - return Result.DONE; - } - - @Override - public CompletableFuture onHttpRequestComplete(RoutingContext context) { - return Result.DONE; - } - - @Override - public CompletableFuture onException(RoutingContext context, Throwable throwable) { - return Result.DONE; - } - - @Override - public CompletableFuture onWebSocketHandshake(RoutingContext context) { - return CompletableFuture.completedFuture(new WebSocketServerConnectionHandler()); - } - - @Override - public CompletableFuture onAcceptHttpTunnel(HttpServerRequest request) { - return CompletableFuture.completedFuture(true); - } - - @Override - public CompletableFuture onAcceptHttpTunnelHandshakeResponse(RoutingContext context) { - return Result.DONE; - } - - @Override - public CompletableFuture onRefuseHttpTunnelHandshakeResponse(RoutingContext context) { - return Result.DONE; - } - - @Override - public CompletableFuture onHttpTunnelHandshakeComplete(TcpConnection connection, InetSocketAddress address) { - return Result.DONE; - } - } - } - - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentHandler.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentHandler.java deleted file mode 100644 index ef8652577..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentHandler.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.net.http.common.content.handler.HttpContentHandler; - -public interface HttpServerContentHandler extends HttpContentHandler { -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentHandlerFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentHandlerFactory.java deleted file mode 100644 index 71a79a28c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentHandlerFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fireflysource.net.http.server; - - -import com.fireflysource.net.http.server.impl.content.handler.ByteBufferContentHandler; -import com.fireflysource.net.http.server.impl.content.handler.FileContentHandler; -import com.fireflysource.net.http.server.impl.content.handler.StringContentHandler; - -import java.nio.file.OpenOption; -import java.nio.file.Path; - -abstract public class HttpServerContentHandlerFactory { - public static HttpServerContentHandler bytesHandler() { - return new ByteBufferContentHandler(); - } - - public static HttpServerContentHandler bytesHandler(long maxRequestBodySize) { - return new ByteBufferContentHandler(maxRequestBodySize); - } - - public static HttpServerContentHandler stringHandler() { - return new StringContentHandler(); - } - - public static HttpServerContentHandler stringHandler(long maxRequestBodySize) { - return new StringContentHandler(maxRequestBodySize); - } - - public static HttpServerContentHandler fileHandler(Path path, OpenOption... openOptions) { - return new FileContentHandler(path, openOptions); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentProvider.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentProvider.java deleted file mode 100644 index fca43d629..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.net.http.common.content.provider.HttpContentProvider; - -import java.nio.ByteBuffer; - -/** - * @author Pengtao Qiu - */ -public interface HttpServerContentProvider extends HttpContentProvider { - - /** - * The content length. If the length is -1, the content is the data stream. - * - * @return The content length. - */ - long length(); - - /** - * Convert fixed length content to a ByteBuffer. If the content is the data stream, return an empty ByteBuffer. - * - * @return The ByteBuffer. - */ - ByteBuffer toByteBuffer(); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentProviderFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentProviderFactory.java deleted file mode 100644 index b68154ca7..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerContentProviderFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.net.http.server.impl.content.provider.ByteBufferContentProvider; -import com.fireflysource.net.http.server.impl.content.provider.FileContentProvider; -import com.fireflysource.net.http.server.impl.content.provider.StringContentProvider; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.util.Set; - -abstract public class HttpServerContentProviderFactory { - - public static HttpServerContentProvider bytesBody(ByteBuffer buffer) { - return new ByteBufferContentProvider(buffer); - } - - public static HttpServerContentProvider stringBody(String string, Charset charset) { - return new StringContentProvider(string, charset); - } - - public static HttpServerContentProvider fileBody(Path path, OpenOption... openOptions) { - return new FileContentProvider(path, openOptions); - } - - public static HttpServerContentProvider fileBody(Path path, Set openOptions, long position, long length) { - return new FileContentProvider(path, openOptions, position, length); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerFactory.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerFactory.java deleted file mode 100644 index 25a4c2183..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.net.http.common.HttpConfig; -import com.fireflysource.net.http.server.impl.AsyncHttpProxy; -import com.fireflysource.net.http.server.impl.AsyncHttpServer; - -abstract public class HttpServerFactory { - - public static HttpServer create(HttpConfig config) { - return new AsyncHttpServer(config); - } - - public static HttpServer create() { - return new AsyncHttpServer(); - } - - public static HttpProxy createHttpProxy() { - return new AsyncHttpProxy(); - } - - public static HttpProxy createHttpProxy(HttpConfig config) { - return new AsyncHttpProxy(config); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerOutputChannel.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerOutputChannel.java deleted file mode 100644 index ba909e4d1..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerOutputChannel.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.io.OutputChannel; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public interface HttpServerOutputChannel extends OutputChannel { - - /** - * Commit the http response. - * - * @return The future result. - */ - CompletableFuture commit(); - - /** - * If true, the http response has committed. - * - * @return If true, the http response has committed. - */ - boolean isCommitted(); - - /** - * Write the message to the remote endpoint. - * - * @param byteBuffers The byte buffer array. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The future result. - */ - CompletableFuture write(ByteBuffer[] byteBuffers, int offset, int length); - - /** - * Write the message to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer list of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @return The future result. - */ - CompletableFuture write(List byteBufferList, int offset, int length); - - /** - * Write the message to the remote endpoint. - * - * @param string The string. - * @return The future result. - */ - CompletableFuture write(String string); - - /** - * Write the message to the remote endpoint. - * - * @param string The string. - * @param charset The charset. - * @return The future result. - */ - CompletableFuture write(String string, Charset charset); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerRequest.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerRequest.java deleted file mode 100644 index ad346a8d8..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerRequest.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.net.http.common.model.Cookie; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpURI; -import com.fireflysource.net.http.common.model.HttpVersion; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -/** - * The HTTP request. - * - * @author Pengtao Qiu - */ -public interface HttpServerRequest { - - /** - * Get the HTTP method. - * - * @return The HTTP method. - */ - String getMethod(); - - /** - * Get the HTTP URI. - * - * @return The HTTP URI. - */ - HttpURI getURI(); - - /** - * Get the HTTP version. - * - * @return The HTTP version. - */ - HttpVersion getHttpVersion(); - - /** - * Get the URL query string. - * - * @param name The URL query parameter name. - * @return The URL query parameter value. - */ - String getQueryString(String name); - - /** - * Get the URL query strings. - * - * @param name The URL query parameter name. - * @return The URL query parameter values. - */ - List getQueryStrings(String name); - - /** - * Get all URL query strings. - * - * @return All URL query strings. - */ - Map> getQueryStrings(); - - /** - * Get the HTTP header fields. - * - * @return The HTTP header fields. - */ - HttpFields getHttpFields(); - - /** - * Get the HTTP cookies. - * - * @return The HTTP cookies. - */ - List getCookies(); - - /** - * Get the content length. - * - * @return The content length. - */ - long getContentLength(); - - /** - * Get the HTTP trailers. - * - * @return The HTTP trailers. - */ - Supplier getTrailerSupplier(); - - /** - * Set the HTTP request is complete. - * - * @param requestComplete If true, the HTTP request is complete. - */ - void setRequestComplete(boolean requestComplete); - - /** - * Get the HTTP request is complete. - * - * @return If true, the HTTP request is complete. - */ - boolean isRequestComplete(); - - /** - * Get the HTTP body and convert it to the UTF-8 string. - * - * @return The HTTP body string. - */ - String getStringBody(); - - /** - * Get the HTTP body and convert the specified charset string. - * - * @param charset The charset of the HTTP body string. - * @return The HTTP body string. - */ - String getStringBody(Charset charset); - - /** - * Get the HTTP body raw binary data. - * - * @return The HTTP body raw binary data. - */ - List getBody(); - - /** - * Get the web form input value. - * - * @param name The form input name. - * @return The value. - */ - String getFormInput(String name); - - /** - * Get the web form input values. - * - * @param name The web form input name. - * @return The values. - */ - List getFormInputs(String name); - - /** - * Get all web form inputs. - * - * @return All web form inputs. - */ - Map> getFormInputs(); - - /** - * Get HTTP request multi-part content. - * - * @param name The part name. - * @return The HTTP request multi-part content. - */ - MultiPart getPart(String name); - - /** - * Get all HTTP request multi-part content. - * - * @return All HTTP request multi-part content. - */ - List getParts(); - - /** - * Get HTTP request content handler. - * - * @return HTTP content handler. - */ - HttpServerContentHandler getContentHandler(); - - /** - * Set HTTP request content handler. - * - * @param contentHandler HTTP request content handler. - */ - void setContentHandler(HttpServerContentHandler contentHandler); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerResponse.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerResponse.java deleted file mode 100644 index 4f735de01..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/HttpServerResponse.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.io.AsyncCloseable; -import com.fireflysource.net.http.common.model.Cookie; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpVersion; - -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - -/** - * The HTTP response. - * - * @author Pengtao Qiu - */ -public interface HttpServerResponse extends AsyncCloseable { - - /** - * Get the HTTP response status code. - * - * @return The HTTP response status code. - */ - int getStatus(); - - /** - * Set the HTTP response status code. - * - * @param status The HTTP response status code. - */ - void setStatus(int status); - - /** - * Get the textual description associated with the numeric status code. - * - * @return The textual description associated with the numeric status code. - */ - String getReason(); - - /** - * Set the textual description associated with the numeric status code. - * - * @param reason The textual description associated with the numeric status code. - */ - void setReason(String reason); - - /** - * Get the HTTP version of the current HTTP connection. - * - * @return The HTTP version of the current HTTP connection. - */ - HttpVersion getHttpVersion(); - - /** - * Set the HTTP version of the current HTTP connection. - * - * @param httpVersion The HTTP version of the current HTTP connection. - */ - void setHttpVersion(HttpVersion httpVersion); - - /** - * Get the HTTP header fields. - * - * @return The HTTP header fields. - */ - HttpFields getHttpFields(); - - /** - * Set the HTTP header fields. - * - * @param httpFields The HTTP header fields. - */ - void setHttpFields(HttpFields httpFields); - - /** - * Get the cookies. - * - * @return The cookies. - */ - List getCookies(); - - /** - * Set the cookies. - * - * @param cookies The cookies. - */ - void setCookies(List cookies); - - /** - * Get the HTTP trailer fields. - * - * @return The HTTP trailer fields. - */ - Supplier getTrailerSupplier(); - - /** - * Set the HTTP trailer fields. - * - * @param supplier The HTTP trailer fields supplier. - */ - void setTrailerSupplier(Supplier supplier); - - /** - * Get the content provider. - * - * @return the content provider. - */ - HttpServerContentProvider getContentProvider(); - - /** - * Set the content provider. When you commit the response, the HTTP server will send the data that read from the content provider. - * If you set content provider after commit response, this method will throw IllegalStateException. - * - * @param contentProvider When you commit the response, the HTTP server will send the data that read from the content provider. - */ - void setContentProvider(HttpServerContentProvider contentProvider); - - /** - * Get the output channel. It can write data to the client. - * If you get output channel before commit response, this method will throw IllegalStateException. - * - * @return The output channel. - */ - HttpServerOutputChannel getOutputChannel(); - - /** - * Commit the response. If you set the content provider, the server will output the data from the content provider, - * or else you can write data using the output channel. - * - * @return The future result. - */ - CompletableFuture commit(); - - /** - * If true, the http response has committed. - * - * @return If true, the http response has committed. - */ - boolean isCommitted(); - - /** - * Response 100 continue. - * - * @return The future result. - */ - CompletableFuture response100Continue(); - - /** - * Response 200 connection established. - * - * @return The future result. - */ - CompletableFuture response200ConnectionEstablished(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/Matcher.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/Matcher.java deleted file mode 100644 index 69a7eba93..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/Matcher.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.fireflysource.net.http.server; - -import java.util.Collections; -import java.util.Map; -import java.util.SortedSet; - -public interface Matcher { - - enum MatchType { - PATH, METHOD, ACCEPT, CONTENT_TYPE - } - - class MatchResult { - private final SortedSet routers; - private final Map> parameters; - private final MatchType matchType; - - public MatchResult(SortedSet routers, Map> parameters, MatchType matchType) { - this.routers = routers; - this.parameters = Collections.unmodifiableMap(parameters); - this.matchType = matchType; - } - - public SortedSet getRouters() { - return routers; - } - - public Map> getParameters() { - return parameters; - } - - public MatchType getMatchType() { - return matchType; - } - } - - void add(String rule, Router router); - - MatchResult match(String value); - - MatchType getMatchType(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/MultiPart.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/MultiPart.java deleted file mode 100644 index b2c795ff2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/MultiPart.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.io.InputChannel; -import com.fireflysource.net.http.common.model.HttpFields; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -public interface MultiPart extends InputChannel { - - /** - * Gets the content type of this part. - * - * @return The content type of this part. - */ - String getContentType(); - - /** - * Gets the name of this part - * - * @return The name of this part as a String - */ - String getName(); - - /** - * Gets the file name - * - * @return The file name. - */ - String getFileName(); - - /** - * Returns the size of this file. - * - * @return a long specifying the size of this part, in bytes. - */ - long getSize(); - - /** - * Get this part headers. - * - * @return This part headers. - */ - HttpFields getHttpFields(); - - /** - * Get string body. If the content length not exceeded file threshold. - * - * @param charset The string charset. - * @return The string body. - */ - String getStringBody(Charset charset); - - /** - * Get string body. If the content length not exceeded file threshold. - * - * @return The string body. - */ - default String getStringBody() { - return getStringBody(StandardCharsets.UTF_8); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/Router.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/Router.java deleted file mode 100644 index 8c37ab55b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/Router.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.common.coroutine.CoroutineDispatchers; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.http.common.model.HttpMethod; - -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; - -public interface Router extends Comparable { - - Handler EMPTY_HANDLER = ctx -> Result.DONE; - - /** - * Get router id. - * - * @return The router id. - */ - int getId(); - - /** - * If true, the router is enable. - * - * @return If true, the router is enable. - */ - boolean isEnable(); - - /** - * Get matched types. - * - * @return The matched types. - */ - Set getMatchTypes(); - - /** - * Bind a URL for this router. - * - * @param url The URL. - * @return router. - */ - Router path(String url); - - /** - * Bind some URLs for this router. - * - * @param urlList The URL list. - * @return router. - */ - Router paths(List urlList); - - /** - * Bind URL using regex. - * - * @param regex The URL regex. - * @return router. - */ - Router pathRegex(String regex); - - /** - * Bind HTTP method. - * - * @param httpMethod The HTTP method. - * @return router. - */ - Router method(String httpMethod); - - /** - * Bind HTTP method. - * - * @param httpMethod The HTTP method. - * @return router. - */ - Router method(HttpMethod httpMethod); - - /** - * Bind get method and URL. - * - * @param url The URL. - * @return router. - */ - Router get(String url); - - /** - * Bind post method and URL. - * - * @param url The URL. - * @return router. - */ - Router post(String url); - - /** - * Bind put method and URL. - * - * @param url The URL. - * @return router. - */ - Router put(String url); - - /** - * Bind delete method and URL. - * - * @param url The URL. - * @return router. - */ - Router delete(String url); - - /** - * Bind the request content type. - * - * @param contentType The request content type. - * @return router. - */ - Router consumes(String contentType); - - /** - * Bind remote accepted content type. - * - * @param accept The remote accepted content type. - * @return router. - */ - Router produces(String accept); - - /** - * Set router handler. When the HTTP server accepted request, and the request match this router, - * the server will call this handler to process request. - * - * @param handler router handler. - * @return The HTTP server. - */ - HttpServer handler(Handler handler); - - /** - * Set router handler. The handler executes thread blocking task on the IO thread pool. - * - * @param consumer The thread blocking router handler. - * @return The HTTP server. - */ - default HttpServer blockingHandler(Consumer consumer) { - return this.handler(ctx -> CompletableFuture.runAsync(() -> consumer.accept(ctx), - CoroutineDispatchers.INSTANCE.getIoBlockingThreadPool())); - } - - /** - * Enable this router. - * - * @return router. - */ - Router enable(); - - /** - * Disable this router. - * - * @return router. - */ - Router disable(); - - interface Handler extends Function> { - - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/RouterManager.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/RouterManager.java deleted file mode 100644 index 9ab7d7425..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/RouterManager.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.http.server; - -import java.util.*; - -public interface RouterManager { - - class RouterMatchResult implements Comparable { - - private final Router router; - private final Map parameters; - private final Set matchTypes; - - public RouterMatchResult(Router router, Map parameters, Set matchTypes) { - this.router = router; - this.parameters = Collections.unmodifiableMap(parameters); - this.matchTypes = Collections.unmodifiableSet(matchTypes); - } - - public Router getRouter() { - return router; - } - - public Map getParameters() { - return parameters; - } - - public Set getMatchTypes() { - return matchTypes; - } - - @Override - public int compareTo(RouterMatchResult o) { - return router.compareTo(o.getRouter()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RouterMatchResult that = (RouterMatchResult) o; - return Objects.equals(router, that.router); - } - - @Override - public int hashCode() { - return Objects.hash(router); - } - } - - /** - * Register a router using automatic increase id. - * - * @return The new router. - */ - Router register(); - - /** - * Register a router. - * - * @param id The router id. - * @return The new router. - */ - Router register(Integer id); - - /** - * Find routers. - * - * @param context The routing context. - * @return The registered routers. - */ - SortedSet findRouters(RoutingContext context); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/http/server/RoutingContext.java b/firefly-net/src/main/java/com/fireflysource/net/http/server/RoutingContext.java deleted file mode 100644 index 5d736da64..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/http/server/RoutingContext.java +++ /dev/null @@ -1,576 +0,0 @@ -package com.fireflysource.net.http.server; - -import com.fireflysource.net.http.common.model.*; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - -/** - * A new routing context instance creates when the server receives an HTTP request. - *

    - * You can visit the RoutingContext instance in the whole router chain. - * It provides HTTP request/response API and allows you to maintain data that lives for the lifetime of the context. - * Contexts discarded once they have been routed to the handler for the request. - *

    - * The context also provides access to the Session, cookies and body for the request, given the correct handlers in the application. - * - * @author Pengtao Qiu - */ -public interface RoutingContext { - - /** - * Get the attribute value. - * - * @param key The attribute key. - * @return The value. - */ - Object getAttribute(String key); - - /** - * Set the attribute value. - * - * @param key The attribute key. - * @param value The value. - * @return The old value if exists. - */ - Object setAttribute(String key, Object value); - - /** - * Remove the value. - * - * @param key The attribute key. - * @return The old value if exists. - */ - Object removeAttribute(String key); - - /** - * Get all attributes. - * - * @return All attributes. - */ - Map getAttributes(); - - /** - * Get HTTP request. - * - * @return The HTTP request. - */ - HttpServerRequest getRequest(); - - /** - * Get HTTP response. - * - * @return The HTTP response. - */ - HttpServerResponse getResponse(); - - - /** - * Get the parameter value. If you bind the parameter name for the path. - * - * @param name The path parameter name. - * @return The value. - */ - String getPathParameter(String name); - - /** - * Get the parameter value. If you bind the wildcard for the path. - * - * @param index The wildcard index. - * @return The value. - */ - String getPathParameter(int index); - - /** - * Get the path parameter by the regex group index. If you register the path using regex. - * - * @param index The regex group index. - * @return The value. - */ - String getPathParameterByRegexGroup(int index); - - /** - * Get the URL query string. - * - * @param name The URL query parameter name. - * @return The URL query parameter value. - */ - default String getQueryString(String name) { - return getRequest().getQueryString(name); - } - - /** - * Get the URL query strings. - * - * @param name The URL query parameter name. - * @return The URL query parameter values. - */ - default List getQueryStrings(String name) { - return getRequest().getQueryStrings(name); - } - - /** - * Get all URL query strings. - * - * @return All URL query strings. - */ - default Map> getQueryStrings() { - return getRequest().getQueryStrings(); - } - - /** - * Get the web form input value. - * - * @param name The form input name. - * @return The value. - */ - default String getFormInput(String name) { - return getRequest().getFormInput(name); - } - - /** - * Get the web form input values. - * - * @param name The web form input name. - * @return The values. - */ - default List getFormInputs(String name) { - return getRequest().getFormInputs(name); - } - - /** - * Get all web form inputs. - * - * @return All web form inputs. - */ - default Map> getFormInputs() { - return getRequest().getFormInputs(); - } - - /** - * Get the HTTP body and convert it to the UTF-8 string. - * - * @return The HTTP body string. - */ - default String getStringBody() { - return getRequest().getStringBody(); - } - - /** - * Get the HTTP body and convert the specified charset string. - * - * @param charset The charset of the HTTP body string. - * @return The HTTP body string. - */ - default String getStringBody(Charset charset) { - return getRequest().getStringBody(charset); - } - - /** - * Get the HTTP body raw binary data. - * - * @return The HTTP body raw binary data. - */ - default List getBody() { - return getRequest().getBody(); - } - - /** - * Get HTTP request multi-part content. - * - * @param name The part name. - * @return The HTTP request multi-part content. - */ - default MultiPart getPart(String name) { - return getRequest().getPart(name); - } - - /** - * Get all HTTP request multi-part content. - * - * @return All HTTP request multi-part content. - */ - default List getParts() { - return getRequest().getParts(); - } - - /** - * Set HTTP request content handler. - * - * @param contentHandler HTTP request content handler. - * @return The routing context. - */ - default RoutingContext contentHandler(HttpServerContentHandler contentHandler) { - getRequest().setContentHandler(contentHandler); - return this; - } - - /** - * Get HTTP request method. - * - * @return The HTTP request method. - */ - default String getMethod() { - return getRequest().getMethod(); - } - - /** - * Get HTTP request URI. - * - * @return The HTTP request URI. - */ - default HttpURI getURI() { - return getRequest().getURI(); - } - - /** - * Get HTTP request version. - * - * @return The HTTP request version. - */ - default HttpVersion getHttpVersion() { - return getRequest().getHttpVersion(); - } - - /** - * Get HTTP request headers. - * - * @return The HTTP request headers. - */ - default HttpFields getHttpFields() { - return getRequest().getHttpFields(); - } - - /** - * The request headers contain expect 100 continue. - * - * @return If true, the headers contain expect 100 continue. - */ - boolean expect100Continue(); - - /** - * Get HTTP request content length. - * - * @return The HTTP request content length. - */ - default long getContentLength() { - return getRequest().getContentLength(); - } - - /** - * Get HTTP request content type. - * - * @return The content type. - */ - default String getContentType() { - return getHttpFields().get(HttpHeader.CONTENT_TYPE); - } - - /** - * Get HTTP request cookies. - * - * @return The HTTP request cookies. - */ - default List getCookies() { - return getRequest().getCookies(); - } - - /** - * Set HTTP response status. - * - * @param status The HTTP response status. - * @return The routing context. - */ - default RoutingContext setStatus(int status) { - getResponse().setStatus(status); - return this; - } - - /** - * Set HTTP response reason. - * - * @param reason The HTTP response reason. - * @return The routing context. - */ - default RoutingContext setReason(String reason) { - getResponse().setReason(reason); - return this; - } - - /** - * Set HTTP response version. - * - * @param httpVersion The HTTP response version. - * @return The routing context. - */ - default RoutingContext setHttpVersion(HttpVersion httpVersion) { - getResponse().setHttpVersion(httpVersion); - return this; - } - - /** - * Put HTTP response header. - * - * @param header The HTTP header. - * @param value The value. - * @return The routing context. - */ - default RoutingContext put(HttpHeader header, String value) { - getResponse().getHttpFields().put(header, value); - return this; - } - - /** - * Put HTTP response header. - * - * @param header The HTTP header. - * @param value The value. - * @return The routing context. - */ - default RoutingContext put(HttpHeader header, HttpHeaderValue value) { - getResponse().getHttpFields().put(header, value); - return this; - } - - /** - * Put HTTP response header. - * - * @param header The HTTP header. - * @param value The value. - * @return The routing context. - */ - default RoutingContext put(String header, String value) { - getResponse().getHttpFields().put(header, value); - return this; - } - - /** - * Add HTTP response header. - * - * @param header The HTTP header. - * @param value The value. - * @return The routing context. - */ - default RoutingContext add(HttpHeader header, String value) { - getResponse().getHttpFields().add(header, value); - return this; - } - - /** - * Add HTTP response header. - * - * @param header The HTTP header. - * @param value The value. - * @return The routing context. - */ - default RoutingContext add(HttpHeader header, HttpHeaderValue value) { - getResponse().getHttpFields().add(header, value); - return this; - } - - /** - * Add HTTP response header. - * - * @param header The HTTP header. - * @param value The value. - * @return The routing context. - */ - default RoutingContext add(String header, String value) { - getResponse().getHttpFields().add(header, value); - return this; - } - - /** - * Add HTTP response header and CSV values. - * - * @param header The HTTP header. - * @param values The value. - * @return The routing context. - */ - default RoutingContext addCSV(HttpHeader header, String... values) { - getResponse().getHttpFields().addCSV(header, values); - return this; - } - - /** - * Add HTTP response header and CSV values. - * - * @param header The HTTP header. - * @param values The value. - * @return The routing context. - */ - default RoutingContext addCSV(String header, String... values) { - getResponse().getHttpFields().addCSV(header, values); - return this; - } - - /** - * Add HTTP response fields. - * - * @param fields The HTTP response fields. - * @return The routing context. - */ - default RoutingContext addAll(HttpFields fields) { - getResponse().getHttpFields().addAll(fields); - return this; - } - - /** - * Set the HTTP trailer fields. - * - * @param supplier The HTTP trailer fields supplier. - * @return The routing context. - */ - default RoutingContext setTrailerSupplier(Supplier supplier) { - getResponse().setTrailerSupplier(supplier); - return this; - } - - /** - * Set HTTP response cookies. - * - * @param cookies The HTTP response cookies. - * @return he routing context. - */ - default RoutingContext setCookies(List cookies) { - getResponse().setCookies(cookies); - return this; - } - - /** - * Set the HTTP response content provider. - * - * @param contentProvider HTTP response content provider. - * @return The routing context. - */ - default RoutingContext contentProvider(HttpServerContentProvider contentProvider) { - getResponse().setContentProvider(contentProvider); - return this; - } - - /** - * Response 100 continue. - * - * @return The future result. - */ - default CompletableFuture response100Continue() { - return getResponse().response100Continue(); - } - - /** - * Response 200 Connection Established. - * - * @return The future result. - */ - default CompletableFuture response200ConnectionEstablished() { - return getResponse().response200ConnectionEstablished(); - } - - /** - * Write string to the client. - * - * @param value The response content. - * @return The routing context. - */ - default RoutingContext write(String value) { - getResponse().commit().thenCompose(ignore -> getResponse().getOutputChannel().write(value)); - return this; - } - - /** - * Write the response content. - * - * @param byteBuffer The response content. - * @return The routing context. - */ - default RoutingContext write(ByteBuffer byteBuffer) { - getResponse().commit().thenCompose(ignore -> getResponse().getOutputChannel().write(byteBuffer)); - return this; - } - - /** - * Write the response content. - * - * @param byteBufferList The response content list. - * @param offset The offset within the buffer list of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The routing context. - */ - default RoutingContext write(List byteBufferList, int offset, int length) { - getResponse().commit().thenCompose(ignore -> getResponse().getOutputChannel().write(byteBufferList, offset, length)); - return this; - } - - /** - * Write the response content. - * - * @param byteBuffers The response content array. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The routing context. - */ - default RoutingContext write(ByteBuffer[] byteBuffers, int offset, int length) { - getResponse().commit().thenCompose(ignore -> getResponse().getOutputChannel().write(byteBuffers, offset, length)); - return this; - } - - /** - * End the HTTP response. - * - * @return The response future result. - */ - default CompletableFuture end() { - return getResponse().commit().thenCompose(ignore -> getResponse().closeAsync()); - } - - /** - * Write the value and end the HTTP response. - * - * @param value The HTTP response content. - * @return The response future result. - */ - default CompletableFuture end(String value) { - return write(value).end(); - } - - /** - * Write the redirect response to the client. - * - * @param url The redirect URL. - * @return The response future result. - */ - CompletableFuture redirect(String url); - - - /** - * If true, the router chain has next handler. - * - * @return If true, the router chain has next handler. - */ - boolean hasNext(); - - /** - * Execute the next handler of the router chain. - * - * @return The handler future result. - */ - CompletableFuture next(); - - /** - * Get the HTTP server connection. - * - * @return The HTTP server connection. - */ - HttpServerConnection getConnection(); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpChannelGroup.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpChannelGroup.java deleted file mode 100644 index ea291e140..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpChannelGroup.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.common.lifecycle.LifeCycle; -import kotlinx.coroutines.CoroutineDispatcher; - -import java.nio.channels.AsynchronousChannelGroup; - -/** - * The asynchronous channel and message thread group. It manages the IO and message threads. - * - * @author Pengtao Qiu - */ -public interface TcpChannelGroup extends LifeCycle { - - /** - * Get the asynchronous channel group. It manages the IO thread. - * - * @return The asynchronous channel group. - */ - AsynchronousChannelGroup getAsynchronousChannelGroup(); - - /** - * Get the coroutine dispatcher. It is the message handler thread pool. - * - * @param connectionId The connection id. - * @return The coroutine dispatcher. - */ - CoroutineDispatcher getDispatcher(int connectionId); - - /** - * Get the next connection id. It is auto increment. - * - * @return The next connection id. - */ - int getNextId(); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClient.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClient.java deleted file mode 100644 index b076964e3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClient.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.common.lifecycle.LifeCycle; -import com.fireflysource.net.tcp.secure.SecureEngineFactory; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The TCP net client. - * - * @author Pengtao Qiu - */ -public interface TcpClient extends LifeCycle { - - /** - * Set the TCP channel group. - * - * @param group The TCP channel group. - * @return The TCP client. - */ - TcpClient tcpChannelGroup(TcpChannelGroup group); - - /** - * Stop the TCP group when the TCP client stops. - * - * @param stop If true, stop the TCP group when the TCP client stops. - * @return The TCP client. - */ - TcpClient stopTcpChannelGroup(boolean stop); - - /** - * Set the TLS engine factory. - * - * @param secureEngineFactory The TLS engine factory. - * @return The TCP client. - */ - TcpClient secureEngineFactory(SecureEngineFactory secureEngineFactory); - - /** - * Enable the TLS protocol over the TCP connection. - * - * @return The TCP client. - */ - TcpClient enableSecureConnection(); - - /** - * Set the TCP idle timeout. The unit is second. - * - * @param timeout The TCP idle timeout. The unit is second. - * @return The TCP client. - */ - TcpClient timeout(Long timeout); - - /** - * Enable output buffer. - * - * @return The TCP client. - */ - TcpClient enableOutputBuffer(); - - /** - * Set output buffer size. - * - * @param bufferSize The output buffer size. - * @return The TCP client. - */ - TcpClient bufferSize(int bufferSize); - - /** - * Create a TCP connection. - * - * @param address The server address. - * @return The TCP connection. - */ - CompletableFuture connect(SocketAddress address); - - /** - * Create a TCP connection to the server. - * - * @param host The server host. - * @param port The server port. - * @return The TCP connection. - */ - default CompletableFuture connect(String host, int port) { - return connect(new InetSocketAddress(host, port)); - } - - /** - * If you enable TLS connection, tt creates a TLS connection and set the supported application layer protocols. - * - * @param address The server address. - * @param supportedProtocols The supported application layer protocols. - * @return The TCP connection. - */ - CompletableFuture connect(SocketAddress address, List supportedProtocols); - - /** - * If you enable TLS connection, tt creates a TLS connection using advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param address The server address. - * @param peerHost the non-authoritative name of the host. - * @param peerPort the non-authoritative port. - * @param supportedProtocols The supported application layer protocols. - * @return The TCP connection. - */ - CompletableFuture connect(SocketAddress address, String peerHost, int peerPort, List supportedProtocols); - - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClientConnectionFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClientConnectionFactory.java deleted file mode 100644 index 98cc22b64..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClientConnectionFactory.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.common.collection.CollectionUtils; -import com.fireflysource.common.lifecycle.AbstractLifeCycle; -import com.fireflysource.common.object.Assert; -import com.fireflysource.net.tcp.secure.SecureEngineFactory; - -import java.net.InetSocketAddress; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public class TcpClientConnectionFactory extends AbstractLifeCycle { - - private TcpChannelGroup tcpChannelGroup; - private boolean stopTcpChannelGroup; - private long timeout; - private SecureEngineFactory secureEngineFactory; - private TcpClient tcpClient; - private TcpClient secureTcpClient; - - public TcpClientConnectionFactory() { - } - - public TcpClientConnectionFactory(TcpChannelGroup tcpChannelGroup, boolean stopTcpChannelGroup, long timeout, SecureEngineFactory secureEngineFactory) { - this.tcpChannelGroup = tcpChannelGroup; - this.stopTcpChannelGroup = stopTcpChannelGroup; - this.timeout = timeout; - this.secureEngineFactory = secureEngineFactory; - } - - public TcpChannelGroup getTcpChannelGroup() { - return tcpChannelGroup; - } - - public void setTcpChannelGroup(TcpChannelGroup tcpChannelGroup) { - this.tcpChannelGroup = tcpChannelGroup; - } - - public boolean isStopTcpChannelGroup() { - return stopTcpChannelGroup; - } - - public void setStopTcpChannelGroup(boolean stopTcpChannelGroup) { - this.stopTcpChannelGroup = stopTcpChannelGroup; - } - - public long getTimeout() { - return timeout; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - public SecureEngineFactory getSecureEngineFactory() { - return secureEngineFactory; - } - - public void setSecureEngineFactory(SecureEngineFactory secureEngineFactory) { - this.secureEngineFactory = secureEngineFactory; - } - - public TcpClientConnectionFactory isStopTcpChannelGroup(boolean isStopTcpChannelGroup) { - this.stopTcpChannelGroup = isStopTcpChannelGroup; - return this; - } - - public TcpClientConnectionFactory timeout(long timeout) { - this.timeout = timeout; - return this; - } - - public TcpClientConnectionFactory tcpChannelGroup(TcpChannelGroup tcpChannelGroup) { - this.tcpChannelGroup = tcpChannelGroup; - return this; - } - - public TcpClientConnectionFactory secureEngineFactory(SecureEngineFactory secureEngineFactory) { - this.secureEngineFactory = secureEngineFactory; - return this; - } - - public CompletableFuture connect(InetSocketAddress inetSocketAddress, boolean secure) { - return connect(inetSocketAddress, secure, Collections.emptyList()); - } - - public CompletableFuture connect(InetSocketAddress inetSocketAddress, boolean secure, List supportedProtocols) { - CompletableFuture future; - if (secure) { - if (CollectionUtils.isEmpty(supportedProtocols)) { - future = secureTcpClient.connect(inetSocketAddress); - } else { - future = secureTcpClient.connect(inetSocketAddress, supportedProtocols); - } - } else { - future = tcpClient.connect(inetSocketAddress); - } - return future; - } - - @Override - protected void init() { - Assert.notNull(tcpChannelGroup, "The tcp channel group must be not null"); - tcpClient = TcpClientFactory - .create() - .tcpChannelGroup(tcpChannelGroup) - .stopTcpChannelGroup(stopTcpChannelGroup) - .timeout(timeout); - secureTcpClient = TcpClientFactory - .create() - .tcpChannelGroup(tcpChannelGroup) - .stopTcpChannelGroup(stopTcpChannelGroup) - .timeout(timeout) - .enableSecureConnection(); - if (secureEngineFactory != null) { - secureTcpClient.secureEngineFactory(secureEngineFactory); - } - } - - @Override - protected void destroy() { - tcpClient.stop(); - secureTcpClient.stop(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClientFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClientFactory.java deleted file mode 100644 index c966b5e2d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpClientFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.net.tcp.aio.AioTcpClient; -import com.fireflysource.net.tcp.aio.TcpConfig; - -abstract public class TcpClientFactory { - - public static TcpClient create() { - return new AioTcpClient(); - } - - public static TcpClient create(TcpConfig config) { - return new AioTcpClient(config); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpConnection.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpConnection.java deleted file mode 100644 index c18b1a284..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpConnection.java +++ /dev/null @@ -1,292 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.common.func.Callback; -import com.fireflysource.common.io.AsyncCloseable; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.Connection; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -import static com.fireflysource.common.sys.Result.futureToConsumer; - -/** - * The TCP connection. It reads or writes messages using the TCP (or TLS over the TCP) protocol. - * - * @author Pengtao Qiu - */ -public interface TcpConnection extends Connection, ApplicationProtocolSelector, TcpCoroutineDispatcher, AsyncCloseable { - - Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - - /** - * Register a connection close event callback. When the connection close, the framework will invoke this function. - * - * @param callback The connection close event callback. - * @return The current connection. - */ - TcpConnection onClose(Callback callback); - - /** - * Close the current connection and wait the remaining messages of the channel have been sent completely. - * - * @param result When the connection close, the framework will invoke this function. - * @return The current connection. - */ - TcpConnection close(Consumer> result); - - /** - * Close the current connection and wait the remaining messages of the channel have been sent completely. - * - * @return The future result. - */ - default CompletableFuture closeAsync() { - CompletableFuture future = new CompletableFuture<>(); - close(futureToConsumer(future)); - return future; - } - - /** - * Close the current connection immediately. The remaining messages of the channel will not be sent. - * - * @return The current connection. - */ - TcpConnection closeNow(); - - /** - * If return true, the connection input channel has been closed. You can't receive any messages from the remote endpoint. - * - * @return If return true, the connection input channel has been closed. - */ - boolean isShutdownInput(); - - /** - * If return true, the connection output channel has been closed. You can't send any messages to the remote endpoint. - * - * @return If return true, the connection output channel has been closed. You can't send any messages to the remote endpoint. - */ - boolean isShutdownOutput(); - - /** - * Shutdown the connection for reading without closing the connection. - * - * @return The current connection. - */ - TcpConnection shutdownInput(); - - /** - * Shutdown the connection for writing without closing the connection. - * - * @return The current connection. - */ - TcpConnection shutdownOutput(); - - /** - * Read data from the remote endpoint. - * - * @return The future data. - */ - CompletableFuture read(); - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffer The byte buffer. - * @param result The handler for consuming the result. - * @return The current connection. - */ - TcpConnection write(ByteBuffer byteBuffer, Consumer> result); - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffers The byte buffer array. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @param result The handler for consuming the result. - * @return The current connection. - */ - TcpConnection write(ByteBuffer[] byteBuffers, int offset, int length, Consumer> result); - - /** - * Write the data to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @param result The handler for consuming the result. - * @return The current connection. - */ - TcpConnection write(List byteBufferList, int offset, int length, Consumer> result); - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffer The byte buffer. - * @return The future for consuming the result. - */ - default CompletableFuture write(ByteBuffer byteBuffer) { - CompletableFuture future = new CompletableFuture<>(); - write(byteBuffer, futureToConsumer(future)); - return future; - } - - /** - * Write and flush data to the remote endpoint. - * - * @param byteBuffer The byte buffer. - * @return The future for consuming the result. - */ - default CompletableFuture writeAndFlush(ByteBuffer byteBuffer) { - return write(byteBuffer).thenCompose(len -> flush().thenApply(n -> len)); - } - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffers The byte buffer array. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The future for consuming the result. - */ - default CompletableFuture write(ByteBuffer[] byteBuffers, int offset, int length) { - CompletableFuture future = new CompletableFuture<>(); - write(byteBuffers, offset, length, futureToConsumer(future)); - return future; - } - - /** - * Write the data to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @return The future for consuming the result. - */ - default CompletableFuture write(List byteBufferList, int offset, int length) { - CompletableFuture future = new CompletableFuture<>(); - write(byteBufferList, offset, length, futureToConsumer(future)); - return future; - } - - /** - * Write and flush data to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @return The future for consuming the result. - */ - default CompletableFuture writeAndFlush(List byteBufferList, int offset, int length) { - return write(byteBufferList, offset, length).thenCompose(len -> flush().thenApply(n -> len)); - } - - /** - * Write the data to the remote endpoint. - * - * @param bytes The byte array. - * @param result The handler for consuming the result. - * @return The current connection. - */ - default TcpConnection write(byte[] bytes, Consumer> result) { - return write(ByteBuffer.wrap(bytes), result); - } - - /** - * Write the data to the remote endpoint. - * - * @param string The string. - * @param result The handler for consuming the result. - * @return The current connection. - */ - default TcpConnection write(String string, Consumer> result) { - return write(ByteBuffer.wrap(string.getBytes(DEFAULT_CHARSET)), result); - } - - /** - * Flush output buffer to remote endpoint. - * - * @param result When flush data to remote endpoint, the framework will invoke this function. - * @return The current connection. - */ - TcpConnection flush(Consumer> result); - - /** - * Flush output buffer to remote endpoint. - * - * @return The future result. - */ - default CompletableFuture flush() { - CompletableFuture future = new CompletableFuture<>(); - flush(futureToConsumer(future)); - return future; - } - - /** - * Get output buffer size. - * - * @return The output buffer size. - */ - int getBufferSize(); - - /** - * If you enable the TLS protocol, it returns true. - * - * @return If you enable the TLS protocol, it returns true. - */ - boolean isSecureConnection(); - - /** - * If you enable the TLS protocol, it presents the TLS engine is client mode or server mode. - * - * @return The TLS engine is client mode or server mode. - */ - boolean isClientMode(); - - /** - * If return true, the TLS engine completes the handshake stage. - * - * @return If return true, the TLS engine completes the handshake stage. - */ - boolean isHandshakeComplete(); - - /** - * Listen the TLS handshake complete event. If the TLS handshake has finished, the framework will invoke the callback function. - * - * @param result The value is the negotiated application layer protocol. - * @return The current connection. - */ - TcpConnection beginHandshake(Consumer> result); - - /** - * Listen the TLS handshake complete event. - * - * @return The value is the negotiated application layer protocol. - */ - default CompletableFuture beginHandshake() { - CompletableFuture future = new CompletableFuture<>(); - beginHandshake(futureToConsumer(future)); - return future; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpCoroutineDispatcher.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpCoroutineDispatcher.java deleted file mode 100644 index 256162cf9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpCoroutineDispatcher.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fireflysource.net.tcp; - -import kotlinx.coroutines.CompletableJob; -import kotlinx.coroutines.CoroutineDispatcher; -import kotlinx.coroutines.CoroutineScope; - -import java.util.concurrent.Executor; - -public interface TcpCoroutineDispatcher extends Executor { - - /** - * Get the coroutine dispatcher of this connection. One TCP connection is always in the same coroutine context. - * - * @return The coroutine dispatcher of this connection. - */ - CoroutineDispatcher getCoroutineDispatcher(); - - /** - * Get the coroutine scope of this connection. - * - * @return The coroutine scope. - */ - CoroutineScope getCoroutineScope(); - - /** - * Get the supervisor job. - * - * @return The supervisor job. - */ - CompletableJob getSupervisorJob(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpServer.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpServer.java deleted file mode 100644 index 33a587bb7..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpServer.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.common.lifecycle.LifeCycle; -import com.fireflysource.net.tcp.secure.SecureEngineFactory; -import kotlinx.coroutines.channels.Channel; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.List; -import java.util.function.Consumer; - -/** - * The TCP net server. - * - * @author Pengtao Qiu - */ -public interface TcpServer extends LifeCycle, Cloneable { - - /** - * Set the TCP channel group. - * - * @param group The TCP channel group. - * @return The TCP server. - */ - TcpServer tcpChannelGroup(TcpChannelGroup group); - - /** - * Stop the TCP group when the TCP server stops. - * - * @param stop If true, stop the TCP group when the TCP server stops. - * @return The TCP client. - */ - TcpServer stopTcpChannelGroup(boolean stop); - - /** - * Set the TLS engine factory. - * - * @param secureEngineFactory The TLS engine factory. - * @return The TCP server. - */ - TcpServer secureEngineFactory(SecureEngineFactory secureEngineFactory); - - /** - * The supported application layer protocols. - * - * @param supportedProtocols The supported application layer protocols. - * @return The TCP server. - */ - TcpServer supportedProtocols(List supportedProtocols); - - /** - * Create a TLS engine using advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param peerHost the non-authoritative name of the host. - * @return The TCP server. - */ - TcpServer peerHost(String peerHost); - - /** - * Create a TLS engine using advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param peerPort the non-authoritative port. - * @return The TCP server. - */ - TcpServer peerPort(int peerPort); - - /** - * Enable the TLS protocol over the TCP connection. - * - * @return The TCP server. - */ - TcpServer enableSecureConnection(); - - /** - * Set the TCP idle timeout. The unit is second. - * - * @param timeout The TCP idle timeout. Time unit is second. - * @return The TCP server. - */ - TcpServer timeout(Long timeout); - - /** - * Enable output buffer. - * - * @return The TCP server. - */ - TcpServer enableOutputBuffer(); - - /** - * Set output buffer size. - * - * @param bufferSize The output buffer size. - * @return The TCP server. - */ - TcpServer bufferSize(int bufferSize); - - /** - * Accept the client TCP connection. - * - * @param consumer Accept the connection callback. - * @return The TCP server. - */ - TcpServer onAccept(Consumer consumer); - - /** - * If you don't set a callback for the connection accepting event. The server will accept connection - * and send it to the channel, and then, you can receive the connection from this channel. - * - * @return The TCP connection channel. - */ - Channel getTcpConnectionChannel(); - - /** - * Bind a server TCP address - * - * @param address The server TCP address. - * @return The TCP server. - */ - TcpServer listen(SocketAddress address); - - /** - * Bind the server host and port. - * - * @param host The server host. - * @param port The server port. - * @return The TCP server. - */ - default TcpServer listen(String host, int port) { - return listen(new InetSocketAddress(host, port)); - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpServerFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpServerFactory.java deleted file mode 100644 index 9e4e01fb6..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/TcpServerFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.net.tcp; - -import com.fireflysource.net.tcp.aio.AioTcpServer; -import com.fireflysource.net.tcp.aio.TcpConfig; - -abstract public class TcpServerFactory { - - public static TcpServer create() { - return new AioTcpServer(); - } - - public static TcpServer create(TcpConfig config) { - return new AioTcpServer(config); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/WrappedTcpConnection.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/WrappedTcpConnection.java deleted file mode 100644 index 8241d4d17..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/WrappedTcpConnection.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.fireflysource.net.tcp; - -/** - * The wrapped TCP connection. - * - * @author Pengtao Qiu - */ -public interface WrappedTcpConnection { - - /** - * Get the raw TCP connection. - * - * @return The raw TCP connection. - */ - TcpConnection getRawTcpConnection(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/exception/UnknownProtocolException.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/exception/UnknownProtocolException.java deleted file mode 100644 index 429834e5f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/exception/UnknownProtocolException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.tcp.exception; - -public class UnknownProtocolException extends RuntimeException { - - public UnknownProtocolException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/ApplicationProtocolSelector.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/ApplicationProtocolSelector.java deleted file mode 100644 index 7894950cf..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/ApplicationProtocolSelector.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.net.tcp.secure; - -import java.util.List; - -/** - * The TLS application layer protocol negotiation. - * - * @author Pengtao Qiu - */ -public interface ApplicationProtocolSelector { - - /** - * The protocol negotiation result. - * - * @return The protocol negotiation result. - */ - default String getApplicationProtocol() { - return ""; - } - - /** - * The current connection supports the protocols. - * - * @return The current connection supports the protocols. - */ - List getSupportedApplicationProtocols(); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/DefaultSecureEngineFactorySelector.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/DefaultSecureEngineFactorySelector.java deleted file mode 100644 index 2c44b1d4c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/DefaultSecureEngineFactorySelector.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fireflysource.net.tcp.secure; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.JavaVersion; -import com.fireflysource.net.tcp.secure.conscrypt.NoCheckConscryptSSLContextFactory; -import com.fireflysource.net.tcp.secure.conscrypt.SelfSignedCertificateConscryptSSLContextFactory; -import com.fireflysource.net.tcp.secure.jdk.NoCheckOpenJdkSSLContextFactory; -import com.fireflysource.net.tcp.secure.jdk.SelfSignedCertificateOpenJdkSSLContextFactory; - -public class DefaultSecureEngineFactorySelector { - - public static SecureEngineFactory createSecureEngineFactory(boolean client) { - SecureEngineFactory secureEngineFactory; - if (JavaVersion.VERSION.getPlatform() < 9) { - if (JavaVersion.VERSION.getPlatform() == 8) { - String[] update = StringUtils.split(JavaVersion.VERSION.getVersion(), '_'); - if (update.length == 2) { - try { - int u = Integer.parseInt(update[1]); - if (u >= 222) { - secureEngineFactory = createOpenJdkSecureEngineFactory(client); - } else { - secureEngineFactory = createConscryptSecureEngineFactory(client); - } - } catch (Exception e) { - secureEngineFactory = createConscryptSecureEngineFactory(client); - } - } else { - secureEngineFactory = createConscryptSecureEngineFactory(client); - } - } else { - secureEngineFactory = createConscryptSecureEngineFactory(client); - } - } else { - secureEngineFactory = createOpenJdkSecureEngineFactory(client); - } - return secureEngineFactory; - } - - private static SecureEngineFactory createOpenJdkSecureEngineFactory(boolean client) { - SecureEngineFactory secureEngineFactory; - if (client) { - secureEngineFactory = new NoCheckOpenJdkSSLContextFactory(); - } else { - secureEngineFactory = new SelfSignedCertificateOpenJdkSSLContextFactory(); - } - return secureEngineFactory; - } - - private static SecureEngineFactory createConscryptSecureEngineFactory(boolean client) { - SecureEngineFactory secureEngineFactory; - if (client) { - secureEngineFactory = new NoCheckConscryptSSLContextFactory(); - } else { - secureEngineFactory = new SelfSignedCertificateConscryptSSLContextFactory(); - } - return secureEngineFactory; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/HandshakeResult.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/HandshakeResult.java deleted file mode 100644 index a209560f9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/HandshakeResult.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.net.tcp.secure; - -import java.nio.ByteBuffer; -import java.util.List; - -public interface HandshakeResult { - - /** - * Get the stashed application buffers during the handshake process. - * - * @return The stashed application buffers. - */ - List getStashedAppBuffers(); - - /** - * The application protocol negotiation result. - * - * @return The application protocol negotiation result. - */ - String getApplicationProtocol(); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/SecureEngine.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/SecureEngine.java deleted file mode 100644 index bbc8308f9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/SecureEngine.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.fireflysource.net.tcp.secure; - -import com.fireflysource.common.sys.Result; - -import java.io.Closeable; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import static com.fireflysource.common.sys.Result.futureToConsumer; - -/** - * The TLS engine. It can encrypt or decrypt the message. - * - * @author Pengtao Qiu - */ -public interface SecureEngine extends Closeable, ApplicationProtocolSelector { - - /** - * If return true, the TLS engine is client mode. - * - * @return If return true, the TLS engine is client mode. - */ - boolean isClientMode(); - - /** - * If return true, the TLS handshake stage is complete. - * - * @return If return true, the TLS handshake stage is complete. - */ - boolean isHandshakeComplete(); - - /** - * Begin the TLS handshake. - * - * @param result The TLS handshake result. - */ - void beginHandshake(Consumer> result); - - /** - * Begin the TLS handshake. - * - * @return The future for consuming the TLS handshake result. - */ - default CompletableFuture beginHandshake() { - CompletableFuture future = new CompletableFuture<>(); - beginHandshake(futureToConsumer(future)); - return future; - } - - /** - * Need read data in the handshake process. - * - * @param supplier The data supplier. - * @return The secure engine. - */ - SecureEngine onHandshakeRead(Supplier> supplier); - - /** - * Need write data in the handshake process. - * - * @param function The write function. - * @return The secure engine. - */ - SecureEngine onHandshakeWrite(Function> function); - - /** - * Decrypt the cipher text to the plain text. - * - * @param byteBuffer The cipher text data. - * @return The cipher text byte buffer. - */ - ByteBuffer decrypt(ByteBuffer byteBuffer); - - /** - * Encrypt the plain text to the cipher text. - * - * @param byteBuffer The plain text data. - * @return The cipher text byte buffer. - */ - ByteBuffer encrypt(ByteBuffer byteBuffer); - - /** - * Encrypt the plain text to the cipher text. - * - * @param byteBuffers The plain text data. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The cipher text byte buffer. - */ - ByteBuffer encrypt(ByteBuffer[] byteBuffers, int offset, int length); - - /** - * Encrypt the plain text to the cipher text. - * - * @param byteBuffers The plain text data. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The cipher text byte buffer. - */ - ByteBuffer encrypt(List byteBuffers, int offset, int length); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/SecureEngineFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/SecureEngineFactory.java deleted file mode 100644 index 665571b25..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/SecureEngineFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.fireflysource.net.tcp.secure; - -import kotlinx.coroutines.CoroutineScope; - -import java.util.List; - -/** - * The TLS engine factory. - * - * @author Pengtao Qiu - */ -public interface SecureEngineFactory { - - /** - * Create a TLS engine. - * - * @param coroutineScope The coroutine scope. - * @param clientMode If true, the current connection is the client tcp connection. - * @param supportedProtocols The supported application layer protocols. - * @return The TLS engine. - */ - SecureEngine create(CoroutineScope coroutineScope, boolean clientMode, List supportedProtocols); - - /** - * Create a TLS engine using advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param coroutineScope The coroutine scope. - * @param clientMode If true, the current connection is the client tcp connection. - * @param peerHost the non-authoritative name of the host. - * @param peerPort the non-authoritative port. - * @param supportedProtocols The supported application layer protocols. - * @return The TLS engine. - */ - SecureEngine create(CoroutineScope coroutineScope, boolean clientMode, String peerHost, int peerPort, List supportedProtocols); - - /** - * Create a TLS engine by default coroutine scope. - * - * @param clientMode If true, the current connection is the client tcp connection. - * @param supportedProtocols The supported application layer protocols. - * @return The TLS engine. - */ - default SecureEngine create(boolean clientMode, List supportedProtocols) { - return create(null, clientMode, supportedProtocols); - } - - /** - * Create a TLS engine by default coroutine scope and advisory peer information. - * Applications using this factory method are providing hints for an internal session reuse strategy. - * Some cipher suites (such as Kerberos) require remote hostname information, in which case peerHost needs to be specified. - * - * @param clientMode If true, the current connection is the client tcp connection. - * @param peerHost the non-authoritative name of the host. - * @param peerPort the non-authoritative port. - * @param supportedProtocols The supported application layer protocols. - * @return The TLS engine. - */ - default SecureEngine create(boolean clientMode, String peerHost, int peerPort, List supportedProtocols) { - return create(null, clientMode, peerHost, peerPort, supportedProtocols); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/common/AbstractSecureEngineFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/common/AbstractSecureEngineFactory.java deleted file mode 100644 index 386af7c57..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/common/AbstractSecureEngineFactory.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.fireflysource.net.tcp.secure.common; - -import com.fireflysource.common.coroutine.CommonCoroutinePoolKt; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import com.fireflysource.net.tcp.secure.SecureEngine; -import com.fireflysource.net.tcp.secure.SecureEngineFactory; -import kotlinx.coroutines.CoroutineScope; - -import javax.net.ssl.*; -import java.io.IOException; -import java.io.InputStream; -import java.security.*; -import java.security.cert.CertificateException; -import java.util.List; -import java.util.Optional; - -import static com.fireflysource.net.tcp.secure.utils.SecureUtils.KEY_MANAGER_FACTORY_TYPE; -import static com.fireflysource.net.tcp.secure.utils.SecureUtils.TRUST_MANAGER_FACTORY_TYPE; - -abstract public class AbstractSecureEngineFactory implements SecureEngineFactory { - - protected static final LazyLogger LOG = SystemLogger.create(AbstractSecureEngineFactory.class); - - public SSLContext getSSLContextWithManager(KeyManager[] km, TrustManager[] tm, SecureRandom random) - throws NoSuchAlgorithmException, KeyManagementException, NoSuchProviderException { - long start = System.currentTimeMillis(); - - final SSLContext sslContext = SSLContext.getInstance(getSecureProtocol(), getProviderName()); - sslContext.init(km, tm, random); - - long end = System.currentTimeMillis(); - String protocol = sslContext.getProtocol(); - long time = end - start; - logCreatingSSLContent(time, protocol); - return sslContext; - } - - private void logCreatingSSLContent(long time, String protocol) { - LOG.info("Created SSL context in time {}ms. TLS protocol: {}", time, protocol); - } - - public SSLContext getSSLContext(InputStream in, String keystorePassword, String keyPassword, String keyStoreType) - throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, - UnrecoverableKeyException, KeyManagementException, NoSuchProviderException { - return getSSLContext(in, keystorePassword, keyPassword, keyStoreType, null, null, null); - } - - public SSLContext getSSLContext(InputStream in, String keystorePassword, String keyPassword, - String keyStoreType, - String keyManagerFactoryType, String trustManagerFactoryType, String sslProtocol) - throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, - UnrecoverableKeyException, KeyManagementException, NoSuchProviderException { - long start = System.currentTimeMillis(); - final SSLContext sslContext; - - KeyStore ks = KeyStore.getInstance(keyStoreType); - ks.load(in, keystorePassword != null ? keystorePassword.toCharArray() : null); - - KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryType == null ? KEY_MANAGER_FACTORY_TYPE : keyManagerFactoryType); - kmf.init(ks, keyPassword != null ? keyPassword.toCharArray() : null); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance(trustManagerFactoryType == null ? TRUST_MANAGER_FACTORY_TYPE : trustManagerFactoryType); - tmf.init(ks); - - sslContext = SSLContext.getInstance(sslProtocol == null ? getSecureProtocol() : sslProtocol, getProviderName()); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - - long end = System.currentTimeMillis(); - String protocol = sslContext.getProtocol(); - long time = end - start; - logCreatingSSLContent(time, protocol); - return sslContext; - } - - @Override - public SecureEngine create(CoroutineScope coroutineScope, boolean clientMode, List supportedProtocols) { - SSLEngine sslEngine = getSSLContext().createSSLEngine(); - sslEngine.setUseClientMode(clientMode); - ApplicationProtocolSelector selector = createApplicationProtocolSelector(sslEngine, supportedProtocols); - CoroutineScope scope = Optional.ofNullable(coroutineScope).orElseGet(CommonCoroutinePoolKt::getApplicationScope); - return createSecureEngine(scope, sslEngine, selector); - } - - @Override - public SecureEngine create(CoroutineScope coroutineScope, boolean clientMode, String peerHost, int peerPort, - List supportedProtocols) { - SSLEngine sslEngine = getSSLContext().createSSLEngine(peerHost, peerPort); - sslEngine.setUseClientMode(clientMode); - ApplicationProtocolSelector selector = createApplicationProtocolSelector(sslEngine, supportedProtocols); - CoroutineScope scope = Optional.ofNullable(coroutineScope).orElseGet(CommonCoroutinePoolKt::getApplicationScope); - return createSecureEngine(scope, sslEngine, selector); - } - - abstract public SSLContext getSSLContext(); - - abstract public String getSecureProtocol(); - - abstract public String getProviderName(); - - abstract public SecureEngine createSecureEngine( - CoroutineScope coroutineScope, - SSLEngine sslEngine, - ApplicationProtocolSelector applicationProtocolSelector); - - abstract public ApplicationProtocolSelector createApplicationProtocolSelector( - SSLEngine sslEngine, List supportedProtocolList); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/AbstractConscryptSecureEngineFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/AbstractConscryptSecureEngineFactory.java deleted file mode 100644 index c388cb758..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/AbstractConscryptSecureEngineFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import com.fireflysource.net.tcp.secure.SecureEngine; -import com.fireflysource.net.tcp.secure.common.AbstractSecureEngineFactory; -import kotlinx.coroutines.CoroutineScope; -import org.conscrypt.Conscrypt; - -import javax.net.ssl.SSLEngine; -import java.security.Provider; -import java.security.Security; -import java.util.List; - -/** - * @author Pengtao Qiu - */ -abstract public class AbstractConscryptSecureEngineFactory extends AbstractSecureEngineFactory { - - private static final String SECURE_PROTOCOL = "TLSv1.3"; - private static final String PROVIDER_NAME; - - static { - Provider provider = Conscrypt.newProvider(); - PROVIDER_NAME = provider.getName(); - Security.addProvider(provider); - LOG.info("Add Conscrypt security provider. info: {}, name: {}", provider.getInfo(), PROVIDER_NAME); - } - - @Override - public String getProviderName() { - return PROVIDER_NAME; - } - - @Override - public String getSecureProtocol() { - return SECURE_PROTOCOL; - } - - @Override - public SecureEngine createSecureEngine( - CoroutineScope coroutineScope, - SSLEngine sslEngine, - ApplicationProtocolSelector applicationProtocolSelector) { - return new ConscryptSecureEngine(coroutineScope, sslEngine, applicationProtocolSelector); - } - - @Override - public ApplicationProtocolSelector createApplicationProtocolSelector( - SSLEngine sslEngine, List supportedProtocolList) { - return new ConscryptApplicationProtocolSelector(sslEngine, supportedProtocolList); - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/ConscryptApplicationProtocolSelector.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/ConscryptApplicationProtocolSelector.java deleted file mode 100644 index 7454dfc34..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/ConscryptApplicationProtocolSelector.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import org.conscrypt.Conscrypt; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSocket; -import java.util.List; -import java.util.Optional; - -/** - * @author Pengtao Qiu - */ -public class ConscryptApplicationProtocolSelector implements ApplicationProtocolSelector { - - private static final LazyLogger LOG = SystemLogger.create(ConscryptApplicationProtocolSelector.class); - - private final String[] supportedProtocols; - private final List supportedProtocolList; - private final SSLEngine sslEngine; - - public ConscryptApplicationProtocolSelector(SSLEngine sslEngine, List supportedProtocolList) { - this.supportedProtocolList = supportedProtocolList; - supportedProtocols = this.supportedProtocolList.toArray(StringUtils.EMPTY_STRING_ARRAY); - this.sslEngine = sslEngine; - if (sslEngine.getUseClientMode()) { - Conscrypt.setApplicationProtocols(sslEngine, supportedProtocols); - } else { - Conscrypt.setApplicationProtocolSelector(sslEngine, new Selector()); - } - } - - @Override - public String getApplicationProtocol() { - return Optional.ofNullable(Conscrypt.getApplicationProtocol(sslEngine)).orElse(""); - } - - @Override - public List getSupportedApplicationProtocols() { - return supportedProtocolList; - } - - private final class Selector extends org.conscrypt.ApplicationProtocolSelector { - - @Override - public String selectApplicationProtocol(SSLEngine sslEngine, List list) { - return select(list); - } - - @Override - public String selectApplicationProtocol(SSLSocket sslSocket, List list) { - return select(list); - } - - String select(List clientProtocols) { - if (clientProtocols != null) { - for (String p : supportedProtocols) { - if (clientProtocols.contains(p)) { - LOG.debug(() -> "ALPN local server selected protocol -> " + p); - return p; - } - } - } - return null; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/ConscryptSecureEngine.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/ConscryptSecureEngine.java deleted file mode 100644 index 1ad23ff5d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/ConscryptSecureEngine.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - -import com.fireflysource.net.tcp.secure.AbstractAsyncSecureEngine; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import kotlinx.coroutines.CoroutineScope; - -import javax.net.ssl.SSLEngine; - -/** - * @author Pengtao Qiu - */ -public class ConscryptSecureEngine extends AbstractAsyncSecureEngine { - - public ConscryptSecureEngine( - CoroutineScope coroutineScope, - SSLEngine sslEngine, - ApplicationProtocolSelector applicationProtocolSelector) { - super(coroutineScope, sslEngine, applicationProtocolSelector); - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/DefaultConscryptSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/DefaultConscryptSSLContextFactory.java deleted file mode 100644 index 8adb9c167..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/DefaultConscryptSSLContextFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - -import javax.net.ssl.SSLContext; - -/** - * @author Pengtao Qiu - */ -public class DefaultConscryptSSLContextFactory extends AbstractConscryptSecureEngineFactory { - - private SSLContext sslContext; - - public DefaultConscryptSSLContextFactory() { - try { - sslContext = getSSLContextWithManager(null, null, null); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/FileConscryptSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/FileConscryptSSLContextFactory.java deleted file mode 100644 index 78ee372b4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/FileConscryptSSLContextFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - -import javax.net.ssl.SSLContext; -import java.io.InputStream; - -/** - * @author Pengtao Qiu - */ -public class FileConscryptSSLContextFactory extends AbstractConscryptSecureEngineFactory { - - private SSLContext sslContext; - - public FileConscryptSSLContextFactory(String path, String keystorePassword, String keyPassword, String keyStoreType) { - this(FileConscryptSSLContextFactory.class.getClassLoader().getResourceAsStream(path), keystorePassword, keyPassword, - keyStoreType, null, null, null); - } - - public FileConscryptSSLContextFactory( - InputStream inputStream, String keystorePassword, String keyPassword, - String keyStoreType, - String keyManagerFactoryType, - String trustManagerFactoryType, - String sslProtocol) { - try (InputStream in = inputStream) { - sslContext = getSSLContext(in, keystorePassword, keyPassword, - keyStoreType, keyManagerFactoryType, trustManagerFactoryType, sslProtocol); - } catch (Exception e) { - LOG.error("get SSL context exception", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/NoCheckConscryptSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/NoCheckConscryptSSLContextFactory.java deleted file mode 100644 index 0a4699324..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/NoCheckConscryptSSLContextFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - - -import com.fireflysource.net.tcp.secure.utils.SecureUtils; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -/** - * @author Pengtao Qiu - */ -public class NoCheckConscryptSSLContextFactory extends AbstractConscryptSecureEngineFactory { - - private SSLContext sslContext; - - public NoCheckConscryptSSLContextFactory() { - try { - sslContext = getSSLContextWithManager(null, new TrustManager[]{SecureUtils.createX509TrustManagerNoCheck()}, null); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/SelfSignedCertificateConscryptSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/SelfSignedCertificateConscryptSSLContextFactory.java deleted file mode 100644 index a604b13da..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/conscrypt/SelfSignedCertificateConscryptSSLContextFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.net.tcp.secure.conscrypt; - - -import com.fireflysource.net.tcp.secure.utils.SecureUtils; - -import javax.net.ssl.SSLContext; -import java.io.InputStream; - -import static com.fireflysource.net.tcp.secure.utils.SecureUtils.*; - -/** - * @author Pengtao Qiu - */ -public class SelfSignedCertificateConscryptSSLContextFactory extends AbstractConscryptSecureEngineFactory { - - private SSLContext sslContext; - - public SelfSignedCertificateConscryptSSLContextFactory() { - try (InputStream in = SecureUtils.getSelfSignedCertificate()) { - sslContext = getSSLContext(in, SELF_SIGNED_KEY_STORE_PASSWORD, SELF_SIGNED_KEY_PASSWORD, SELF_SIGNED_KEY_STORE_TYPE); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/exception/SecureNetException.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/exception/SecureNetException.java deleted file mode 100644 index 1306e45f7..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/exception/SecureNetException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.net.tcp.secure.exception; - -/** - * @author Pengtao Qiu - */ -public class SecureNetException extends IllegalStateException { - public SecureNetException(String msg) { - super(msg); - } - - public SecureNetException(String msg, Throwable t) { - super(msg, t); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/AbstractOpenJdkSecureEngineFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/AbstractOpenJdkSecureEngineFactory.java deleted file mode 100644 index 02ae96276..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/AbstractOpenJdkSecureEngineFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.JavaVersion; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import com.fireflysource.net.tcp.secure.SecureEngine; -import com.fireflysource.net.tcp.secure.common.AbstractSecureEngineFactory; -import kotlinx.coroutines.CoroutineScope; -import org.openjsse.net.ssl.OpenJSSE; - -import javax.net.ssl.SSLEngine; -import java.security.Provider; -import java.security.Security; -import java.util.List; - -abstract public class AbstractOpenJdkSecureEngineFactory extends AbstractSecureEngineFactory { - - protected static final LazyLogger LOG = SystemLogger.create(AbstractOpenJdkSecureEngineFactory.class); - - private static final String SECURE_PROTOCOL = "TLSv1.3"; - private static final String PROVIDER_NAME; - - static { - if (JavaVersion.VERSION.getPlatform() < 9) { - Provider provider = new OpenJSSE(); - PROVIDER_NAME = provider.getName(); - Security.addProvider(provider); - LOG.info("Add Openjsse security provider. info: {}, name: {}", provider.getInfo(), PROVIDER_NAME); - } else { - PROVIDER_NAME = "SunJSSE"; - Provider provider = Security.getProvider(PROVIDER_NAME); - LOG.info("Select {} security provider. info: {}", PROVIDER_NAME, provider.getInfo()); - } - } - - @Override - public String getProviderName() { - return PROVIDER_NAME; - } - - @Override - public String getSecureProtocol() { - return SECURE_PROTOCOL; - } - - @Override - public SecureEngine createSecureEngine( - CoroutineScope coroutineScope, - SSLEngine sslEngine, - ApplicationProtocolSelector applicationProtocolSelector) { - return new OpenJdkSecureEngine(coroutineScope, sslEngine, applicationProtocolSelector); - } - - @Override - public ApplicationProtocolSelector createApplicationProtocolSelector( - SSLEngine sslEngine, List supportedProtocolList) { - return new OpenJdkApplicationProtocolSelector(sslEngine, supportedProtocolList); - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/DefaultOpenJdkSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/DefaultOpenJdkSSLContextFactory.java deleted file mode 100644 index 6c09368fc..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/DefaultOpenJdkSSLContextFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - -import javax.net.ssl.SSLContext; - -/** - * @author Pengtao Qiu - */ -public class DefaultOpenJdkSSLContextFactory extends AbstractOpenJdkSecureEngineFactory { - - private SSLContext sslContext; - - public DefaultOpenJdkSSLContextFactory() { - try { - sslContext = getSSLContextWithManager(null, null, null); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/FileOpenJdkSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/FileOpenJdkSSLContextFactory.java deleted file mode 100644 index 64d957f6d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/FileOpenJdkSSLContextFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - -import javax.net.ssl.SSLContext; -import java.io.InputStream; - -/** - * @author Pengtao Qiu - */ -public class FileOpenJdkSSLContextFactory extends AbstractOpenJdkSecureEngineFactory { - - private SSLContext sslContext; - - public FileOpenJdkSSLContextFactory(String path, String keystorePassword, String keyPassword, String keyStoreType) { - this(FileOpenJdkSSLContextFactory.class.getClassLoader().getResourceAsStream(path), - keystorePassword, keyPassword, - keyStoreType, null, null, null); - } - - public FileOpenJdkSSLContextFactory( - InputStream inputStream, String keystorePassword, String keyPassword, - String keyStoreType, - String keyManagerFactoryType, - String trustManagerFactoryType, - String sslProtocol) { - try (InputStream in = inputStream) { - sslContext = getSSLContext(in, keystorePassword, keyPassword, - keyStoreType, keyManagerFactoryType, trustManagerFactoryType, sslProtocol); - } catch (Exception e) { - LOG.error("get SSL context exception", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/NoCheckOpenJdkSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/NoCheckOpenJdkSSLContextFactory.java deleted file mode 100644 index c9aca1a5e..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/NoCheckOpenJdkSSLContextFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - - -import com.fireflysource.net.tcp.secure.utils.SecureUtils; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -/** - * @author Pengtao Qiu - */ -public class NoCheckOpenJdkSSLContextFactory extends AbstractOpenJdkSecureEngineFactory { - - private SSLContext sslContext; - - public NoCheckOpenJdkSSLContextFactory() { - try { - sslContext = getSSLContextWithManager(null, new TrustManager[]{SecureUtils.createX509TrustManagerNoCheck()}, null); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/OpenJdkApplicationProtocolSelector.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/OpenJdkApplicationProtocolSelector.java deleted file mode 100644 index 93ce77721..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/OpenJdkApplicationProtocolSelector.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.JavaVersion; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLParameters; -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.BiFunction; - -public class OpenJdkApplicationProtocolSelector implements ApplicationProtocolSelector { - - private static final LazyLogger LOG = SystemLogger.create(OpenJdkApplicationProtocolSelector.class); - private static Method setApplicationProtocols; - private static Method setHandshakeApplicationProtocolSelector; - private static Method getApplicationProtocol; - - private final List supportedProtocolList; - private final SSLEngine sslEngine; - - static { - try { - if (JavaVersion.VERSION.getPlatform() < 9) { - setApplicationProtocols = org.openjsse.javax.net.ssl.SSLParameters.class.getMethod("setApplicationProtocols", String[].class); - Class sslEngineImpl = Class.forName("org.openjsse.sun.security.ssl.SSLEngineImpl"); - setHandshakeApplicationProtocolSelector = sslEngineImpl.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class); - getApplicationProtocol = sslEngineImpl.getMethod("getApplicationProtocol"); - } else { - setApplicationProtocols = SSLParameters.class.getMethod("setApplicationProtocols", String[].class); - setHandshakeApplicationProtocolSelector = SSLEngine.class.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class); - getApplicationProtocol = SSLEngine.class.getMethod("getApplicationProtocol"); - } - setApplicationProtocols.setAccessible(true); - setHandshakeApplicationProtocolSelector.setAccessible(true); - getApplicationProtocol.setAccessible(true); - } catch (Exception e) { - LOG.error("Init openjsse application protocol selector exception", e); - } - } - - public OpenJdkApplicationProtocolSelector(SSLEngine sslEngine, List supportedProtocolList) { - this.sslEngine = sslEngine; - this.supportedProtocolList = supportedProtocolList; - - try { - if (sslEngine.getUseClientMode()) { - SSLParameters parameters; - if (JavaVersion.VERSION.getPlatform() < 9) { - parameters = new org.openjsse.javax.net.ssl.SSLParameters(); - } else { - parameters = new SSLParameters(); - } - setApplicationProtocols.invoke(parameters, new Object[]{supportedProtocolList.toArray(StringUtils.EMPTY_STRING_ARRAY)}); - sslEngine.setSSLParameters(parameters); - - } else { - BiFunction, String> selector = (serverEngine, clientProtocols) -> { - if (clientProtocols != null) { - for (String p : this.supportedProtocolList) { - if (clientProtocols.contains(p)) { - LOG.debug(() -> "ALPN local server selected protocol -> " + p); - return p; - } - } - } - return null; - }; - setHandshakeApplicationProtocolSelector.invoke(sslEngine, selector); - } - } catch (Exception e) { - LOG.error("Init openjsse application protocol selector exception", e); - } - } - - @Override - public String getApplicationProtocol() { - String protocol; - try { - protocol = (String) getApplicationProtocol.invoke(sslEngine); - } catch (Exception e) { - LOG.error("Get application protocol exception", e); - protocol = null; - } - return protocol; - } - - @Override - public List getSupportedApplicationProtocols() { - return supportedProtocolList; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/OpenJdkSecureEngine.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/OpenJdkSecureEngine.java deleted file mode 100644 index 53607f2b1..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/OpenJdkSecureEngine.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - -import com.fireflysource.net.tcp.secure.AbstractAsyncSecureEngine; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import kotlinx.coroutines.CoroutineScope; - -import javax.net.ssl.SSLEngine; - -public class OpenJdkSecureEngine extends AbstractAsyncSecureEngine { - - public OpenJdkSecureEngine( - CoroutineScope coroutineScope, - SSLEngine sslEngine, - ApplicationProtocolSelector applicationProtocolSelector) { - super(coroutineScope, sslEngine, applicationProtocolSelector); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/SelfSignedCertificateOpenJdkSSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/SelfSignedCertificateOpenJdkSSLContextFactory.java deleted file mode 100644 index f9a9715b9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/jdk/SelfSignedCertificateOpenJdkSSLContextFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.net.tcp.secure.jdk; - - -import com.fireflysource.net.tcp.secure.utils.SecureUtils; - -import javax.net.ssl.SSLContext; -import java.io.InputStream; - -import static com.fireflysource.net.tcp.secure.utils.SecureUtils.*; - -/** - * @author Pengtao Qiu - */ -public class SelfSignedCertificateOpenJdkSSLContextFactory extends AbstractOpenJdkSecureEngineFactory { - - private SSLContext sslContext; - - public SelfSignedCertificateOpenJdkSSLContextFactory() { - try (InputStream in = SecureUtils.getSelfSignedCertificate()) { - sslContext = getSSLContext(in, SELF_SIGNED_KEY_STORE_PASSWORD, SELF_SIGNED_KEY_PASSWORD, SELF_SIGNED_KEY_STORE_TYPE); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/utils/SecureUtils.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/utils/SecureUtils.java deleted file mode 100644 index 649177d52..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/utils/SecureUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.fireflysource.net.tcp.secure.utils; - -import javax.net.ssl.X509TrustManager; -import java.io.InputStream; -import java.security.cert.X509Certificate; - -/** - * @author Pengtao Qiu - */ -abstract public class SecureUtils { - - public static final String SELF_SIGNED_KEY_STORE_TYPE = "jks"; - public static final String SELF_SIGNED_KEY_STORE_PASSWORD = "123456"; - public static final String SELF_SIGNED_KEY_PASSWORD = "654321"; - public static final String KEY_MANAGER_FACTORY_TYPE = "SunX509"; // // PKIX, SunX509 - public static final String TRUST_MANAGER_FACTORY_TYPE = "SunX509"; - - public static InputStream getSelfSignedCertificate() { - return SecureUtils.class.getClassLoader().getResourceAsStream("fireflyKeystore.jks"); - } - - public static X509TrustManager createX509TrustManagerNoCheck() { - return new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/AbstractWildflySecureEngineFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/AbstractWildflySecureEngineFactory.java deleted file mode 100644 index 339f98639..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/AbstractWildflySecureEngineFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import com.fireflysource.net.tcp.secure.SecureEngine; -import com.fireflysource.net.tcp.secure.common.AbstractSecureEngineFactory; -import kotlinx.coroutines.CoroutineScope; -import org.wildfly.openssl.OpenSSLProvider; - -import javax.net.ssl.SSLEngine; -import java.util.List; - -abstract public class AbstractWildflySecureEngineFactory extends AbstractSecureEngineFactory { - - protected static final LazyLogger LOG = SystemLogger.create(AbstractWildflySecureEngineFactory.class); - - private static final String SECURE_PROTOCOL = "openssl.TLS"; - private static final String PROVIDER_NAME; - - static { - OpenSSLProvider.register(); - PROVIDER_NAME = OpenSSLProvider.INSTANCE.getName(); - LOG.info("Add wildfly openssl security provider. info: {}, name: {}", OpenSSLProvider.INSTANCE.getInfo(), PROVIDER_NAME); - } - - @Override - public String getSecureProtocol() { - return SECURE_PROTOCOL; - } - - @Override - public String getProviderName() { - return PROVIDER_NAME; - } - - @Override - public SecureEngine createSecureEngine(CoroutineScope coroutineScope, SSLEngine sslEngine, ApplicationProtocolSelector applicationProtocolSelector) { - return new WildflySecureEngine(coroutineScope, sslEngine, applicationProtocolSelector); - } - - @Override - public ApplicationProtocolSelector createApplicationProtocolSelector(SSLEngine sslEngine, List supportedProtocolList) { - return new WildflyOpenSSLApplicationProtocolSelector(sslEngine, supportedProtocolList); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/DefaultWildflySSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/DefaultWildflySSLContextFactory.java deleted file mode 100644 index b9aee7dfb..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/DefaultWildflySSLContextFactory.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import javax.net.ssl.SSLContext; - -public class DefaultWildflySSLContextFactory extends AbstractWildflySecureEngineFactory { - private SSLContext sslContext; - - public DefaultWildflySSLContextFactory() { - try { - sslContext = getSSLContextWithManager(null, null, null); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/FileWildflySSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/FileWildflySSLContextFactory.java deleted file mode 100644 index a6d2bdf4e..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/FileWildflySSLContextFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import com.fireflysource.net.tcp.secure.jdk.FileOpenJdkSSLContextFactory; - -import javax.net.ssl.SSLContext; -import java.io.InputStream; - -public class FileWildflySSLContextFactory extends AbstractWildflySecureEngineFactory { - private SSLContext sslContext; - - public FileWildflySSLContextFactory(String path, String keystorePassword, String keyPassword, String keyStoreType) { - this(FileOpenJdkSSLContextFactory.class.getClassLoader().getResourceAsStream(path), - keystorePassword, keyPassword, - keyStoreType, null, null, null); - } - - public FileWildflySSLContextFactory( - InputStream inputStream, String keystorePassword, String keyPassword, - String keyStoreType, - String keyManagerFactoryType, - String trustManagerFactoryType, - String sslProtocol) { - try (InputStream in = inputStream) { - sslContext = getSSLContext(in, keystorePassword, keyPassword, - keyStoreType, keyManagerFactoryType, trustManagerFactoryType, sslProtocol); - } catch (Exception e) { - LOG.error("get SSL context exception", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/NoCheckWildflySSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/NoCheckWildflySSLContextFactory.java deleted file mode 100644 index 657598d0a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/NoCheckWildflySSLContextFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import com.fireflysource.net.tcp.secure.utils.SecureUtils; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -public class NoCheckWildflySSLContextFactory extends AbstractWildflySecureEngineFactory{ - - private SSLContext sslContext; - - public NoCheckWildflySSLContextFactory() { - try { - sslContext = getSSLContextWithManager(null, new TrustManager[]{SecureUtils.createX509TrustManagerNoCheck()}, null); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/SelfSignedCertificateWildflySSLContextFactory.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/SelfSignedCertificateWildflySSLContextFactory.java deleted file mode 100644 index 5cf58326a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/SelfSignedCertificateWildflySSLContextFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import com.fireflysource.net.tcp.secure.utils.SecureUtils; - -import javax.net.ssl.SSLContext; -import java.io.InputStream; - -import static com.fireflysource.net.tcp.secure.utils.SecureUtils.*; - -public class SelfSignedCertificateWildflySSLContextFactory extends AbstractWildflySecureEngineFactory { - - private SSLContext sslContext; - - public SelfSignedCertificateWildflySSLContextFactory() { - try (InputStream in = SecureUtils.getSelfSignedCertificate()) { - sslContext = getSSLContext(in, SELF_SIGNED_KEY_STORE_PASSWORD, SELF_SIGNED_KEY_PASSWORD, SELF_SIGNED_KEY_STORE_TYPE); - } catch (Throwable e) { - LOG.error("get SSL context error", e); - } - } - - @Override - public SSLContext getSSLContext() { - return sslContext; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/WildflyOpenSSLApplicationProtocolSelector.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/WildflyOpenSSLApplicationProtocolSelector.java deleted file mode 100644 index 0778a2535..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/WildflyOpenSSLApplicationProtocolSelector.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; - -import javax.net.ssl.SSLEngine; -import java.lang.reflect.Method; -import java.util.List; -import java.util.function.BiFunction; - -public class WildflyOpenSSLApplicationProtocolSelector implements ApplicationProtocolSelector { - - private static final LazyLogger LOG = SystemLogger.create(WildflyOpenSSLApplicationProtocolSelector.class); - private static Method setApplicationProtocols; - private static Method setHandshakeApplicationProtocolSelector; - private static Method getApplicationProtocol; - - private final SSLEngine sslEngine; - private final List supportedProtocolList; - - static { - try { - Class sslEngineImpl = Class.forName("org.wildfly.openssl.OpenSSLEngine"); - setApplicationProtocols = sslEngineImpl.getMethod("setApplicationProtocols", String[].class); - setHandshakeApplicationProtocolSelector = sslEngineImpl.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class); - getApplicationProtocol = sslEngineImpl.getMethod("getApplicationProtocol"); - setApplicationProtocols.setAccessible(true); - setHandshakeApplicationProtocolSelector.setAccessible(true); - getApplicationProtocol.setAccessible(true); - } catch (Exception e) { - LOG.error("Get wildfly openssl application protocol selector methods exception.", e); - } - } - - public WildflyOpenSSLApplicationProtocolSelector(SSLEngine sslEngine, List supportedProtocolList) { - this.sslEngine = sslEngine; - this.supportedProtocolList = supportedProtocolList; - - try { - if (sslEngine.getUseClientMode()) { - setApplicationProtocols.invoke(sslEngine, new Object[]{supportedProtocolList.toArray(StringUtils.EMPTY_STRING_ARRAY)}); - } else { - BiFunction, String> selector = (serverEngine, clientProtocols) -> { - if (clientProtocols != null) { - for (String p : this.supportedProtocolList) { - if (clientProtocols.contains(p)) { - LOG.debug(() -> "ALPN local server selected protocol -> " + p); - return p; - } - } - } - return null; - }; - setHandshakeApplicationProtocolSelector.invoke(sslEngine, selector); - } - } catch (Exception e) { - LOG.error("Init wildfly openssl application protocol selector exception.", e); - } - } - - @Override - public String getApplicationProtocol() { - String protocol; - try { - protocol = (String) getApplicationProtocol.invoke(sslEngine); - } catch (Exception e) { - LOG.error("Get application protocol exception", e); - protocol = null; - } - return protocol; - } - - @Override - public List getSupportedApplicationProtocols() { - return supportedProtocolList; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/WildflySecureEngine.java b/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/WildflySecureEngine.java deleted file mode 100644 index 738d7dc23..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/tcp/secure/wildfly/WildflySecureEngine.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.net.tcp.secure.wildfly; - -import com.fireflysource.net.tcp.secure.AbstractAsyncSecureEngine; -import com.fireflysource.net.tcp.secure.ApplicationProtocolSelector; -import kotlinx.coroutines.CoroutineScope; - -import javax.net.ssl.SSLEngine; - -public class WildflySecureEngine extends AbstractAsyncSecureEngine { - - public WildflySecureEngine( - CoroutineScope coroutineScope, - SSLEngine sslEngine, - ApplicationProtocolSelector applicationProtocolSelector) { - super(coroutineScope, sslEngine, applicationProtocolSelector); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpClient.java b/firefly-net/src/main/java/com/fireflysource/net/udp/UdpClient.java deleted file mode 100644 index bf26a524b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.net.udp; - -import java.net.SocketAddress; - -public interface UdpClient { - - /** - * Create a UDP connection. - * - * @param address The server address. - * @return The UDP connection. - */ - UdpConnection connect(SocketAddress address); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpConnection.java b/firefly-net/src/main/java/com/fireflysource/net/udp/UdpConnection.java deleted file mode 100644 index f8fe12ef2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpConnection.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.fireflysource.net.udp; - -import com.fireflysource.common.io.AsyncCloseable; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.Connection; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -import static com.fireflysource.common.sys.Result.futureToConsumer; - -/** - * The UDP connection. It reads or writes messages using the UDP (or DTLS over the UDP) protocol. - */ -public interface UdpConnection extends Connection, AsyncCloseable { - - Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - - /** - * Read data from the remote endpoint. - * - * @return The future data. - */ - CompletableFuture read(); - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffer The byte buffer. - * @param result The handler for consuming the result. - * @return The current connection. - */ - UdpConnection write(ByteBuffer byteBuffer, Consumer> result); - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffers The byte buffer array. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @param result The handler for consuming the result. - * @return The current connection. - */ - UdpConnection write(ByteBuffer[] byteBuffers, int offset, int length, Consumer> result); - - /** - * Write the data to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @param result The handler for consuming the result. - * @return The current connection. - */ - UdpConnection write(List byteBufferList, int offset, int length, Consumer> result); - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffer The byte buffer. - * @return The future for consuming the result. - */ - default CompletableFuture write(ByteBuffer byteBuffer) { - CompletableFuture future = new CompletableFuture<>(); - write(byteBuffer, futureToConsumer(future)); - return future; - } - - /** - * Write and flush data to the remote endpoint. - * - * @param byteBuffer The byte buffer. - * @return The future for consuming the result. - */ - default CompletableFuture writeAndFlush(ByteBuffer byteBuffer) { - return write(byteBuffer).thenCompose(len -> flush().thenApply(n -> len)); - } - - /** - * Write the data to the remote endpoint. - * - * @param byteBuffers The byte buffer array. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBuffers.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBuffers.length - offset. - * @return The future for consuming the result. - */ - default CompletableFuture write(ByteBuffer[] byteBuffers, int offset, int length) { - CompletableFuture future = new CompletableFuture<>(); - write(byteBuffers, offset, length, futureToConsumer(future)); - return future; - } - - /** - * Write the data to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @return The future for consuming the result. - */ - default CompletableFuture write(List byteBufferList, int offset, int length) { - CompletableFuture future = new CompletableFuture<>(); - write(byteBufferList, offset, length, futureToConsumer(future)); - return future; - } - - /** - * Write and flush data to the remote endpoint. - * - * @param byteBufferList The byte buffer list. - * @param offset The offset within the buffer array of the first buffer into which - * bytes are to be transferred; must be non-negative and no larger than - * byteBufferList.length. - * @param length The maximum number of buffers to be accessed; must be non-negative - * and no larger than byteBufferList.length - offset. - * @return The future for consuming the result. - */ - default CompletableFuture writeAndFlush(List byteBufferList, int offset, int length) { - return write(byteBufferList, offset, length).thenCompose(len -> flush().thenApply(n -> len)); - } - - /** - * Write the data to the remote endpoint. - * - * @param bytes The byte array. - * @param result The handler for consuming the result. - * @return The current connection. - */ - default UdpConnection write(byte[] bytes, Consumer> result) { - return write(ByteBuffer.wrap(bytes), result); - } - - /** - * Write the data to the remote endpoint. - * - * @param string The string. - * @param result The handler for consuming the result. - * @return The current connection. - */ - default UdpConnection write(String string, Consumer> result) { - return write(ByteBuffer.wrap(string.getBytes(DEFAULT_CHARSET)), result); - } - - /** - * Flush output buffer to remote endpoint. - * - * @param result When flush data to remote endpoint, the framework will invoke this function. - * @return The current connection. - */ - UdpConnection flush(Consumer> result); - - /** - * Flush output buffer to remote endpoint. - * - * @return The future result. - */ - default CompletableFuture flush() { - CompletableFuture future = new CompletableFuture<>(); - flush(futureToConsumer(future)); - return future; - } - - /** - * Get output buffer size. - * - * @return The output buffer size. - */ - int getBufferSize(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpCoroutineDispatcher.java b/firefly-net/src/main/java/com/fireflysource/net/udp/UdpCoroutineDispatcher.java deleted file mode 100644 index be592635f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpCoroutineDispatcher.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.fireflysource.net.udp; - -import kotlinx.coroutines.CompletableJob; -import kotlinx.coroutines.CoroutineDispatcher; -import kotlinx.coroutines.CoroutineScope; - -import java.util.concurrent.Executor; - -public interface UdpCoroutineDispatcher extends Executor { - - /** - * Get the coroutine dispatcher of this connection. One TCP connection is always in the same coroutine context. - * - * @return The coroutine dispatcher of this connection. - */ - CoroutineDispatcher getCoroutineDispatcher(); - - /** - * Get the coroutine scope of this connection. - * - * @return The coroutine scope. - */ - CoroutineScope getCoroutineScope(); - - /** - * Get the supervisor job. - * - * @return The supervisor job. - */ - CompletableJob getSupervisorJob(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpServer.java b/firefly-net/src/main/java/com/fireflysource/net/udp/UdpServer.java deleted file mode 100644 index e8b6935ed..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/udp/UdpServer.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.udp; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.function.Consumer; - -public interface UdpServer { - - /** - * The UDP connection open event listener. - * - * @param consumer The UDP connection. - * @return The UDP server. - */ - UdpServer onOpen(Consumer consumer); - - /** - * Bind a server UDP address - * - * @param address The server UDP address. - * @return The UDP server. - */ - UdpServer listen(SocketAddress address); - - /** - * Bind a server UDP address - * - * @param host The server host. - * @param port The server port. - * @return The UDP server. - */ - default UdpServer listen(String host, int port) { - return listen(new InetSocketAddress(host, port)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/udp/exception/UdpAttachmentTypeException.java b/firefly-net/src/main/java/com/fireflysource/net/udp/exception/UdpAttachmentTypeException.java deleted file mode 100644 index 9459b951c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/udp/exception/UdpAttachmentTypeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fireflysource.net.udp.exception; - -public class UdpAttachmentTypeException extends RuntimeException { - public UdpAttachmentTypeException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientConnectionBuilder.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientConnectionBuilder.java deleted file mode 100644 index f3ef9c6bf..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientConnectionBuilder.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.fireflysource.net.websocket.client; - -import com.fireflysource.net.websocket.common.WebSocketConnection; -import com.fireflysource.net.websocket.common.WebSocketMessageHandler; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * @author Pengtao Qiu - */ -public interface WebSocketClientConnectionBuilder { - - /** - * Set the websocket url. - * - * @param url The websocket url. - * @return The websocket client connection builder. - */ - WebSocketClientConnectionBuilder url(String url); - - /** - * Set the websocket policy. - * - * @param policy The websocket policy. - * @return The websocket client connection builder. - */ - WebSocketClientConnectionBuilder policy(WebSocketPolicy policy); - - /** - * Put the websocket extensions. - * - * @param extensions The websocket extensions. - * @return The websocket client connection builder. - */ - WebSocketClientConnectionBuilder extensions(List extensions); - - /** - * Put the websocket sub protocols. - * - * @param subProtocols The websocket sub protocols. - * @return The websocket client connection builder. - */ - WebSocketClientConnectionBuilder subProtocols(List subProtocols); - - /** - * Set the websocket message handler. - * - * @param handler The websocket handler. - * @return The websocket client connection builder. - */ - WebSocketClientConnectionBuilder onMessage(WebSocketMessageHandler handler); - - /** - * Create the websocket connection. - * - * @return The future websocket connection. - */ - CompletableFuture connect(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientConnectionManager.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientConnectionManager.java deleted file mode 100644 index c1b3ecc77..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientConnectionManager.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.fireflysource.net.websocket.client; - -import com.fireflysource.net.websocket.common.WebSocketConnection; - -import java.util.concurrent.CompletableFuture; - -/** - * @author Pengtao Qiu - */ -public interface WebSocketClientConnectionManager { - - /** - * Create a websocket connection. - * - * @param request The websocket connection request. - * @return The websocket connection future. - */ - CompletableFuture connect(WebSocketClientRequest request); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientRequest.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientRequest.java deleted file mode 100644 index 9b4956667..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/client/WebSocketClientRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.fireflysource.net.websocket.client; - -import com.fireflysource.net.websocket.common.WebSocketMessageHandler; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -import java.util.List; - -/** - * @author Pengtao Qiu - */ -public class WebSocketClientRequest { - - private String url; - private WebSocketPolicy policy; - private List extensions; - private List subProtocols; - private WebSocketMessageHandler handler; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public WebSocketPolicy getPolicy() { - return policy; - } - - public void setPolicy(WebSocketPolicy policy) { - this.policy = policy; - } - - public List getExtensions() { - return extensions; - } - - public void setExtensions(List extensions) { - this.extensions = extensions; - } - - public List getSubProtocols() { - return subProtocols; - } - - public void setSubProtocols(List subProtocols) { - this.subProtocols = subProtocols; - } - - public WebSocketMessageHandler getHandler() { - return handler; - } - - public void setHandler(WebSocketMessageHandler handler) { - this.handler = handler; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketConnection.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketConnection.java deleted file mode 100644 index 68417e228..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketConnection.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.fireflysource.net.websocket.common; - -import com.fireflysource.net.Connection; -import com.fireflysource.net.tcp.TcpCoroutineDispatcher; -import com.fireflysource.net.websocket.common.extension.ExtensionFactory; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The websocket connection. - * - * @author Pengtao Qiu - */ -public interface WebSocketConnection extends Connection, TcpCoroutineDispatcher, WebSocketConnectionState { - - /** - * Get the absolute URL of the WebSocket. - * - * @return The absolute URL of the WebSocket. - */ - String getUrl(); - - /** - * Get the websocket extensions. - * - * @return The websocket extensions. - */ - List getExtensions(); - - /** - * Get the websocket sub protocols. - * - * @return The websocket sub protocols. - */ - List getSubProtocols(); - - /** - * Get the policy that the connection is running under. - * - * @return the policy for the connection - */ - WebSocketPolicy getPolicy(); - - /** - * Get the websocket extension factory. - * - * @return The websocket extension factory. - */ - ExtensionFactory getExtensionFactory(); - - /** - * Generate random 4bytes mask key - * - * @return the mask key - */ - byte[] generateMask(); - - /** - * Send text message. - * - * @param text The text message. - * @return The future result. - */ - CompletableFuture sendText(String text); - - /** - * Send binary message. - * - * @param data The binary message. - * @return The future result. - */ - CompletableFuture sendData(ByteBuffer data); - - /** - * Send websocket frame. - * - * @param frame The websocket frame. - * @return The future result. - */ - CompletableFuture sendFrame(Frame frame); - - /** - * Set the websocket message handler. - * - * @param handler The websocket message handler. - */ - void setWebSocketMessageHandler(WebSocketMessageHandler handler); - - /** - * Begin to receive websocket data. - */ - void begin(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketConnectionState.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketConnectionState.java deleted file mode 100644 index 0f9f7d4f7..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketConnectionState.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.websocket.common; - -import com.fireflysource.net.websocket.common.stream.ConnectionState; - -/** - * @author Pengtao Qiu - */ -public interface WebSocketConnectionState { - - boolean isInputAvailable(); - - boolean isOutputAvailable(); - - ConnectionState getConnectionState(); - - boolean isRemoteCloseInitiated(); - - boolean isLocalCloseInitiated(); - - boolean isAbnormalClose(); - - boolean isCleanClose(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketMessageHandler.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketMessageHandler.java deleted file mode 100644 index f9c9a0290..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/WebSocketMessageHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.fireflysource.net.websocket.common; - -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.util.concurrent.CompletableFuture; - -/** - * The websocket message handler. - * - * @author Pengtao Qiu - */ -public interface WebSocketMessageHandler { - - CompletableFuture handle(Frame frame, WebSocketConnection connection); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/Parser.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/Parser.java deleted file mode 100644 index 29bcfca53..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/Parser.java +++ /dev/null @@ -1,529 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.decoder.payload.DeMaskProcessor; -import com.fireflysource.net.websocket.common.decoder.payload.PayloadProcessor; -import com.fireflysource.net.websocket.common.exception.MessageTooLargeException; -import com.fireflysource.net.websocket.common.exception.ProtocolException; -import com.fireflysource.net.websocket.common.exception.WebSocketException; -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.model.*; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.concurrent.atomic.LongAdder; - -import static com.fireflysource.net.websocket.common.extension.AbstractExtension.getFlags; - -/** - * Parsing of a frames in WebSocket land. - */ -public class Parser { - private enum State { - START, - PAYLOAD_LEN, - PAYLOAD_LEN_BYTES, - MASK, - MASK_BYTES, - PAYLOAD - } - - private static final LazyLogger LOG = SystemLogger.create(Parser.class); - private final WebSocketPolicy policy; - - // Stats (where a message is defined as a WebSocket frame) - private final LongAdder messagesIn = new LongAdder(); - - // State specific - private State state = State.START; - private int cursor = 0; - // Frame - private WebSocketFrame frame; - private boolean priorDataFrame; - // payload specific - private ByteBuffer payload; - private int payloadLength; - private final PayloadProcessor maskProcessor = new DeMaskProcessor(); - - /** - * Is there an extension using RSV flag? - *

    - * - *

    -     *   0100_0000 (0x40) = rsv1
    -     *   0010_0000 (0x20) = rsv2
    -     *   0001_0000 (0x10) = rsv3
    -     * 
    - */ - private byte flagsInUse = 0x00; - - private IncomingFrames incomingFramesHandler; - - public Parser(WebSocketPolicy policy) { - this.policy = policy; - } - - private void assertSanePayloadLength(long len) { - if (LOG.isDebugEnabled()) { - LOG.debug("{} Payload Length: {} - {}", policy.getBehavior(), len, this); - } - - // Since we use ByteBuffer so often, having lengths over Integer.MAX_VALUE is really impossible. - if (len > Integer.MAX_VALUE) { - // OMG! Sanity Check! DO NOT WANT! Won't anyone think of the memory! - throw new MessageTooLargeException("[int-sane!] cannot handle payload lengths larger than " + Integer.MAX_VALUE); - } - - switch (frame.getOpCode()) { - case OpCode.CLOSE: - if (len == 1) { - throw new ProtocolException("Invalid close frame payload length, [" + payloadLength + "]"); - } - case OpCode.PING: - case OpCode.PONG: - if (len > ControlFrame.MAX_CONTROL_PAYLOAD) { - throw new ProtocolException("Invalid control frame payload length, [" + payloadLength + "] cannot exceed [" + ControlFrame.MAX_CONTROL_PAYLOAD + "]"); - } - break; - case OpCode.TEXT: - policy.assertValidTextMessageSize((int) len); - break; - case OpCode.BINARY: - policy.assertValidBinaryMessageSize((int) len); - break; - } - } - - public void configureFromExtensions(List extensions) { - flagsInUse = getFlags(extensions); - } - - public IncomingFrames getIncomingFramesHandler() { - return incomingFramesHandler; - } - - public WebSocketPolicy getPolicy() { - return policy; - } - - public boolean isRsv1InUse() { - return (flagsInUse & 0x40) != 0; - } - - public boolean isRsv2InUse() { - return (flagsInUse & 0x20) != 0; - } - - public boolean isRsv3InUse() { - return (flagsInUse & 0x10) != 0; - } - - protected void notifyFrame(final Frame f) throws WebSocketException { - if (LOG.isDebugEnabled()) - LOG.debug("{} Notify {}", policy.getBehavior(), getIncomingFramesHandler()); - - if (policy.getBehavior() == WebSocketBehavior.SERVER) { - /* Parsing on server. - * - * Then you MUST make sure all incoming frames are masked! - * - * Technically, this test is in violation of RFC-6455, Section 5.1 - * http://tools.ietf.org/html/rfc6455#section-5.1 - * - * But we can't trust the client at this point, so Server opts to close - * the connection as a Protocol error. - */ - if (!f.isMasked()) { - throw new ProtocolException("Client MUST mask all frames (RFC-6455: Section 5.1)"); - } - } else if (policy.getBehavior() == WebSocketBehavior.CLIENT) { - // Required by RFC-6455 / Section 5.1 - if (f.isMasked()) { - throw new ProtocolException("Server MUST NOT mask any frames (RFC-6455: Section 5.1)"); - } - } - - if (incomingFramesHandler == null) { - if (LOG.isDebugEnabled()) - LOG.debug("No IncomingFrames Handler to notify"); - return; - } - try { - incomingFramesHandler.incomingFrame(f); - } catch (WebSocketException e) { - throw e; - } catch (Throwable t) { - throw new WebSocketException(t); - } - } - - public void parse(ByteBuffer buffer) throws WebSocketException { - while (buffer.hasRemaining()) { - parseSingleFrame(buffer); - } - } - - public void parseSingleFrame(ByteBuffer buffer) throws WebSocketException { - if (buffer.remaining() <= 0) - return; - - try { - // attempt to parse a frame from the buffer - if (parseFrame(buffer)) { - if (LOG.isDebugEnabled()) - LOG.debug("{} Parsed Frame: {}", policy.getBehavior(), frame); - - messagesIn.increment(); - notifyFrame(frame); - if (frame.isDataFrame()) { - priorDataFrame = !frame.isFin(); - } - reset(); - } - } catch (Throwable t) { - buffer.position(buffer.limit()); // consume remaining - reset(); - // need to throw for proper close behavior in connection - if (t instanceof WebSocketException) - throw t; - else - throw new WebSocketException(t); - } - } - - private void reset() { - frame = null; - payload = null; - } - - /** - * Parse the base framing protocol buffer. - *

    - * Note the first byte (fin,rsv1,rsv2,rsv3,opcode) are parsed by the {@link Parser#parse(ByteBuffer)} method - *

    - * Not overridable - * - * @param buffer the buffer to parse from. - * @return true if done parsing base framing protocol and ready for parsing of the payload. false if incomplete parsing of base framing protocol. - */ - private boolean parseFrame(ByteBuffer buffer) { - if (LOG.isDebugEnabled()) { - LOG.debug("{} Parsing {} bytes", policy.getBehavior(), buffer.remaining()); - } - while (buffer.hasRemaining()) { - switch (state) { - case START: { - // peek at byte - byte b = buffer.get(); - boolean fin = ((b & 0x80) != 0); - - byte opcode = (byte) (b & 0x0F); - - if (!OpCode.isKnown(opcode)) { - throw new ProtocolException("Unknown opcode: " + opcode); - } - - if (LOG.isDebugEnabled()) - LOG.debug("{} OpCode {}, fin={} rsv={}{}{}", - policy.getBehavior(), - OpCode.name(opcode), - fin, - (((b & 0x40) != 0) ? '1' : '.'), - (((b & 0x20) != 0) ? '1' : '.'), - (((b & 0x10) != 0) ? '1' : '.')); - - // base framing flags - switch (opcode) { - case OpCode.TEXT: - frame = new TextFrame(); - // data validation - if (priorDataFrame) { - throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION"); - } - break; - case OpCode.BINARY: - frame = new BinaryFrame(); - // data validation - if (priorDataFrame) { - throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION"); - } - break; - case OpCode.CONTINUATION: - frame = new ContinuationFrame(); - // continuation validation - if (!priorDataFrame) { - throw new ProtocolException("CONTINUATION frame without prior !FIN"); - } - // Be careful to use the original opcode - break; - case OpCode.CLOSE: - frame = new CloseFrame(); - // control frame validation - if (!fin) { - throw new ProtocolException("Fragmented Close Frame [" + OpCode.name(opcode) + "]"); - } - break; - case OpCode.PING: - frame = new PingFrame(); - // control frame validation - if (!fin) { - throw new ProtocolException("Fragmented Ping Frame [" + OpCode.name(opcode) + "]"); - } - break; - case OpCode.PONG: - frame = new PongFrame(); - // control frame validation - if (!fin) { - throw new ProtocolException("Fragmented Pong Frame [" + OpCode.name(opcode) + "]"); - } - break; - } - - frame.setFin(fin); - - // Are any flags set? - if ((b & 0x70) != 0) { - /* - * RFC 6455 Section 5.2 - * - * MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the - * negotiated extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_. - */ - if ((b & 0x40) != 0) { - if (isRsv1InUse()) - frame.setRsv1(true); - else { - String err = "RSV1 not allowed to be set"; - if (LOG.isDebugEnabled()) { - LOG.debug(err + ": Remaining buffer: {}", BufferUtils.toDetailString(buffer)); - } - throw new ProtocolException(err); - } - } - if ((b & 0x20) != 0) { - if (isRsv2InUse()) - frame.setRsv2(true); - else { - String err = "RSV2 not allowed to be set"; - if (LOG.isDebugEnabled()) { - LOG.debug(err + ": Remaining buffer: {}", BufferUtils.toDetailString(buffer)); - } - throw new ProtocolException(err); - } - } - if ((b & 0x10) != 0) { - if (isRsv3InUse()) - frame.setRsv3(true); - else { - String err = "RSV3 not allowed to be set"; - if (LOG.isDebugEnabled()) { - LOG.debug(err + ": Remaining buffer: {}", BufferUtils.toDetailString(buffer)); - } - throw new ProtocolException(err); - } - } - } - - state = State.PAYLOAD_LEN; - break; - } - - case PAYLOAD_LEN: { - byte b = buffer.get(); - frame.setMasked((b & 0x80) != 0); - payloadLength = (byte) (0x7F & b); - - if (payloadLength == 127) // 0x7F - { - // length 8 bytes (extended payload length) - payloadLength = 0; - state = State.PAYLOAD_LEN_BYTES; - cursor = 8; - break; // continue onto next state - } else if (payloadLength == 126) // 0x7E - { - // length 2 bytes (extended payload length) - payloadLength = 0; - state = State.PAYLOAD_LEN_BYTES; - cursor = 2; - break; // continue onto next state - } - - assertSanePayloadLength(payloadLength); - if (frame.isMasked()) { - state = State.MASK; - } else { - // special case for empty payloads (no more bytes left in buffer) - if (payloadLength == 0) { - state = State.START; - return true; - } - - maskProcessor.reset(frame); - state = State.PAYLOAD; - } - - break; - } - - case PAYLOAD_LEN_BYTES: { - byte b = buffer.get(); - --cursor; - payloadLength |= (b & 0xFF) << (8 * cursor); - if (cursor == 0) { - assertSanePayloadLength(payloadLength); - if (frame.isMasked()) { - state = State.MASK; - } else { - // special case for empty payloads (no more bytes left in buffer) - if (payloadLength == 0) { - state = State.START; - return true; - } - - maskProcessor.reset(frame); - state = State.PAYLOAD; - } - } - break; - } - - case MASK: { - byte[] m = new byte[4]; - frame.setMask(m); - if (buffer.remaining() >= 4) { - buffer.get(m, 0, 4); - // special case for empty payloads (no more bytes left in buffer) - if (payloadLength == 0) { - state = State.START; - return true; - } - - maskProcessor.reset(frame); - state = State.PAYLOAD; - } else { - state = State.MASK_BYTES; - cursor = 4; - } - break; - } - - case MASK_BYTES: { - byte b = buffer.get(); - frame.getMask()[4 - cursor] = b; - --cursor; - if (cursor == 0) { - // special case for empty payloads (no more bytes left in buffer) - if (payloadLength == 0) { - state = State.START; - return true; - } - - maskProcessor.reset(frame); - state = State.PAYLOAD; - } - break; - } - - case PAYLOAD: { - frame.assertValid(); - if (parsePayload(buffer)) { - // special check for close - if (frame.getOpCode() == OpCode.CLOSE) { - // TODO: yuck. Don't create an object to do validation checks! - new CloseInfo(frame); - } - state = State.START; - // we have a frame! - return true; - } - break; - } - } - } - - return false; - } - - /** - * Implementation specific parsing of a payload - * - * @param buffer the payload buffer - * @return true if payload is done reading, false if incomplete - */ - private boolean parsePayload(ByteBuffer buffer) { - if (payloadLength == 0) { - return true; - } - - if (buffer.hasRemaining()) { - // Create a small window of the incoming buffer to work with. - // this should only show the payload itself, and not any more - // bytes that could belong to the start of the next frame. - int bytesSoFar = payload == null ? 0 : payload.position(); - int bytesExpected = payloadLength - bytesSoFar; - int bytesAvailable = buffer.remaining(); - int windowBytes = Math.min(bytesAvailable, bytesExpected); - int limit = buffer.limit(); - buffer.limit(buffer.position() + windowBytes); - ByteBuffer window = buffer.slice(); - buffer.limit(limit); - buffer.position(buffer.position() + window.remaining()); - - if (LOG.isDebugEnabled()) { - LOG.debug("{} Window: {}", policy.getBehavior(), BufferUtils.toDetailString(window)); - } - - maskProcessor.process(window); - - if (window.remaining() == payloadLength) { - // We have the whole content, no need to copy. - frame.setPayload(window); - return true; - } else { - if (payload == null) { - payload = BufferUtils.allocate(payloadLength); - BufferUtils.clearToFill(payload); - } - // Copy the payload. - payload.put(window); - - if (payload.position() == payloadLength) { - BufferUtils.flipToFlush(payload, 0); - frame.setPayload(payload); - return true; - } - } - } - return false; - } - - public void setIncomingFramesHandler(IncomingFrames incoming) { - this.incomingFramesHandler = incoming; - } - - public long getMessagesIn() { - return messagesIn.longValue(); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Parser@").append(Integer.toHexString(hashCode())); - builder.append("["); - if (incomingFramesHandler == null) { - builder.append("NO_HANDLER"); - } else { - builder.append(incomingFramesHandler.getClass().getSimpleName()); - } - builder.append(",s=").append(state); - builder.append(",c=").append(cursor); - builder.append(",len=").append(payloadLength); - builder.append(",f=").append(frame); - // builder.append(",p=").append(policy); - builder.append("]"); - return builder.toString(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/payload/DeMaskProcessor.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/payload/DeMaskProcessor.java deleted file mode 100644 index 7be90db76..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/payload/DeMaskProcessor.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder.payload; - -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.nio.ByteBuffer; - -public class DeMaskProcessor implements PayloadProcessor { - private byte[] maskBytes; - private int maskInt; - private int maskOffset; - - @Override - public void process(ByteBuffer payload) { - if (maskBytes == null) { - return; - } - - int maskInt = this.maskInt; - int start = payload.position(); - int end = payload.limit(); - int offset = this.maskOffset; - int remaining; - while ((remaining = end - start) > 0) { - if (remaining >= 4 && (offset & 3) == 0) { - payload.putInt(start, payload.getInt(start) ^ maskInt); - start += 4; - offset += 4; - } else { - payload.put(start, (byte) (payload.get(start) ^ maskBytes[offset & 3])); - ++start; - ++offset; - } - } - maskOffset = offset; - } - - public void reset(byte[] mask) { - this.maskBytes = mask; - int maskInt = 0; - if (mask != null) { - for (byte maskByte : mask) { - maskInt = (maskInt << 8) + (maskByte & 0xFF); - } - } - this.maskInt = maskInt; - this.maskOffset = 0; - } - - @Override - public void reset(Frame frame) { - reset(frame.getMask()); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/payload/PayloadProcessor.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/payload/PayloadProcessor.java deleted file mode 100644 index c12ddadcb..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/decoder/payload/PayloadProcessor.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder.payload; - - -import com.fireflysource.net.websocket.common.exception.BadPayloadException; -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.nio.ByteBuffer; - -/** - * Process the payload (for demasking, validating, etc..) - */ -public interface PayloadProcessor { - /** - * Used to process payloads for in the spec. - * - * @param payload the payload to process - * @throws BadPayloadException the exception when the payload fails to validate properly - */ - void process(ByteBuffer payload); - - void reset(Frame frame); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/encoder/Generator.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/encoder/Generator.java deleted file mode 100644 index d7db4e6ba..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/encoder/Generator.java +++ /dev/null @@ -1,335 +0,0 @@ -package com.fireflysource.net.websocket.common.encoder; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.exception.ProtocolException; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.*; - -import java.nio.ByteBuffer; -import java.util.List; - -import static com.fireflysource.net.websocket.common.extension.AbstractExtension.getFlags; - -/** - * Generating a frame in WebSocket land. - *

    - *

    - *    0                   1                   2                   3
    - *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    - *   +-+-+-+-+-------+-+-------------+-------------------------------+
    - *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
    - *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
    - *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
    - *   | |1|2|3|       |K|             |                               |
    - *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
    - *   |     Extended payload length continued, if payload len == 127  |
    - *   + - - - - - - - - - - - - - - - +-------------------------------+
    - *   |                               |Masking-key, if MASK set to 1  |
    - *   +-------------------------------+-------------------------------+
    - *   | Masking-key (continued)       |          Payload Data         |
    - *   +-------------------------------- - - - - - - - - - - - - - - - +
    - *   :                     Payload Data continued ...                :
    - *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    - *   |                     Payload Data continued ...                |
    - *   +---------------------------------------------------------------+
    - * 
    - */ -public class Generator { - /** - * The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key. - */ - public static final int MAX_HEADER_LENGTH = 28; - - private final WebSocketBehavior behavior; - private final boolean validating; - private final boolean readOnly; - - /** - * Are any flags in use - *

    - *

    - *

    -     *   0100_0000 (0x40) = rsv1
    -     *   0010_0000 (0x20) = rsv2
    -     *   0001_0000 (0x10) = rsv3
    -     * 
    - */ - private byte flagsInUse = 0x00; - - /** - * Construct Generator with provided policy and bufferPool - * - * @param policy the policy to use - */ - public Generator(WebSocketPolicy policy) { - this(policy, true, false); - } - - /** - * Construct Generator with provided policy and bufferPool - * - * @param policy the policy to use - * @param validating true to enable RFC frame validation - */ - public Generator(WebSocketPolicy policy, boolean validating) { - this(policy, validating, false); - } - - /** - * Construct Generator with provided policy and bufferPool - * - * @param policy the policy to use - * @param validating true to enable RFC frame validation - * @param readOnly true if generator is to treat frames as read-only and not modify them. Useful for debugging purposes, but not generally for runtime use. - */ - public Generator(WebSocketPolicy policy, boolean validating, boolean readOnly) { - this.behavior = policy.getBehavior(); - this.validating = validating; - this.readOnly = readOnly; - } - - public void assertFrameValid(Frame frame) { - if (!validating) { - return; - } - - /* - * RFC 6455 Section 5.2 - * - * MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated - * extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_. - */ - if (frame.isRsv1() && !isRsv1InUse()) { - throw new ProtocolException("RSV1 not allowed to be set"); - } - - if (frame.isRsv2() && !isRsv2InUse()) { - throw new ProtocolException("RSV2 not allowed to be set"); - } - - if (frame.isRsv3() && !isRsv3InUse()) { - throw new ProtocolException("RSV3 not allowed to be set"); - } - - if (OpCode.isControlFrame(frame.getOpCode())) { - /* - * RFC 6455 Section 5.5 - * - * All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented. - */ - if (frame.getPayloadLength() > 125) { - throw new ProtocolException("Invalid control frame payload length"); - } - - if (!frame.isFin()) { - throw new ProtocolException("Control Frames must be FIN=true"); - } - - /* - * RFC 6455 Section 5.5.1 - * - * close frame payload is specially formatted which is checked in CloseInfo - */ - if (frame.getOpCode() == OpCode.CLOSE) { - - ByteBuffer payload = frame.getPayload(); - if (payload != null) { - new CloseInfo(payload, true); - } - } - } - } - - public void configureFromExtensions(List extensions) { - flagsInUse = getFlags(extensions); - } - - public ByteBuffer generateHeaderBytes(Frame frame) { - ByteBuffer buffer = BufferUtils.allocate(MAX_HEADER_LENGTH); - generateHeaderBytes(frame, buffer); - return buffer; - } - - public void generateHeaderBytes(Frame frame, ByteBuffer buffer) { - int p = BufferUtils.flipToFill(buffer); - - // we need a framing header - assertFrameValid(frame); - - /* - * start the generation process - */ - byte b = 0x00; - - // Setup fin thru opcode - if (frame.isFin()) { - b |= 0x80; // 1000_0000 - } - - // Set the flags - if (frame.isRsv1()) { - b |= 0x40; // 0100_0000 - } - if (frame.isRsv2()) { - b |= 0x20; // 0010_0000 - } - if (frame.isRsv3()) { - b |= 0x10; // 0001_0000 - } - - // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons - byte opcode = frame.getOpCode(); - - if (frame.getOpCode() == OpCode.CONTINUATION) { - // Continuations are not the same OPCODE - opcode = OpCode.CONTINUATION; - } - - b |= opcode & 0x0F; - - buffer.put(b); - - // is masked - b = (frame.isMasked() ? (byte) 0x80 : (byte) 0x00); - - // payload lengths - int payloadLength = frame.getPayloadLength(); - - /* - * if length is over 65535 then its a 7 + 64 bit length - */ - if (payloadLength > 0xFF_FF) { - // we have a 64 bit length - b |= 0x7F; - buffer.put(b); // indicate 8 byte length - buffer.put((byte) 0); // - buffer.put((byte) 0); // anything over an - buffer.put((byte) 0); // int is just - buffer.put((byte) 0); // insane! - buffer.put((byte) ((payloadLength >> 24) & 0xFF)); - buffer.put((byte) ((payloadLength >> 16) & 0xFF)); - buffer.put((byte) ((payloadLength >> 8) & 0xFF)); - buffer.put((byte) (payloadLength & 0xFF)); - } - /* - * if payload is greater that 126 we have a 7 + 16 bit length - */ - else if (payloadLength >= 0x7E) { - b |= 0x7E; - buffer.put(b); // indicate 2 byte length - buffer.put((byte) (payloadLength >> 8)); - buffer.put((byte) (payloadLength & 0xFF)); - } - /* - * we have a 7 bit length - */ - else { - b |= (payloadLength & 0x7F); - buffer.put(b); - } - - // masking key - if (frame.isMasked() && !readOnly) { - byte[] mask = frame.getMask(); - buffer.put(mask); - int maskInt = 0; - for (byte maskByte : mask) - maskInt = (maskInt << 8) + (maskByte & 0xFF); - - // perform data masking here - ByteBuffer payload = frame.getPayload(); - if ((payload != null) && (payload.remaining() > 0)) { - int maskOffset = 0; - int start = payload.position(); - int end = payload.limit(); - int remaining; - while ((remaining = end - start) > 0) { - if (remaining >= 4) { - payload.putInt(start, payload.getInt(start) ^ maskInt); - start += 4; - } else { - payload.put(start, (byte) (payload.get(start) ^ mask[maskOffset & 3])); - ++start; - ++maskOffset; - } - } - } - } - - BufferUtils.flipToFlush(buffer, p); - } - - /** - * Generate the whole frame (header + payload copy) into a single ByteBuffer. - *

    - * Note: This is slow, moves lots of memory around. Only use this if you must (such as in unit testing). - * - * @param frame the frame to generate - * @param buf the buffer to output the generated frame to - */ - public void generateWholeFrame(Frame frame, ByteBuffer buf) { - buf.put(generateHeaderBytes(frame)); - if (frame.hasPayload()) { - if (readOnly) { - buf.put(frame.getPayload().slice()); - } else { - buf.put(frame.getPayload()); - } - } - } - - public void setRsv1InUse(boolean rsv1InUse) { - if (readOnly) { - throw new RuntimeException("Not allowed to modify read-only frame"); - } - flagsInUse = (byte) ((flagsInUse & 0xBF) | (rsv1InUse ? 0x40 : 0x00)); - } - - public void setRsv2InUse(boolean rsv2InUse) { - if (readOnly) { - throw new RuntimeException("Not allowed to modify read-only frame"); - } - flagsInUse = (byte) ((flagsInUse & 0xDF) | (rsv2InUse ? 0x20 : 0x00)); - } - - public void setRsv3InUse(boolean rsv3InUse) { - if (readOnly) { - throw new RuntimeException("Not allowed to modify read-only frame"); - } - flagsInUse = (byte) ((flagsInUse & 0xEF) | (rsv3InUse ? 0x10 : 0x00)); - } - - public boolean isRsv1InUse() { - return (flagsInUse & 0x40) != 0; - } - - public boolean isRsv2InUse() { - return (flagsInUse & 0x20) != 0; - } - - public boolean isRsv3InUse() { - return (flagsInUse & 0x10) != 0; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Generator["); - builder.append(behavior); - if (validating) { - builder.append(",validating"); - } - if (isRsv1InUse()) { - builder.append(",+rsv1"); - } - if (isRsv2InUse()) { - builder.append(",+rsv2"); - } - if (isRsv3InUse()) { - builder.append(",+rsv3"); - } - builder.append("]"); - return builder.toString(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/BadPayloadException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/BadPayloadException.java deleted file mode 100644 index 59dba6c1a..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/BadPayloadException.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -import com.fireflysource.net.websocket.common.model.StatusCode; - -/** - * Exception to terminate the connection because it has received data within a frame payload that was not consistent with the requirements of that frame - * payload. (eg: not UTF-8 in a text frame, or a unexpected data seen by an extension) - * - * @see StatusCode#BAD_PAYLOAD - */ -@SuppressWarnings("serial") -public class BadPayloadException extends CloseException { - public BadPayloadException(String message) { - super(StatusCode.BAD_PAYLOAD, message); - } - - public BadPayloadException(String message, Throwable t) { - super(StatusCode.BAD_PAYLOAD, message, t); - } - - public BadPayloadException(Throwable t) { - super(StatusCode.BAD_PAYLOAD, t); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/CloseException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/CloseException.java deleted file mode 100644 index ccf4c29d9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/CloseException.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -@SuppressWarnings("serial") -public class CloseException extends WebSocketException { - private int statusCode; - - public CloseException(int closeCode, String message) { - super(message); - this.statusCode = closeCode; - } - - public CloseException(int closeCode, String message, Throwable cause) { - super(message, cause); - this.statusCode = closeCode; - } - - public CloseException(int closeCode, Throwable cause) { - super(cause); - this.statusCode = closeCode; - } - - public int getStatusCode() { - return statusCode; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/EncodingAcceptHashKeyException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/EncodingAcceptHashKeyException.java deleted file mode 100644 index 606c8797f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/EncodingAcceptHashKeyException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -/** - * @author Pengtao Qiu - */ -public class EncodingAcceptHashKeyException extends RuntimeException { - - public EncodingAcceptHashKeyException(String message) { - super(message); - } - - public EncodingAcceptHashKeyException(String message, Throwable e) { - super(message, e); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/MessageTooLargeException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/MessageTooLargeException.java deleted file mode 100644 index 79256cf4b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/MessageTooLargeException.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -import com.fireflysource.net.websocket.common.model.StatusCode; - -/** - * Exception when a message is too large for the internal buffers occurs and should trigger a connection close. - * - * @see StatusCode#MESSAGE_TOO_LARGE - */ -@SuppressWarnings("serial") -public class MessageTooLargeException extends CloseException { - public MessageTooLargeException(String message) { - super(StatusCode.MESSAGE_TOO_LARGE, message); - } - - public MessageTooLargeException(String message, Throwable t) { - super(StatusCode.MESSAGE_TOO_LARGE, message, t); - } - - public MessageTooLargeException(Throwable t) { - super(StatusCode.MESSAGE_TOO_LARGE, t); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/NextIncomingFramesNotSetException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/NextIncomingFramesNotSetException.java deleted file mode 100644 index d710d2e87..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/NextIncomingFramesNotSetException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -/** - * @author Pengtao Qiu - */ -public class NextIncomingFramesNotSetException extends RuntimeException { - - public NextIncomingFramesNotSetException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/ProtocolException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/ProtocolException.java deleted file mode 100644 index 46fc59a41..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/ProtocolException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -import com.fireflysource.net.websocket.common.model.StatusCode; - -/** - * Per spec, a protocol error should result in a Close frame of status code 1002 (PROTOCOL_ERROR) - */ -@SuppressWarnings("serial") -public class ProtocolException extends CloseException { - public ProtocolException(String message) { - super(StatusCode.PROTOCOL, message); - } - - public ProtocolException(String message, Throwable t) { - super(StatusCode.PROTOCOL, message, t); - } - - public ProtocolException(Throwable t) { - super(StatusCode.PROTOCOL, t); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/UpgradeWebSocketConnectionException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/UpgradeWebSocketConnectionException.java deleted file mode 100644 index e0f87e5ea..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/UpgradeWebSocketConnectionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -/** - * @author Pengtao Qiu - */ -public class UpgradeWebSocketConnectionException extends RuntimeException { - - public UpgradeWebSocketConnectionException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/WebSocketException.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/WebSocketException.java deleted file mode 100644 index 5b165dc15..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/exception/WebSocketException.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.websocket.common.exception; - -/** - * A recoverable exception within the websocket framework. - */ -@SuppressWarnings("serial") -public class WebSocketException extends RuntimeException { - public WebSocketException() { - super(); - } - - public WebSocketException(String message) { - super(message); - } - - public WebSocketException(String message, Throwable cause) { - super(message, cause); - } - - public WebSocketException(Throwable cause) { - super(cause); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/AbstractExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/AbstractExtension.java deleted file mode 100644 index bdc54d653..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/AbstractExtension.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.fireflysource.net.websocket.common.extension; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.Result; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.*; - -import java.io.IOException; -import java.util.List; -import java.util.function.Consumer; - -@SuppressWarnings("unused") -public abstract class AbstractExtension implements Extension { - - private static final LazyLogger log = SystemLogger.create(AbstractExtension.class); - - private WebSocketPolicy policy; - private ExtensionConfig config; - private OutgoingFrames nextOutgoing; - private IncomingFrames nextIncoming; - - public AbstractExtension() { - } - - public void dump(Appendable out, String indent) throws IOException { - // incoming - dumpWithHeading(out, indent, "incoming", this.nextIncoming); - dumpWithHeading(out, indent, "outgoing", this.nextOutgoing); - } - - protected void dumpWithHeading(Appendable out, String indent, String heading, Object bean) throws IOException { - out.append(indent).append(" +- "); - out.append(heading).append(" : "); - out.append(bean.toString()); - } - - @Override - public ExtensionConfig getConfig() { - return config; - } - - @Override - public String getName() { - return config.getName(); - } - - public IncomingFrames getNextIncoming() { - return nextIncoming; - } - - public OutgoingFrames getNextOutgoing() { - return nextOutgoing; - } - - public WebSocketPolicy getPolicy() { - return policy; - } - - /** - * Used to indicate that the extension makes use of the RSV1 bit of the base websocket framing. - *

    - * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV1. - * - * @return true if extension uses RSV1 for its own purposes. - */ - @Override - public boolean isRsv1User() { - return false; - } - - /** - * Used to indicate that the extension makes use of the RSV2 bit of the base websocket framing. - *

    - * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV2. - * - * @return true if extension uses RSV2 for its own purposes. - */ - @Override - public boolean isRsv2User() { - return false; - } - - /** - * Used to indicate that the extension makes use of the RSV3 bit of the base websocket framing. - *

    - * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV3. - * - * @return true if extension uses RSV3 for its own purposes. - */ - @Override - public boolean isRsv3User() { - return false; - } - - protected void nextIncomingFrame(Frame frame) { - log.debug("nextIncomingFrame({})", frame); - this.nextIncoming.incomingFrame(frame); - } - - protected void nextOutgoingFrame(Frame frame, Consumer> result) { - log.debug("nextOutgoingFrame({})", frame); - this.nextOutgoing.outgoingFrame(frame, result); - } - - public void setConfig(ExtensionConfig config) { - this.config = config; - } - - @Override - public void setNextIncomingFrames(IncomingFrames nextIncoming) { - this.nextIncoming = nextIncoming; - } - - @Override - public void setNextOutgoingFrames(OutgoingFrames nextOutgoing) { - this.nextOutgoing = nextOutgoing; - } - - public void setPolicy(WebSocketPolicy policy) { - this.policy = policy; - } - - public static byte getFlags(List extensions) { - byte flags = 0x00; - for (Extension ext : extensions) { - if (ext.isRsv1User()) { - flags = (byte) (flags | 0x40); - } - if (ext.isRsv2User()) { - flags = (byte) (flags | 0x20); - } - if (ext.isRsv3User()) { - flags = (byte) (flags | 0x10); - } - } - return flags; - } - - @Override - public String toString() { - return String.format("%s[%s]", this.getClass().getSimpleName(), config.getParameterizedName()); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/ExtensionFactory.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/ExtensionFactory.java deleted file mode 100644 index b3a6315e4..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/ExtensionFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.fireflysource.net.websocket.common.extension; - - -import com.fireflysource.common.collection.CollectionUtils; -import com.fireflysource.net.websocket.common.extension.compress.DeflateFrameExtension; -import com.fireflysource.net.websocket.common.extension.compress.PerMessageDeflateExtension; -import com.fireflysource.net.websocket.common.extension.compress.XWebkitDeflateFrameExtension; -import com.fireflysource.net.websocket.common.extension.fragment.FragmentExtension; -import com.fireflysource.net.websocket.common.extension.identity.IdentityExtension; -import com.fireflysource.net.websocket.common.model.Extension; -import com.fireflysource.net.websocket.common.model.ExtensionConfig; - -import java.util.*; - -@SuppressWarnings("unused") -public abstract class ExtensionFactory implements Iterable> { - private static final ServiceLoader extensionLoader = ServiceLoader.load(Extension.class); - private final Map> availableExtensions; - - public ExtensionFactory() { - availableExtensions = new HashMap<>(); - for (Extension ext : extensionLoader) { - if (ext != null) { - availableExtensions.put(ext.getName(), ext.getClass()); - } - } - if (CollectionUtils.isEmpty(availableExtensions)) { - availableExtensions.put(new DeflateFrameExtension().getName(), DeflateFrameExtension.class); - availableExtensions.put(new PerMessageDeflateExtension().getName(), PerMessageDeflateExtension.class); - availableExtensions.put(new XWebkitDeflateFrameExtension().getName(), XWebkitDeflateFrameExtension.class); - availableExtensions.put(new IdentityExtension().getName(), IdentityExtension.class); - availableExtensions.put(new FragmentExtension().getName(), FragmentExtension.class); - } - } - - public Map> getAvailableExtensions() { - return availableExtensions; - } - - public Class getExtension(String name) { - return availableExtensions.get(name); - } - - public Set getExtensionNames() { - return availableExtensions.keySet(); - } - - public boolean isAvailable(String name) { - return availableExtensions.containsKey(name); - } - - @Override - public Iterator> iterator() { - return availableExtensions.values().iterator(); - } - - public abstract Extension newInstance(ExtensionConfig config); - - public void register(String name, Class extension) { - availableExtensions.put(name, extension); - } - - public void unregister(String name) { - availableExtensions.remove(name); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/WebSocketExtensionFactory.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/WebSocketExtensionFactory.java deleted file mode 100644 index b13815c6d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/WebSocketExtensionFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.fireflysource.net.websocket.common.extension; - - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.exception.WebSocketException; -import com.fireflysource.net.websocket.common.model.Extension; -import com.fireflysource.net.websocket.common.model.ExtensionConfig; - -public class WebSocketExtensionFactory extends ExtensionFactory { - - @Override - public Extension newInstance(ExtensionConfig config) { - if (config == null) { - return null; - } - - String name = config.getName(); - if (!StringUtils.hasText(name)) { - return null; - } - - Class extClass = getExtension(name); - if (extClass == null) { - return null; - } - - try { - return extClass.getConstructor().newInstance(); - } catch (Exception e) { - throw new WebSocketException("Cannot instantiate extension: " + extClass, e); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/ByteAccumulator.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/ByteAccumulator.java deleted file mode 100644 index f416abf44..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/ByteAccumulator.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.exception.MessageTooLargeException; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -public class ByteAccumulator { - private final List chunks = new ArrayList<>(); - private final int maxSize; - private int length = 0; - - public ByteAccumulator(int maxOverallBufferSize) { - this.maxSize = maxOverallBufferSize; - } - - public void copyChunk(byte[] buf, int offset, int length) { - if (this.length + length > maxSize) { - String err = String.format("Resulting message size [%,d] is too large for configured max of [%,d]", this.length + length, maxSize); - throw new MessageTooLargeException(err); - } - - byte[] copy = new byte[length - offset]; - System.arraycopy(buf, offset, copy, 0, length); - - chunks.add(copy); - this.length += length; - } - - public int getLength() { - return length; - } - - public void transferTo(ByteBuffer buffer) { - if (buffer.remaining() < length) { - throw new IllegalArgumentException(String.format("Not enough space in ByteBuffer remaining [%d] for accumulated buffers length [%d]", - buffer.remaining(), length)); - } - - int position = buffer.position(); - for (byte[] chunk : chunks) { - buffer.put(chunk, 0, chunk.length); - } - BufferUtils.flipToFlush(buffer, position); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/CompressExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/CompressExtension.java deleted file mode 100644 index 721f58938..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/CompressExtension.java +++ /dev/null @@ -1,502 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -import com.fireflysource.common.concurrent.AutoLock; -import com.fireflysource.common.concurrent.IteratingCallback; -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.Result; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.extension.AbstractExtension; -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.zip.DataFormatException; -import java.util.zip.Deflater; -import java.util.zip.Inflater; -import java.util.zip.ZipException; - -import static com.fireflysource.common.coroutine.CommonCoroutinePoolKt.getApplicationCleaner; - -public abstract class CompressExtension extends AbstractExtension { - - private static final LazyLogger LOG = SystemLogger.create(CompressExtension.class); - - protected static final byte[] TAIL_BYTES = new byte[]{0x00, 0x00, (byte) 0xFF, (byte) 0xFF}; - protected static final ByteBuffer TAIL_BYTES_BUF = ByteBuffer.wrap(TAIL_BYTES); - - /** - * Never drop tail bytes 0000FFFF, from any frame type - */ - protected static final int TAIL_DROP_NEVER = 0; - /** - * Always drop tail bytes 0000FFFF, from all frame types - */ - protected static final int TAIL_DROP_ALWAYS = 1; - /** - * Only drop tail bytes 0000FFFF, from fin==true frames - */ - protected static final int TAIL_DROP_FIN_ONLY = 2; - - /** - * Always set RSV flag, on all frame types - */ - protected static final int RSV_USE_ALWAYS = 0; - /** - * Only set RSV flag on first frame in multi-frame messages. - *

    - * Note: this automatically means no-continuation frames have the RSV bit set - */ - protected static final int RSV_USE_ONLY_FIRST = 1; - - /** - * Inflater / Decompressed Buffer Size - */ - protected static final int INFLATE_BUFFER_SIZE = 8 * 1024; - - /** - * Deflater / Inflater: Maximum Input Buffer Size - */ - protected static final int INPUT_MAX_BUFFER_SIZE = 8 * 1024; - - /** - * Inflater : Output Buffer Size - */ - private static final int DECOMPRESS_BUF_SIZE = 8 * 1024; - - private final AutoLock lock = new AutoLock(); - private final Queue entries = new ArrayDeque<>(); - private final IteratingCallback flusher = new Flusher(); - private final Deflater deflaterImpl; - private final Inflater inflaterImpl; - protected AtomicInteger decompressCount = new AtomicInteger(0); - private int tailDrop = TAIL_DROP_NEVER; - private int rsvUse = RSV_USE_ALWAYS; - - protected CompressExtension() { - tailDrop = getTailDropMode(); - rsvUse = getRsvUseMode(); - deflaterImpl = new Deflater(Deflater.DEFAULT_COMPRESSION, true); - inflaterImpl = new Inflater(true); - getApplicationCleaner().register(this, new CompressExtensionCleanTask(deflaterImpl, inflaterImpl)); - } - - public static class CompressExtensionCleanTask implements Runnable { - - private final Deflater deflaterImpl; - private final Inflater inflaterImpl; - - public CompressExtensionCleanTask(Deflater deflaterImpl, Inflater inflaterImpl) { - this.deflaterImpl = deflaterImpl; - this.inflaterImpl = inflaterImpl; - } - - @Override - public void run() { - deflaterImpl.end(); - inflaterImpl.end(); - } - } - - public Deflater getDeflater() { - return deflaterImpl; - } - - public Inflater getInflater() { - return inflaterImpl; - } - - /** - * Indicates use of RSV1 flag for indicating deflation is in use. - */ - @Override - public boolean isRsv1User() { - return true; - } - - /** - * Return the mode of operation for dropping (or keeping) tail bytes in frames generated by compress (outgoing) - * - * @return either {@link #TAIL_DROP_ALWAYS}, {@link #TAIL_DROP_FIN_ONLY}, or {@link #TAIL_DROP_NEVER} - */ - abstract int getTailDropMode(); - - /** - * Return the mode of operation for RSV flag use in frames generate by compress (outgoing) - * - * @return either {@link #RSV_USE_ALWAYS} or {@link #RSV_USE_ONLY_FIRST} - */ - abstract int getRsvUseMode(); - - protected void forwardIncoming(Frame frame, ByteAccumulator accumulator) { - DataFrame newFrame; - switch (frame.getType()) { - case TEXT: - newFrame = new TextFrame(frame); - break; - case BINARY: - newFrame = new BinaryFrame(frame); - break; - case CONTINUATION: - newFrame = new ContinuationFrame(frame); - break; - default: - newFrame = new DataFrame(frame); - break; - } - // Unset RSV1 since it's not compressed anymore. - newFrame.setRsv1(false); - - ByteBuffer buffer = BufferUtils.allocate(accumulator.getLength()); - BufferUtils.flipToFill(buffer); - accumulator.transferTo(buffer); - newFrame.setPayload(buffer); - nextIncomingFrame(newFrame); - } - - protected ByteAccumulator newByteAccumulator() { - int maxSize = Math.max(getPolicy().getMaxTextMessageSize(), getPolicy().getMaxBinaryMessageSize()); - return new ByteAccumulator(maxSize); - } - - protected void decompress(ByteAccumulator accumulator, ByteBuffer buf) throws DataFormatException { - if ((buf == null) || (!buf.hasRemaining())) { - return; - } - byte[] output = new byte[DECOMPRESS_BUF_SIZE]; - - Inflater inflater = getInflater(); - - while (buf.hasRemaining() && inflater.needsInput()) { - if (!supplyInput(inflater, buf)) { - LOG.debug("Needed input, but no buffer could supply input"); - return; - } - - int read; - while ((read = inflater.inflate(output)) >= 0) { - if (read == 0) { - LOG.debug("Decompress: read 0 {}", toDetail(inflater)); - break; - } else { - // do something with output - if (LOG.isDebugEnabled()) { - LOG.debug("Decompressed {} bytes: {}", read, toDetail(inflater)); - } - - accumulator.copyChunk(output, 0, read); - } - } - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Decompress: exiting {}", toDetail(inflater)); - } - } - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - // We use a queue and an IteratingCallback to handle concurrency. - // We must compress and write atomically, otherwise the compression - // context on the other end gets confused. - - if (flusher.isFailed()) { - notifyCallbackFailure(result, new ZipException()); - return; - } - - FrameEntry entry = new FrameEntry(frame, result); - if (LOG.isDebugEnabled()) - LOG.debug("Queuing {}", entry); - offerEntry(entry); - flusher.iterate(); - } - - private void offerEntry(FrameEntry entry) { - lock.lock(() -> entries.offer(entry)); - } - - private FrameEntry pollEntry() { - return lock.lock(entries::poll); - } - - protected void notifyCallbackSuccess(Consumer> result) { - try { - if (result != null) - result.accept(Result.SUCCESS); - } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug("Exception while notifying success", x); - } - } - - protected void notifyCallbackFailure(Consumer> result, Throwable failure) { - try { - if (result != null) - result.accept(Result.createFailedResult(failure)); - } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug("Exception while notifying failure", x); - } - } - - private static boolean supplyInput(Inflater inflater, ByteBuffer buf) { - if (buf == null || buf.remaining() <= 0) { - if (LOG.isDebugEnabled()) { - LOG.debug("No data left left to supply to Inflater"); - } - return false; - } - - byte[] input; - int inputOffset; - int len; - - if (buf.hasArray()) { - // no need to create a new byte buffer, just return this one. - len = buf.remaining(); - input = buf.array(); - inputOffset = buf.position() + buf.arrayOffset(); - buf.position(buf.position() + len); - } else { - // Only create an return byte buffer that is reasonable in size - len = Math.min(INPUT_MAX_BUFFER_SIZE, buf.remaining()); - input = new byte[len]; - inputOffset = 0; - buf.get(input, 0, len); - } - - inflater.setInput(input, inputOffset, len); - if (LOG.isDebugEnabled()) { - LOG.debug("Supplied {} input bytes: {}", input.length, toDetail(inflater)); - } - return true; - } - - private static boolean supplyInput(Deflater deflater, ByteBuffer buf) { - if (buf == null || buf.remaining() <= 0) { - if (LOG.isDebugEnabled()) { - LOG.debug("No data left left to supply to Deflater"); - } - return false; - } - - byte[] input; - int inputOffset; - int len; - - if (buf.hasArray()) { - // no need to create a new byte buffer, just return this one. - len = buf.remaining(); - input = buf.array(); - inputOffset = buf.position() + buf.arrayOffset(); - buf.position(buf.position() + len); - } else { - // Only create an return byte buffer that is reasonable in size - len = Math.min(INPUT_MAX_BUFFER_SIZE, buf.remaining()); - input = new byte[len]; - inputOffset = 0; - buf.get(input, 0, len); - } - - deflater.setInput(input, inputOffset, len); - if (LOG.isDebugEnabled()) { - LOG.debug("Supplied {} input bytes: {}", input.length, toDetail(deflater)); - } - return true; - } - - private static String toDetail(Inflater inflater) { - return String.format("Inflater[finished=%b,read=%d,written=%d,remaining=%d,in=%d,out=%d]", inflater.finished(), inflater.getBytesRead(), - inflater.getBytesWritten(), inflater.getRemaining(), inflater.getTotalIn(), inflater.getTotalOut()); - } - - private static String toDetail(Deflater deflater) { - return String.format("Deflater[finished=%b,read=%d,written=%d,in=%d,out=%d]", deflater.finished(), deflater.getBytesRead(), deflater.getBytesWritten(), - deflater.getTotalIn(), deflater.getTotalOut()); - } - - public static boolean endsWithTail(ByteBuffer buf) { - if ((buf == null) || (buf.remaining() < TAIL_BYTES.length)) { - return false; - } - int limit = buf.limit(); - for (int i = TAIL_BYTES.length; i > 0; i--) { - if (buf.get(limit - i) != TAIL_BYTES[TAIL_BYTES.length - i]) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - private static class FrameEntry { - private final Frame frame; - private final Consumer> result; - - private FrameEntry(Frame frame, Consumer> result) { - this.frame = frame; - this.result = result; - } - - @Override - public String toString() { - return frame.toString(); - } - } - - private class Flusher extends IteratingCallback { - private FrameEntry current; - private boolean finished = true; - - @Override - protected Action process() throws Exception { - if (finished) { - current = pollEntry(); - LOG.debug("Processing {}", current); - if (current == null) - return Action.IDLE; - deflate(current); - } else { - compress(current, false); - } - return Action.SCHEDULED; - } - - private void deflate(FrameEntry entry) { - Frame frame = entry.frame; - if (OpCode.isControlFrame(frame.getOpCode())) { - // Do not deflate control frames - nextOutgoingFrame(frame, this); - return; - } - - compress(entry, true); - } - - private void compress(FrameEntry entry, boolean first) { - // Get a chunk of the payload to avoid to blow - // the heap if the payload is a huge mapped file. - Frame frame = entry.frame; - ByteBuffer data = frame.getPayload(); - - if (data == null) - data = BufferUtils.EMPTY_BUFFER; - - int remaining = data.remaining(); - int outputLength = Math.max(256, data.remaining()); - if (LOG.isDebugEnabled()) - LOG.debug("Compressing {}: {} bytes in {} bytes chunk", entry, remaining, outputLength); - - boolean needsCompress = true; - - Deflater deflater = getDeflater(); - - if (deflater.needsInput() && !supplyInput(deflater, data)) { - // no input supplied - needsCompress = false; - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - byte[] output = new byte[outputLength]; - - boolean fin = frame.isFin(); - - // Compress the data - while (needsCompress) { - int compressed = deflater.deflate(output, 0, outputLength, Deflater.SYNC_FLUSH); - - // Append the output for the eventual frame. - if (LOG.isDebugEnabled()) - LOG.debug("Wrote {} bytes to output buffer", compressed); - out.write(output, 0, compressed); - - if (compressed < outputLength) { - needsCompress = false; - } - } - - ByteBuffer payload = ByteBuffer.wrap(out.toByteArray()); - - if (payload.remaining() > 0) { - // Handle tail bytes generated by SYNC_FLUSH. - if (LOG.isDebugEnabled()) - LOG.debug("compressed[] bytes = {}", BufferUtils.toDetailString(payload)); - - if (tailDrop == TAIL_DROP_ALWAYS) { - if (endsWithTail(payload)) { - payload.limit(payload.limit() - TAIL_BYTES.length); - } - if (LOG.isDebugEnabled()) - LOG.debug("payload (TAIL_DROP_ALWAYS) = {}", BufferUtils.toDetailString(payload)); - } else if (tailDrop == TAIL_DROP_FIN_ONLY) { - if (frame.isFin() && endsWithTail(payload)) { - payload.limit(payload.limit() - TAIL_BYTES.length); - } - if (LOG.isDebugEnabled()) - LOG.debug("payload (TAIL_DROP_FIN_ONLY) = {}", BufferUtils.toDetailString(payload)); - } - } else if (fin) { - // Special case: 7.2.3.6. Generating an Empty Fragment Manually - // https://tools.ietf.org/html/rfc7692#section-7.2.3.6 - payload = ByteBuffer.wrap(new byte[]{0x00}); - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Compressed {}: input:{} -> payload:{}", entry, outputLength, payload.remaining()); - } - - boolean continuation = frame.getType().isContinuation() || !first; - DataFrame chunk = new DataFrame(frame, continuation); - if (rsvUse == RSV_USE_ONLY_FIRST) { - chunk.setRsv1(!continuation); - } else { - // always set - chunk.setRsv1(true); - } - chunk.setPayload(payload); - chunk.setFin(fin); - - nextOutgoingFrame(chunk, this); - } - - @Override - protected void onCompleteSuccess() { - // This IteratingCallback never completes. - } - - @Override - protected void onCompleteFailure(Throwable x) { - // Fail all the frames in the queue. - FrameEntry entry; - while ((entry = pollEntry()) != null) { - notifyCallbackFailure(entry.result, x); - } - } - - @Override - public void accept(Result result) { - if (result.isSuccess()) { - if (finished) - notifyCallbackSuccess(current.result); - } else { - notifyCallbackFailure(current.result, result.getThrowable()); - // If something went wrong, very likely the compression context - // will be invalid, so we need to fail this IteratingCallback. - LOG.warn("", result.getThrowable()); - } - super.accept(result); - } - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/DeflateFrameExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/DeflateFrameExtension.java deleted file mode 100644 index 8c4107d5c..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/DeflateFrameExtension.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -import com.fireflysource.net.websocket.common.exception.BadPayloadException; -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.util.zip.DataFormatException; - -/** - * Implementation of the - * deflate-frame - * extension seen out in the wild. - */ -public class DeflateFrameExtension extends CompressExtension { - @Override - public String getName() { - return "deflate-frame"; - } - - @Override - int getRsvUseMode() { - return RSV_USE_ALWAYS; - } - - @Override - int getTailDropMode() { - return TAIL_DROP_ALWAYS; - } - - @Override - public void incomingFrame(Frame frame) { - // Incoming frames are always non concurrent because - // they are read and parsed with a single thread, and - // therefore there is no need for synchronization. - - if (frame.getType().isControl() || !frame.isRsv1() || !frame.hasPayload()) { - nextIncomingFrame(frame); - return; - } - - try { - ByteAccumulator accumulator = newByteAccumulator(); - decompress(accumulator, frame.getPayload()); - decompress(accumulator, TAIL_BYTES_BUF.slice()); - forwardIncoming(frame, accumulator); - } catch (DataFormatException e) { - throw new BadPayloadException(e); - } - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/PerMessageDeflateExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/PerMessageDeflateExtension.java deleted file mode 100644 index 076ac9379..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/PerMessageDeflateExtension.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.Result; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.exception.BadPayloadException; -import com.fireflysource.net.websocket.common.exception.ProtocolException; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.ExtensionConfig; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; -import java.util.function.Consumer; -import java.util.zip.DataFormatException; - -/** - * Per Message Deflate Compression extension for WebSocket. - *

    - * Attempts to follow Compression Extensions for WebSocket - */ -public class PerMessageDeflateExtension extends CompressExtension { - - private static LazyLogger LOG = SystemLogger.create(PerMessageDeflateExtension.class); - - private ExtensionConfig configRequested; - private ExtensionConfig configNegotiated; - private boolean incomingContextTakeover = true; - private boolean outgoingContextTakeover = true; - private boolean incomingCompressed; - - @Override - public String getName() { - return "permessage-deflate"; - } - - @Override - public void incomingFrame(Frame frame) { - // Incoming frames are always non concurrent because - // they are read and parsed with a single thread, and - // therefore there is no need for synchronization. - - // This extension requires the RSV1 bit set only in the first frame. - // Subsequent continuation frames don't have RSV1 set, but are compressed. - if (frame.getType().isData()) { - incomingCompressed = frame.isRsv1(); - } - - if (OpCode.isControlFrame(frame.getOpCode()) || !incomingCompressed) { - nextIncomingFrame(frame); - return; - } - - if (frame.getOpCode() == OpCode.CONTINUATION && frame.isRsv1()) { - // Per RFC7692 we MUST Fail the websocket connection - throw new ProtocolException("Invalid RSV1 set on permessage-deflate CONTINUATION frame"); - } - - ByteAccumulator accumulator = newByteAccumulator(); - - try { - ByteBuffer payload = frame.getPayload(); - decompress(accumulator, payload); - if (frame.isFin()) { - decompress(accumulator, TAIL_BYTES_BUF.slice()); - } - - forwardIncoming(frame, accumulator); - } catch (DataFormatException e) { - throw new BadPayloadException(e); - } - - if (frame.isFin()) - incomingCompressed = false; - } - - @Override - protected void nextIncomingFrame(Frame frame) { - if (frame.isFin() && !incomingContextTakeover) { - LOG.debug("Incoming Context Reset"); - decompressCount.set(0); - getInflater().reset(); - } - super.nextIncomingFrame(frame); - } - - @Override - protected void nextOutgoingFrame(Frame frame, Consumer> result) { - if (frame.isFin() && !outgoingContextTakeover) { - LOG.debug("Outgoing Context Reset"); - getDeflater().reset(); - } - super.nextOutgoingFrame(frame, result); - } - - @Override - int getRsvUseMode() { - return RSV_USE_ONLY_FIRST; - } - - @Override - int getTailDropMode() { - return TAIL_DROP_FIN_ONLY; - } - - @Override - public void setConfig(final ExtensionConfig config) { - configRequested = new ExtensionConfig(config); - configNegotiated = new ExtensionConfig(config.getName()); - - for (String key : config.getParameterKeys()) { - key = key.trim(); - switch (key) { - case "client_max_window_bits": - case "server_max_window_bits": { - // Don't negotiate these parameters - break; - } - case "client_no_context_takeover": { - configNegotiated.setParameter("client_no_context_takeover"); - switch (getPolicy().getBehavior()) { - case CLIENT: - incomingContextTakeover = false; - break; - case SERVER: - outgoingContextTakeover = false; - break; - } - break; - } - case "server_no_context_takeover": { - configNegotiated.setParameter("server_no_context_takeover"); - switch (getPolicy().getBehavior()) { - case CLIENT: - outgoingContextTakeover = false; - break; - case SERVER: - incomingContextTakeover = false; - break; - } - break; - } - default: { - throw new IllegalArgumentException(); - } - } - } - - LOG.debug("config: outgoingContextTakeover={}, incomingContextTakeover={} : {}", outgoingContextTakeover, incomingContextTakeover, this); - - super.setConfig(configNegotiated); - } - - @Override - public String toString() { - return String.format("%s[requested=\"%s\", negotiated=\"%s\"]", - getClass().getSimpleName(), - configRequested.getParameterizedName(), - configNegotiated.getParameterizedName()); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/XWebkitDeflateFrameExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/XWebkitDeflateFrameExtension.java deleted file mode 100644 index 1b200d798..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/compress/XWebkitDeflateFrameExtension.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -/** - * Implementation of the x-webkit-deflate-frame extension seen out - * in the wild. Using the alternate extension identification - */ -public class XWebkitDeflateFrameExtension extends DeflateFrameExtension { - @Override - public String getName() { - return "x-webkit-deflate-frame"; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/fragment/FragmentExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/fragment/FragmentExtension.java deleted file mode 100644 index 2f7d0cb51..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/fragment/FragmentExtension.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.fragment; - -import com.fireflysource.common.concurrent.AutoLock; -import com.fireflysource.common.concurrent.IteratingCallback; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.Result; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.extension.AbstractExtension; -import com.fireflysource.net.websocket.common.frame.DataFrame; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.ExtensionConfig; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.function.Consumer; - -/** - * Fragment Extension - */ -public class FragmentExtension extends AbstractExtension { - private static LazyLogger LOG = SystemLogger.create(FragmentExtension.class); - - private final AutoLock lock = new AutoLock(); - private final Queue entries = new ArrayDeque<>(); - private final IteratingCallback flusher = new Flusher(); - private int maxLength; - - @Override - public String getName() { - return "fragment"; - } - - @Override - public void incomingFrame(Frame frame) { - nextIncomingFrame(frame); - } - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - ByteBuffer payload = frame.getPayload(); - int length = payload != null ? payload.remaining() : 0; - if (OpCode.isControlFrame(frame.getOpCode()) || maxLength <= 0 || length <= maxLength) { - nextOutgoingFrame(frame, result); - return; - } - - FrameEntry entry = new FrameEntry(frame, result); - if (LOG.isDebugEnabled()) - LOG.debug("Queuing {}", entry); - offerEntry(entry); - flusher.iterate(); - } - - @Override - public void setConfig(ExtensionConfig config) { - super.setConfig(config); - maxLength = config.getParameter("maxLength", -1); - } - - private void offerEntry(FrameEntry entry) { - lock.lock(() -> entries.offer(entry)); - } - - private FrameEntry pollEntry() { - return lock.lock(entries::poll); - } - - private static class FrameEntry { - private final Frame frame; - private final Consumer> result; - - private FrameEntry(Frame frame, Consumer> result) { - this.frame = frame; - this.result = result; - } - - @Override - public String toString() { - return frame.toString(); - } - } - - private class Flusher extends IteratingCallback { - private FrameEntry current; - private boolean finished = true; - - @Override - protected Action process() { - if (finished) { - current = pollEntry(); - LOG.debug("Processing {}", current); - if (current == null) - return Action.IDLE; - fragment(current, true); - } else { - fragment(current, false); - } - return Action.SCHEDULED; - } - - private void fragment(FrameEntry entry, boolean first) { - Frame frame = entry.frame; - ByteBuffer payload = frame.getPayload(); - int remaining = payload.remaining(); - int length = Math.min(remaining, maxLength); - finished = length == remaining; - - boolean continuation = frame.getType().isContinuation() || !first; - DataFrame fragment = new DataFrame(frame, continuation); - boolean fin = frame.isFin() && finished; - fragment.setFin(fin); - - int limit = payload.limit(); - int newLimit = payload.position() + length; - payload.limit(newLimit); - ByteBuffer payloadFragment = payload.slice(); - payload.limit(limit); - fragment.setPayload(payloadFragment); - if (LOG.isDebugEnabled()) - LOG.debug("Fragmented {}->{}", frame, fragment); - payload.position(newLimit); - - nextOutgoingFrame(fragment, this); - } - - @Override - protected void onCompleteSuccess() { - // This IteratingCallback never completes. - } - - @Override - protected void onCompleteFailure(Throwable x) { - // This IteratingCallback never fails. - // The callback are those provided by WriteCallback (implemented - // below) and even in case of writeFailed() we call succeeded(). - } - - @Override - public void accept(Result result) { - if (!result.isSuccess()) { - notifyCallbackFailure(current.result, result.getThrowable()); - } - notifyCallbackSuccess(current.result); - super.accept(result); - } - - private void notifyCallbackSuccess(Consumer> result) { - try { - if (result != null) - result.accept(Result.SUCCESS); - } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug("Exception while notifying success", x); - } - } - - private void notifyCallbackFailure(Consumer> result, Throwable failure) { - try { - if (result != null) - result.accept(Result.createFailedResult(failure)); - } catch (Throwable x) { - if (LOG.isDebugEnabled()) - LOG.debug("Exception while notifying failure", x); - } - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/identity/IdentityExtension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/identity/IdentityExtension.java deleted file mode 100644 index 7529b69dd..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/extension/identity/IdentityExtension.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.identity; - - -import com.fireflysource.common.string.QuotedStringTokenizer; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.extension.AbstractExtension; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.ExtensionConfig; - -import java.util.function.Consumer; - -public class IdentityExtension extends AbstractExtension { - - private String id; - - public String getParam(String key) { - return getConfig().getParameter(key, "?"); - } - - @Override - public String getName() { - return "identity"; - } - - @Override - public void incomingFrame(Frame frame) { - // pass through - nextIncomingFrame(frame); - } - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - // pass through - nextOutgoingFrame(frame, result); - } - - @Override - public void setConfig(ExtensionConfig config) { - super.setConfig(config); - StringBuilder s = new StringBuilder(); - s.append(config.getName()); - s.append("@").append(Integer.toHexString(hashCode())); - s.append("["); - boolean delim = false; - for (String param : config.getParameterKeys()) { - if (delim) { - s.append(';'); - } - s.append(param).append('=').append(QuotedStringTokenizer.quoteIfNeeded(config.getParameter(param, ""), ";=")); - delim = true; - } - s.append("]"); - id = s.toString(); - } - - @Override - public String toString() { - return id; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/BinaryFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/BinaryFrame.java deleted file mode 100644 index 197d1b4c0..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/BinaryFrame.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; - -public class BinaryFrame extends DataFrame { - - public BinaryFrame() { - super(OpCode.BINARY); - } - - public BinaryFrame(Frame basedOn) { - super(basedOn); - } - - @Override - public BinaryFrame setPayload(ByteBuffer buf) { - super.setPayload(buf); - return this; - } - - public BinaryFrame setPayload(byte[] buf) { - setPayload(ByteBuffer.wrap(buf)); - return this; - } - - public BinaryFrame setPayload(String payload) { - setPayload(StringUtils.getUtf8Bytes(payload)); - return this; - } - - @Override - public Type getType() { - return Type.BINARY; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/CloseFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/CloseFrame.java deleted file mode 100644 index a19cfe7a8..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/CloseFrame.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -public class CloseFrame extends ControlFrame { - public CloseFrame() { - super(OpCode.CLOSE); - } - - @Override - public Type getType() { - return Type.CLOSE; - } - - /** - * Truncate arbitrary reason into something that will fit into the CloseFrame limits. - * - * @param reason the arbitrary reason to possibly truncate. - * @return the possibly truncated reason string. - */ - public static String truncate(String reason) { - return StringUtils.truncate(reason, (ControlFrame.MAX_CONTROL_PAYLOAD - 2)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ContinuationFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ContinuationFrame.java deleted file mode 100644 index eab0c72c0..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ContinuationFrame.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; - -public class ContinuationFrame extends DataFrame { - - public ContinuationFrame() { - super(OpCode.CONTINUATION); - } - - public ContinuationFrame(Frame basedOn) { - super(basedOn); - } - - @Override - public ContinuationFrame setPayload(ByteBuffer buf) { - super.setPayload(buf); - return this; - } - - public ContinuationFrame setPayload(byte[] buf) { - return this.setPayload(ByteBuffer.wrap(buf)); - } - - public ContinuationFrame setPayload(String message) { - return this.setPayload(StringUtils.getUtf8Bytes(message)); - } - - @Override - public Type getType() { - return Type.CONTINUATION; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ControlFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ControlFrame.java deleted file mode 100644 index 55befc128..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ControlFrame.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.exception.ProtocolException; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -public abstract class ControlFrame extends WebSocketFrame { - /** - * Maximum size of Control frame, per RFC 6455 - */ - public static final int MAX_CONTROL_PAYLOAD = 125; - - public ControlFrame(byte opcode) { - super(opcode); - } - - @Override - public void assertValid() { - if (isControlFrame()) { - if (getPayloadLength() > ControlFrame.MAX_CONTROL_PAYLOAD) { - throw new ProtocolException("Desired payload length [" + getPayloadLength() + - "] exceeds maximum control payload length [" + MAX_CONTROL_PAYLOAD + "]"); - } - - if ((finRsvOp & 0x80) == 0) { - throw new ProtocolException("Cannot have FIN==false on Control frames"); - } - - if ((finRsvOp & 0x40) != 0) { - throw new ProtocolException("Cannot have RSV1==true on Control frames"); - } - - if ((finRsvOp & 0x20) != 0) { - throw new ProtocolException("Cannot have RSV2==true on Control frames"); - } - - if ((finRsvOp & 0x10) != 0) { - throw new ProtocolException("Cannot have RSV3==true on Control frames"); - } - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - ControlFrame other = (ControlFrame) obj; - if (data == null) { - if (other.data != null) { - return false; - } - } else if (!data.equals(other.data)) { - return false; - } - if (finRsvOp != other.finRsvOp) { - return false; - } - if (!Arrays.equals(mask, other.mask)) { - return false; - } - return masked == other.masked; - } - - @Override - public boolean isControlFrame() { - return true; - } - - @Override - public boolean isDataFrame() { - return false; - } - - @Override - public WebSocketFrame setPayload(ByteBuffer buf) { - if (buf != null && buf.remaining() > MAX_CONTROL_PAYLOAD) { - throw new ProtocolException("Control Payloads can not exceed " + MAX_CONTROL_PAYLOAD + " bytes in length."); - } - return super.setPayload(buf); - } - - @Override - public ByteBuffer getPayload() { - if (super.getPayload() == null) { - return BufferUtils.EMPTY_BUFFER; - } - return super.getPayload(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/DataFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/DataFrame.java deleted file mode 100644 index 00744829f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/DataFrame.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.net.websocket.common.model.OpCode; - -/** - * A Data Frame - */ -public class DataFrame extends WebSocketFrame { - protected DataFrame(byte opcode) { - super(opcode); - } - - /** - * Construct new DataFrame based on headers of provided frame. - *

    - * Useful for when working in extensions and a new frame needs to be created. - * - * @param basedOn the frame this one is based on - */ - public DataFrame(Frame basedOn) { - this(basedOn, false); - } - - /** - * Construct new DataFrame based on headers of provided frame, overriding for continuations if needed. - *

    - * Useful for when working in extensions and a new frame needs to be created. - * - * @param basedOn the frame this one is based on - * @param continuation true if this is a continuation frame - */ - public DataFrame(Frame basedOn, boolean continuation) { - super(basedOn.getOpCode()); - copyHeaders(basedOn); - if (continuation) { - setOpCode(OpCode.CONTINUATION); - } - } - - @Override - public void assertValid() { - /* no extra validation for data frames (yet) here */ - } - - @Override - public boolean isControlFrame() { - return false; - } - - @Override - public boolean isDataFrame() { - return true; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/Frame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/Frame.java deleted file mode 100644 index 901500b0d..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/Frame.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import java.nio.ByteBuffer; - -/** - * An immutable websocket frame. - */ -public interface Frame { - enum Type { - CONTINUATION((byte) 0x00), - TEXT((byte) 0x01), - BINARY((byte) 0x02), - CLOSE((byte) 0x08), - PING((byte) 0x09), - PONG((byte) 0x0A); - - public static Type from(byte op) { - for (Type type : values()) { - if (type.opcode == op) { - return type; - } - } - throw new IllegalArgumentException("OpCode " + op + " is not a valid Frame.Type"); - } - - private byte opcode; - - Type(byte code) { - this.opcode = code; - } - - public byte getOpCode() { - return opcode; - } - - public boolean isControl() { - return (opcode >= CLOSE.getOpCode()); - } - - public boolean isData() { - return (opcode == TEXT.getOpCode()) || (opcode == BINARY.getOpCode()); - } - - public boolean isContinuation() { - return opcode == CONTINUATION.getOpCode(); - } - - @Override - public String toString() { - return this.name(); - } - } - - byte[] getMask(); - - byte getOpCode(); - - ByteBuffer getPayload(); - - /** - * The original payload length ({@link ByteBuffer#remaining()}) - * - * @return the original payload length ({@link ByteBuffer#remaining()}) - */ - int getPayloadLength(); - - Type getType(); - - boolean hasPayload(); - - boolean isFin(); - - boolean isMasked(); - - boolean isRsv1(); - - boolean isRsv2(); - - boolean isRsv3(); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/PingFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/PingFrame.java deleted file mode 100644 index 986483777..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/PingFrame.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; - -public class PingFrame extends ControlFrame { - public PingFrame() { - super(OpCode.PING); - } - - public PingFrame setPayload(byte[] bytes) { - setPayload(ByteBuffer.wrap(bytes)); - return this; - } - - public PingFrame setPayload(String payload) { - setPayload(ByteBuffer.wrap(StringUtils.getUtf8Bytes(payload))); - return this; - } - - @Override - public Type getType() { - return Type.PING; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/PongFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/PongFrame.java deleted file mode 100644 index 27ebbf2f3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/PongFrame.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; - -public class PongFrame extends ControlFrame { - public PongFrame() { - super(OpCode.PONG); - } - - public PongFrame setPayload(byte[] bytes) { - setPayload(ByteBuffer.wrap(bytes)); - return this; - } - - public PongFrame setPayload(String payload) { - setPayload(StringUtils.getUtf8Bytes(payload)); - return this; - } - - @Override - public Type getType() { - return Type.PONG; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ReadOnlyDelegatedFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ReadOnlyDelegatedFrame.java deleted file mode 100644 index a689641c6..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/ReadOnlyDelegatedFrame.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import java.nio.ByteBuffer; - -/** - * Immutable, Read-only, Frame implementation. - */ -public class ReadOnlyDelegatedFrame implements Frame { - private final Frame delegate; - - public ReadOnlyDelegatedFrame(Frame frame) { - this.delegate = frame; - } - - @Override - public byte[] getMask() { - return delegate.getMask(); - } - - @Override - public byte getOpCode() { - return delegate.getOpCode(); - } - - @Override - public ByteBuffer getPayload() { - if (!delegate.hasPayload()) { - return null; - } - return delegate.getPayload().asReadOnlyBuffer(); - } - - @Override - public int getPayloadLength() { - return delegate.getPayloadLength(); - } - - @Override - public Type getType() { - return delegate.getType(); - } - - @Override - public boolean hasPayload() { - return delegate.hasPayload(); - } - - @Override - public boolean isFin() { - return delegate.isFin(); - } - - @Override - public boolean isMasked() { - return delegate.isMasked(); - } - - @Override - public boolean isRsv1() { - return delegate.isRsv1(); - } - - @Override - public boolean isRsv2() { - return delegate.isRsv2(); - } - - @Override - public boolean isRsv3() { - return delegate.isRsv3(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/TextFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/TextFrame.java deleted file mode 100644 index 3c6ae1479..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/TextFrame.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; - -public class TextFrame extends DataFrame { - public TextFrame() { - super(OpCode.TEXT); - } - - public TextFrame(Frame basedOn) { - super(basedOn); - } - - @Override - public Type getType() { - return Type.TEXT; - } - - public TextFrame setPayload(String str) { - setPayload(ByteBuffer.wrap(StringUtils.getUtf8Bytes(str))); - return this; - } - - @Override - public String getPayloadAsUTF8() { - if (data == null) { - return null; - } - return BufferUtils.toUTF8String(data); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/WebSocketFrame.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/WebSocketFrame.java deleted file mode 100644 index 53950ed1b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/frame/WebSocketFrame.java +++ /dev/null @@ -1,311 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.model.OpCode; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * A Base Frame as seen in RFC 6455. Sec 5.2 - * - *

    - *    0                   1                   2                   3
    - *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    - *   +-+-+-+-+-------+-+-------------+-------------------------------+
    - *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
    - *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
    - *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
    - *   | |1|2|3|       |K|             |                               |
    - *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
    - *   |     Extended payload length continued, if payload len == 127  |
    - *   + - - - - - - - - - - - - - - - +-------------------------------+
    - *   |                               |Masking-key, if MASK set to 1  |
    - *   +-------------------------------+-------------------------------+
    - *   | Masking-key (continued)       |          Payload Data         |
    - *   +-------------------------------- - - - - - - - - - - - - - - - +
    - *   :                     Payload Data continued ...                :
    - *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    - *   |                     Payload Data continued ...                |
    - *   +---------------------------------------------------------------+
    - * 
    - */ -public abstract class WebSocketFrame implements Frame { - public static WebSocketFrame copy(Frame original) { - WebSocketFrame copy; - switch (original.getOpCode()) { - case OpCode.BINARY: - copy = new BinaryFrame(); - break; - case OpCode.TEXT: - copy = new TextFrame(); - break; - case OpCode.CLOSE: - copy = new CloseFrame(); - break; - case OpCode.CONTINUATION: - copy = new ContinuationFrame(); - break; - case OpCode.PING: - copy = new PingFrame(); - break; - case OpCode.PONG: - copy = new PongFrame(); - break; - default: - throw new IllegalArgumentException("Cannot copy frame with opcode " + original.getOpCode() + " - " + original); - } - - copy.copyHeaders(original); - ByteBuffer payload = original.getPayload(); - if (payload != null) { - ByteBuffer payloadCopy = ByteBuffer.allocate(payload.remaining()); - payloadCopy.put(payload.slice()).flip(); - copy.setPayload(payloadCopy); - } - return copy; - } - - /** - * Combined FIN + RSV1 + RSV2 + RSV3 + OpCode byte. - * - *
    -     *   1000_0000 (0x80) = fin
    -     *   0100_0000 (0x40) = rsv1
    -     *   0010_0000 (0x20) = rsv2
    -     *   0001_0000 (0x10) = rsv3
    -     *   0000_1111 (0x0F) = opcode
    -     * 
    - */ - protected byte finRsvOp; - protected boolean masked = false; - - protected byte[] mask; - /** - * The payload data. - *

    - * It is assumed to always be in FLUSH mode (ready to read) in this object. - */ - protected ByteBuffer data; - - /** - * Construct form opcode - * - * @param opcode the opcode the frame is based on - */ - protected WebSocketFrame(byte opcode) { - reset(); - setOpCode(opcode); - } - - public abstract void assertValid(); - - protected void copyHeaders(Frame frame) { - finRsvOp = 0x00; - finRsvOp |= frame.isFin() ? 0x80 : 0x00; - finRsvOp |= frame.isRsv1() ? 0x40 : 0x00; - finRsvOp |= frame.isRsv2() ? 0x20 : 0x00; - finRsvOp |= frame.isRsv3() ? 0x10 : 0x00; - finRsvOp |= frame.getOpCode() & 0x0F; - - masked = frame.isMasked(); - if (masked) { - mask = frame.getMask(); - } else { - mask = null; - } - } - - protected void copyHeaders(WebSocketFrame copy) { - finRsvOp = copy.finRsvOp; - masked = copy.masked; - mask = null; - if (copy.mask != null) - mask = Arrays.copyOf(copy.mask, copy.mask.length); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - WebSocketFrame other = (WebSocketFrame) obj; - if (data == null) { - if (other.data != null) { - return false; - } - } else if (!data.equals(other.data)) { - return false; - } - if (finRsvOp != other.finRsvOp) { - return false; - } - if (!Arrays.equals(mask, other.mask)) { - return false; - } - return masked == other.masked; - } - - @Override - public byte[] getMask() { - return mask; - } - - @Override - public final byte getOpCode() { - return (byte) (finRsvOp & 0x0F); - } - - /** - * Get the payload ByteBuffer. possible null. - */ - @Override - public ByteBuffer getPayload() { - return data; - } - - public String getPayloadAsUTF8() { - return BufferUtils.toUTF8String(getPayload()); - } - - @Override - public int getPayloadLength() { - if (data == null) { - return 0; - } - return data.remaining(); - } - - @Override - public Type getType() { - return Type.from(getOpCode()); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = (prime * result) + ((data == null) ? 0 : data.hashCode()); - result = (prime * result) + finRsvOp; - result = (prime * result) + Arrays.hashCode(mask); - return result; - } - - @Override - public boolean hasPayload() { - return ((data != null) && data.hasRemaining()); - } - - public abstract boolean isControlFrame(); - - public abstract boolean isDataFrame(); - - @Override - public boolean isFin() { - return (byte) (finRsvOp & 0x80) != 0; - } - - @Override - public boolean isMasked() { - return masked; - } - - @Override - public boolean isRsv1() { - return (byte) (finRsvOp & 0x40) != 0; - } - - @Override - public boolean isRsv2() { - return (byte) (finRsvOp & 0x20) != 0; - } - - @Override - public boolean isRsv3() { - return (byte) (finRsvOp & 0x10) != 0; - } - - public void reset() { - finRsvOp = (byte) 0x80; // FIN (!RSV, opcode 0) - masked = false; - data = null; - mask = null; - } - - public WebSocketFrame setFin(boolean fin) { - // set bit 1 - this.finRsvOp = (byte) ((finRsvOp & 0x7F) | (fin ? 0x80 : 0x00)); - return this; - } - - public Frame setMask(byte[] maskingKey) { - this.mask = maskingKey; - this.masked = (mask != null); - return this; - } - - public Frame setMasked(boolean mask) { - this.masked = mask; - return this; - } - - protected WebSocketFrame setOpCode(byte op) { - this.finRsvOp = (byte) ((finRsvOp & 0xF0) | (op & 0x0F)); - return this; - } - - /** - * Set the data payload. - *

    - * The provided buffer will be used as is, no copying of bytes performed. - *

    - * The provided buffer should be flipped and ready to READ from. - * - * @param buf the bytebuffer to set - * @return the frame itself - */ - public WebSocketFrame setPayload(ByteBuffer buf) { - data = buf; - return this; - } - - public WebSocketFrame setRsv1(boolean rsv1) { - // set bit 2 - this.finRsvOp = (byte) ((finRsvOp & 0xBF) | (rsv1 ? 0x40 : 0x00)); - return this; - } - - public WebSocketFrame setRsv2(boolean rsv2) { - // set bit 3 - this.finRsvOp = (byte) ((finRsvOp & 0xDF) | (rsv2 ? 0x20 : 0x00)); - return this; - } - - public WebSocketFrame setRsv3(boolean rsv3) { - // set bit 4 - this.finRsvOp = (byte) ((finRsvOp & 0xEF) | (rsv3 ? 0x10 : 0x00)); - return this; - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append(OpCode.name((byte) (finRsvOp & 0x0F))); - b.append('['); - b.append("len=").append(getPayloadLength()); - b.append(",fin=").append((finRsvOp & 0x80) != 0); - b.append(",rsv="); - b.append(((finRsvOp & 0x40) != 0) ? '1' : '.'); - b.append(((finRsvOp & 0x20) != 0) ? '1' : '.'); - b.append(((finRsvOp & 0x10) != 0) ? '1' : '.'); - b.append(",masked=").append(masked); - b.append(']'); - return b.toString(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/AcceptHash.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/AcceptHash.java deleted file mode 100644 index 134b10aad..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/AcceptHash.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.codec.base64.Base64Utils; -import com.fireflysource.net.websocket.common.exception.EncodingAcceptHashKeyException; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; - -/** - * Logic for working with the Sec-WebSocket-Key and Sec-WebSocket-Accept headers. - *

    - * This is kept separate from Connection objects to facilitate difference in behavior between client and server, as well as making testing easier. - */ -public class AcceptHash { - /** - * Globally Unique Identifier for use in WebSocket handshake within Sec-WebSocket-Accept and Sec-WebSocket-Key http headers. - *

    - * See Opening Handshake (Section 1.3) - */ - private final static byte[] MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StandardCharsets.ISO_8859_1); - - /** - * Concatenate the provided key with the Magic GUID and return the Base64 encoded form. - * - * @param key the key to hash - * @return the Sec-WebSocket-Accept header response (per opening handshake spec) - */ - public static String hashKey(String key) { - try { - MessageDigest md = MessageDigest.getInstance("SHA1"); - md.update(key.getBytes(StandardCharsets.UTF_8)); - md.update(MAGIC); - return new String(Base64Utils.encode(md.digest())); - } catch (Exception e) { - throw new EncodingAcceptHashKeyException("", e); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/CloseInfo.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/CloseInfo.java deleted file mode 100644 index 3b2299492..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/CloseInfo.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.string.Utf8Appendable; -import com.fireflysource.common.string.Utf8StringBuilder; -import com.fireflysource.net.websocket.common.exception.BadPayloadException; -import com.fireflysource.net.websocket.common.exception.ProtocolException; -import com.fireflysource.net.websocket.common.frame.CloseFrame; -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -public class CloseInfo { - private int statusCode = 0; - private byte[] reasonBytes; - - public CloseInfo() { - this(StatusCode.NO_CODE, null); - } - - /** - * Parse the Close Frame payload. - * - * @param payload the raw close frame payload. - * @param validate true if payload should be validated per WebSocket spec. - */ - public CloseInfo(ByteBuffer payload, boolean validate) { - this.statusCode = StatusCode.NO_CODE; - - if ((payload == null) || (payload.remaining() == 0)) { - return; // nothing to do - } - - ByteBuffer data = payload.slice(); - if ((data.remaining() == 1) && (validate)) { - throw new ProtocolException("Invalid 1 byte payload"); - } - - if (data.remaining() >= 2) { - // Status Code - statusCode = 0; // start with 0 - statusCode |= (data.get() & 0xFF) << 8; - statusCode |= (data.get() & 0xFF); - - if (validate) { - assertValidStatusCode(statusCode); - } - - if (data.remaining() > 0) { - // Reason (trimmed to max reason size) - int len = Math.min(data.remaining(), CloseStatus.MAX_REASON_PHRASE); - reasonBytes = new byte[len]; - data.get(reasonBytes, 0, len); - - // Spec Requirement : throw BadPayloadException on invalid UTF8 - if (validate) { - try { - Utf8StringBuilder utf = new Utf8StringBuilder(); - // if this throws, we know we have bad UTF8 - utf.append(reasonBytes, 0, reasonBytes.length); - } catch (Utf8Appendable.NotUtf8Exception e) { - throw new BadPayloadException("Invalid Close Reason", e); - } - } - } - } - } - - public CloseInfo(Frame frame) { - this(frame.getPayload(), false); - } - - public CloseInfo(Frame frame, boolean validate) { - this(frame.getPayload(), validate); - } - - public CloseInfo(int statusCode) { - this(statusCode, null); - } - - /** - * Create a CloseInfo, trimming the reason to {@link CloseStatus#MAX_REASON_PHRASE} UTF-8 bytes if needed. - * - * @param statusCode the status code - * @param reason the raw reason code - */ - public CloseInfo(int statusCode, String reason) { - this.statusCode = statusCode; - if (reason != null) { - byte[] utf8Bytes = reason.getBytes(StandardCharsets.UTF_8); - if (utf8Bytes.length > CloseStatus.MAX_REASON_PHRASE) { - this.reasonBytes = new byte[CloseStatus.MAX_REASON_PHRASE]; - System.arraycopy(utf8Bytes, 0, this.reasonBytes, 0, CloseStatus.MAX_REASON_PHRASE); - } else { - this.reasonBytes = utf8Bytes; - } - } - } - - private void assertValidStatusCode(int statusCode) { - // Status Codes outside of RFC6455 defined scope - if ((statusCode <= 999) || (statusCode >= 5000)) { - throw new ProtocolException("Out of range close status code: " + statusCode); - } - - // Status Codes not allowed to exist in a Close frame (per RFC6455) - if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == StatusCode.FAILED_TLS_HANDSHAKE)) { - throw new ProtocolException("Frame forbidden close status code: " + statusCode); - } - - // Status Code is in defined "reserved space" and is declared (all others are invalid) - if ((statusCode >= 1000) && (statusCode <= 2999) && !StatusCode.isTransmittable(statusCode)) { - throw new ProtocolException("RFC6455 and IANA Undefined close status code: " + statusCode); - } - } - - private ByteBuffer asByteBuffer() { - if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == (-1))) { - // codes that are not allowed to be used in endpoint. - return null; - } - - int len = 2; // status code - boolean hasReason = (this.reasonBytes != null) && (this.reasonBytes.length > 0); - if (hasReason) { - len += this.reasonBytes.length; - } - - ByteBuffer buf = BufferUtils.allocate(len); - BufferUtils.flipToFill(buf); - buf.put((byte) ((statusCode >>> 8) & 0xFF)); - buf.put((byte) ((statusCode >>> 0) & 0xFF)); - - if (hasReason) { - buf.put(this.reasonBytes, 0, this.reasonBytes.length); - } - BufferUtils.flipToFlush(buf, 0); - - return buf; - } - - public CloseFrame asFrame() { - CloseFrame frame = new CloseFrame(); - frame.setFin(true); - // Frame forbidden codes result in no status code (and no reason string) - if ((statusCode != StatusCode.NO_CLOSE) && (statusCode != StatusCode.NO_CODE) && (statusCode != StatusCode.FAILED_TLS_HANDSHAKE)) { - assertValidStatusCode(statusCode); - frame.setPayload(asByteBuffer()); - } - return frame; - } - - public String getReason() { - if (this.reasonBytes == null) { - return null; - } - return new String(this.reasonBytes, StandardCharsets.UTF_8); - } - - public int getStatusCode() { - return statusCode; - } - - public boolean isHarsh() { - return !((statusCode == StatusCode.NORMAL) || (statusCode == StatusCode.NO_CODE)); - } - - public boolean isAbnormal() { - return (statusCode != StatusCode.NORMAL); - } - - @Override - public String toString() { - return String.format("CloseInfo[code=%d,reason=%s]", statusCode, getReason()); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/CloseStatus.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/CloseStatus.java deleted file mode 100644 index c23b333ad..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/CloseStatus.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import java.nio.charset.StandardCharsets; - -public class CloseStatus { - private static final int MAX_CONTROL_PAYLOAD = 125; - public static final int MAX_REASON_PHRASE = MAX_CONTROL_PAYLOAD - 2; - - /** - * Convenience method for trimming a long reason phrase at the maximum reason phrase length of 123 UTF-8 bytes (per WebSocket spec). - * - * @param reason the proposed reason phrase - * @return the reason phrase (trimmed if needed) - * @deprecated use of this method is strongly discouraged, as it creates too many new objects that are just thrown away to accomplish its goals. - */ - @Deprecated - public static String trimMaxReasonLength(String reason) { - if (reason == null) { - return null; - } - - byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8); - if (reasonBytes.length > MAX_REASON_PHRASE) { - byte[] trimmed = new byte[MAX_REASON_PHRASE]; - System.arraycopy(reasonBytes, 0, trimmed, 0, MAX_REASON_PHRASE); - return new String(trimmed, StandardCharsets.UTF_8); - } - - return reason; - } - - private int code; - private String phrase; - - /** - * Creates a reason for closing a web socket connection with the given code and reason phrase. - * - * @param closeCode the close code - * @param reasonPhrase the reason phrase - * @see StatusCode - */ - public CloseStatus(int closeCode, String reasonPhrase) { - this.code = closeCode; - this.phrase = reasonPhrase; - if (reasonPhrase.length() > MAX_REASON_PHRASE) { - throw new IllegalArgumentException("Phrase exceeds maximum length of " + MAX_REASON_PHRASE); - } - } - - public int getCode() { - return code; - } - - public String getPhrase() { - return phrase; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/Extension.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/Extension.java deleted file mode 100644 index 4dc69028e..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/Extension.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.net.websocket.common.frame.Frame; - -/** - * Interface for WebSocket Extensions. - *

    - * That {@link Frame}s are passed through the Extension via the {@link IncomingFrames} and {@link OutgoingFrames} interfaces - */ -public interface Extension extends IncomingFrames, OutgoingFrames { - /** - * The active configuration for this extension. - * - * @return the configuration for this extension. never null. - */ - ExtensionConfig getConfig(); - - /** - * The Sec-WebSocket-Extensions name for this extension. - *

    - * Also known as the extension-token per Section 9.1. Negotiating Extensions. - * - * @return the name of the extension - */ - String getName(); - - /** - * Used to indicate that the extension makes use of the RSV1 bit of the base websocket framing. - *

    - * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV1. - * - * @return true if extension uses RSV1 for its own purposes. - */ - boolean isRsv1User(); - - /** - * Used to indicate that the extension makes use of the RSV2 bit of the base websocket framing. - *

    - * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV2. - * - * @return true if extension uses RSV2 for its own purposes. - */ - boolean isRsv2User(); - - /** - * Used to indicate that the extension makes use of the RSV3 bit of the base websocket framing. - *

    - * This is used to adjust validation during parsing, as well as a checkpoint against 2 or more extensions all simultaneously claiming ownership of RSV3. - * - * @return true if extension uses RSV3 for its own purposes. - */ - boolean isRsv3User(); - - /** - * Set the next {@link IncomingFrames} to call in the chain. - * - * @param nextIncoming the next incoming extension - */ - void setNextIncomingFrames(IncomingFrames nextIncoming); - - /** - * Set the next {@link OutgoingFrames} to call in the chain. - * - * @param nextOutgoing the next outgoing extension - */ - void setNextOutgoingFrames(OutgoingFrames nextOutgoing); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/ExtensionConfig.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/ExtensionConfig.java deleted file mode 100644 index eb8879459..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/ExtensionConfig.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.net.websocket.common.utils.QuoteUtil; - -import java.util.*; - -/** - * Represents an Extension Configuration, as seen during the connection Handshake process. - */ -public class ExtensionConfig { - /** - * Parse a single parameterized name. - * - * @param parameterizedName the parameterized name - * @return the ExtensionConfig - */ - public static ExtensionConfig parse(String parameterizedName) { - return new ExtensionConfig(parameterizedName); - } - - /** - * Parse enumeration of Sec-WebSocket-Extensions header values into a {@link ExtensionConfig} list - * - * @param valuesEnum the raw header values enum - * @return the list of extension configs - */ - public static List parseEnum(Enumeration valuesEnum) { - List configs = new ArrayList<>(); - - if (valuesEnum != null) { - while (valuesEnum.hasMoreElements()) { - Iterator extTokenIter = QuoteUtil.splitAt(valuesEnum.nextElement(), ","); - while (extTokenIter.hasNext()) { - String extToken = extTokenIter.next(); - configs.add(ExtensionConfig.parse(extToken)); - } - } - } - - return configs; - } - - /** - * Parse 1 or more raw Sec-WebSocket-Extensions header values into a {@link ExtensionConfig} list - * - * @param rawSecWebSocketExtensions the raw header values - * @return the list of extension configs - */ - public static List parseList(List rawSecWebSocketExtensions) { - List configs = new ArrayList<>(); - - for (String rawValue : rawSecWebSocketExtensions) { - Iterator extTokenIter = QuoteUtil.splitAt(rawValue, ","); - while (extTokenIter.hasNext()) { - String extToken = extTokenIter.next(); - configs.add(ExtensionConfig.parse(extToken)); - } - } - - return configs; - } - - /** - * Convert a list of {@link ExtensionConfig} to a header value - * - * @param configs the list of extension configs - * @return the header value (null if no configs present) - */ - public static String toHeaderValue(List configs) { - if ((configs == null) || (configs.isEmpty())) { - return null; - } - StringBuilder parameters = new StringBuilder(); - boolean needsDelim = false; - for (ExtensionConfig ext : configs) { - if (needsDelim) { - parameters.append(", "); - } - parameters.append(ext.getParameterizedName()); - needsDelim = true; - } - return parameters.toString(); - } - - private final String name; - private final Map parameters; - - /** - * Copy constructor - * - * @param copy the extension config to copy - */ - public ExtensionConfig(ExtensionConfig copy) { - this.name = copy.name; - this.parameters = new HashMap<>(); - this.parameters.putAll(copy.parameters); - } - - public ExtensionConfig(String parameterizedName) { - Iterator extListIter = QuoteUtil.splitAt(parameterizedName, ";"); - this.name = extListIter.next(); - this.parameters = new HashMap<>(); - - // now for parameters - while (extListIter.hasNext()) { - String extParam = extListIter.next(); - Iterator extParamIter = QuoteUtil.splitAt(extParam, "="); - String key = extParamIter.next().trim(); - String value = null; - if (extParamIter.hasNext()) { - value = extParamIter.next(); - } - parameters.put(key, value); - } - } - - public String getName() { - return name; - } - - public final int getParameter(String key, int defValue) { - String val = parameters.get(key); - if (val == null) { - return defValue; - } - return Integer.parseInt(val); - } - - public final String getParameter(String key, String defValue) { - String val = parameters.get(key); - if (val == null) { - return defValue; - } - return val; - } - - public final String getParameterizedName() { - StringBuilder str = new StringBuilder(); - str.append(name); - for (String param : parameters.keySet()) { - str.append(';'); - str.append(param); - String value = parameters.get(param); - if (value != null) { - str.append('='); - QuoteUtil.quoteIfNeeded(str, value, ";="); - } - } - return str.toString(); - } - - public final Set getParameterKeys() { - return parameters.keySet(); - } - - /** - * Return parameters found in request URI. - * - * @return the parameter map - */ - public final Map getParameters() { - return parameters; - } - - /** - * Initialize the parameters on this config from the other configuration. - * - * @param other the other configuration. - */ - public final void init(ExtensionConfig other) { - this.parameters.clear(); - this.parameters.putAll(other.parameters); - } - - public final void setParameter(String key) { - parameters.put(key, null); - } - - public final void setParameter(String key, int value) { - parameters.put(key, Integer.toString(value)); - } - - public final void setParameter(String key, String value) { - parameters.put(key, value); - } - - @Override - public String toString() { - return getParameterizedName(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/IncomingFrames.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/IncomingFrames.java deleted file mode 100644 index 797fb8951..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/IncomingFrames.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.net.websocket.common.frame.Frame; - -/** - * Interface for dealing with Incoming Frames. - */ -public interface IncomingFrames { - /** - * Process the incoming frame. - *

    - * Note: if you need to hang onto any information from the frame, be sure - * to copy it, as the information contained in the Frame will be released - * and/or reused by the implementation. - * - * @param frame the frame to process - */ - void incomingFrame(Frame frame); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/OpCode.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/OpCode.java deleted file mode 100644 index 9d00a731b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/OpCode.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -public final class OpCode { - /** - * OpCode for a Continuation Frame - * - * @see RFC 6455, Section 11.8 (WebSocket Opcode Registry - */ - public static final byte CONTINUATION = (byte) 0x00; - - /** - * OpCode for a Text Frame - * - * @see RFC 6455, Section 11.8 (WebSocket Opcode Registry - */ - public static final byte TEXT = (byte) 0x01; - - /** - * OpCode for a Binary Frame - * - * @see RFC 6455, Section 11.8 (WebSocket Opcode Registry - */ - public static final byte BINARY = (byte) 0x02; - - /** - * OpCode for a Close Frame - * - * @see RFC 6455, Section 11.8 (WebSocket Opcode Registry - */ - public static final byte CLOSE = (byte) 0x08; - - /** - * OpCode for a Ping Frame - * - * @see RFC 6455, Section 11.8 (WebSocket Opcode Registry - */ - public static final byte PING = (byte) 0x09; - - /** - * OpCode for a Pong Frame - * - * @see RFC 6455, Section 11.8 (WebSocket Opcode Registry - */ - public static final byte PONG = (byte) 0x0A; - - /** - * An undefined OpCode - */ - public static final byte UNDEFINED = (byte) -1; - - public static boolean isControlFrame(byte opcode) { - return (opcode >= CLOSE); - } - - public static boolean isDataFrame(byte opcode) { - return (opcode == TEXT) || (opcode == BINARY) || (opcode == CONTINUATION); - } - - /** - * Test for known opcodes (per the RFC spec) - * - * @param opcode the opcode to test - * @return true if known. false if unknown, undefined, or reserved - */ - public static boolean isKnown(byte opcode) { - return (opcode == CONTINUATION) || (opcode == TEXT) || (opcode == BINARY) || (opcode == CLOSE) || (opcode == PING) || (opcode == PONG); - } - - public static String name(byte opcode) { - switch (opcode) { - case -1: - return "NO-OP"; - case CONTINUATION: - return "CONTINUATION"; - case TEXT: - return "TEXT"; - case BINARY: - return "BINARY"; - case CLOSE: - return "CLOSE"; - case PING: - return "PING"; - case PONG: - return "PONG"; - default: - return "NON-SPEC[" + opcode + "]"; - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/OutgoingFrames.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/OutgoingFrames.java deleted file mode 100644 index 81d43984b..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/OutgoingFrames.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.util.function.Consumer; - -/** - * Interface for dealing with frames outgoing to (eventually) the network layer. - */ -public interface OutgoingFrames { - /** - * A frame, and optional callback, intended for the network layer. - *

    - * Note: the frame can undergo many transformations in the various - * layers and extensions present in the implementation. - *

    - * If you are implementing a mutation, you are obliged to handle - * the incoming WriteCallback appropriately. - * - * @param frame the frame to eventually write to the network layer. - * @param result the callback to notify when the frame is written. - */ - void outgoingFrame(Frame frame, Consumer> result); -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/StatusCode.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/StatusCode.java deleted file mode 100644 index 963f9c7d3..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/StatusCode.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -/** - * The RFC 6455 specified status codes and IANA: WebSocket Close Code Number Registry - */ -public final class StatusCode { - /** - * 1000 indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int NORMAL = 1000; - - /** - * 1001 indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int SHUTDOWN = 1001; - - /** - * 1002 indicates that an endpoint is terminating the connection due to a protocol error. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int PROTOCOL = 1002; - - /** - * 1003 indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands - * only text data MAY send this if it receives a binary message). - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int BAD_DATA = 1003; - - /** - * Reserved. The specific meaning might be defined in the future. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int UNDEFINED = 1004; - - /** - * 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting - * a status code to indicate that no status code was actually present. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int NO_CODE = 1005; - - /** - * 1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting - * a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int NO_CLOSE = 1006; - - /** - * Abnormal Close is a synonym for {@link #NO_CLOSE}, used to indicate a close - * condition where no close frame was processed from the remote side. - */ - public static final int ABNORMAL = NO_CLOSE; - - /** - * 1007 indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the - * message (e.g., non-UTF-8 [RFC3629] data within a text message). - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int BAD_PAYLOAD = 1007; - - /** - * 1008 indicates that an endpoint is terminating the connection because it has received a message that violates its policy. This is a generic status code - * that can be returned when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a need to hide specific details about the - * policy. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int POLICY_VIOLATION = 1008; - - /** - * 1009 indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int MESSAGE_TOO_LARGE = 1009; - - /** - * 1010 indicates that an endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the - * server didn't return them in the response message of the WebSocket handshake. The list of extensions that are needed SHOULD appear in the /reason/ part - * of the Close frame. Note that this status code is not used by the server, because it can fail the WebSocket handshake instead. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int REQUIRED_EXTENSION = 1010; - - /** - * 1011 indicates that a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request. - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int SERVER_ERROR = 1011; - - /** - * 1012 indicates that the service is restarted. a client may reconnect, and if it chooses to do, should reconnect using a randomized delay of 5 - 30s. - *

    - * See [hybi] Additional WebSocket Close Error Codes - */ - public static final int SERVICE_RESTART = 1012; - - /** - * 1013 indicates that the service is experiencing overload. a client should only connect to a different IP (when there are multiple for the target) or - * reconnect to the same IP upon user action. - *

    - * See [hybi] Additional WebSocket Close Error Codes - */ - public static final int TRY_AGAIN_LATER = 1013; - - /** - * 1014 indicates that a gateway or proxy received and invalid upstream response. - *

    - * See [hybi] WebSocket Subprotocol Close Code: Bad Gateway - */ - public static final int INVALID_UPSTREAM_RESPONSE = 1014; - - /** - * 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting - * a status code to indicate that the connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified). - *

    - * See RFC 6455, Section 7.4.1 Defined Status Codes. - */ - public static final int FAILED_TLS_HANDSHAKE = 1015; - - /** - * Test if provided status code is a fatal failure for bad protocol behavior. - * - * @param statusCode the status code to test - * @return true if fatal status code - */ - public static boolean isFatal(int statusCode) { - return (statusCode == ABNORMAL) || - (statusCode == PROTOCOL) || - (statusCode == MESSAGE_TOO_LARGE) || - (statusCode == BAD_DATA) || - (statusCode == BAD_PAYLOAD) || - (statusCode == POLICY_VIOLATION) || - (statusCode == REQUIRED_EXTENSION) || - (statusCode == SERVER_ERROR) || - (statusCode == SERVICE_RESTART); - } - - /** - * Test if provided status code can be sent/received on a WebSocket close. - *

    - * This honors the RFC6455 rules and IANA rules. - *

    - * - * @param statusCode the statusCode to test - * @return true if transmittable - */ - public static boolean isTransmittable(int statusCode) { - return (statusCode == NORMAL) || - (statusCode == SHUTDOWN) || - (statusCode == PROTOCOL) || - (statusCode == BAD_DATA) || - (statusCode == BAD_PAYLOAD) || - (statusCode == POLICY_VIOLATION) || - (statusCode == MESSAGE_TOO_LARGE) || - (statusCode == REQUIRED_EXTENSION) || - (statusCode == SERVER_ERROR) || - (statusCode == SERVICE_RESTART) || - (statusCode == TRY_AGAIN_LATER) || - (statusCode == INVALID_UPSTREAM_RESPONSE) || - ((statusCode >= 3000) && (statusCode <= 4999)); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/WebSocketBehavior.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/WebSocketBehavior.java deleted file mode 100644 index 9e901f1bf..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/WebSocketBehavior.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -/** - * Behavior for how the WebSocket should operate. - *

    - * This dictated by the RFC 6455 spec in various places, where certain behavior must be performed depending on - * operation as a CLIENT vs a SERVER - */ -public enum WebSocketBehavior { - CLIENT, - SERVER -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/WebSocketPolicy.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/WebSocketPolicy.java deleted file mode 100644 index 53fe8db8f..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/model/WebSocketPolicy.java +++ /dev/null @@ -1,465 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.net.websocket.common.exception.MessageTooLargeException; - -/** - * Settings for WebSocket operations. - */ -public class WebSocketPolicy { - private static final int KB = 1024; - - public static WebSocketPolicy newClientPolicy() { - return new WebSocketPolicy(WebSocketBehavior.CLIENT); - } - - public static WebSocketPolicy newServerPolicy() { - return new WebSocketPolicy(WebSocketBehavior.SERVER); - } - - /** - * The maximum size of a text message during parsing/generating. - *

    - * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * Default: 65536 (64 K) - */ - private int maxTextMessageSize = 64 * KB; - - /** - * The maximum size of a text message buffer. - *

    - * Used ONLY for stream based message writing. - *

    - * Default: 32768 (32 K) - */ - private int maxTextMessageBufferSize = 32 * KB; - - /** - * The maximum size of a binary message during parsing/generating. - *

    - * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * Default: 65536 (64 K) - */ - private int maxBinaryMessageSize = 64 * KB; - - /** - * The maximum size of a binary message buffer - *

    - * Used ONLY for for stream based message writing - *

    - * Default: 32768 (32 K) - */ - private int maxBinaryMessageBufferSize = 32 * KB; - - /** - * The timeout in ms (milliseconds) for async write operations. - *

    - * Negative values indicate a disabled timeout. - */ - private long asyncWriteTimeout = 60000; - - /** - * The time in ms (milliseconds) that a websocket may be idle before closing. - *

    - * Default: 300000 (ms) - */ - private long idleTimeout = 300000; - - /** - * The size of the input (read from network layer) buffer size. - *

    - * Default: 4096 (4 K) - */ - private int inputBufferSize = 4 * KB; - - /** - * Behavior of the websockets - */ - private final WebSocketBehavior behavior; - - public WebSocketPolicy(WebSocketBehavior behavior) { - this.behavior = behavior; - } - - private void assertLessThan(String name, long size, String otherName, long otherSize) { - if (size > otherSize) { - throw new IllegalArgumentException(String.format("%s [%d] must be less than %s [%d]", name, size, otherName, otherSize)); - } - } - - private void assertGreaterThan(String name, long size, long minSize) { - if (size < minSize) { - throw new IllegalArgumentException(String.format("%s [%d] must be a greater than or equal to " + minSize, name, size)); - } - } - - public void assertValidBinaryMessageSize(int requestedSize) { - if (maxBinaryMessageSize > 0) { - // validate it - if (requestedSize > maxBinaryMessageSize) { - throw new MessageTooLargeException("Binary message size [" + requestedSize + "] exceeds maximum size [" + maxBinaryMessageSize + "]"); - } - } - } - - public void assertValidTextMessageSize(int requestedSize) { - if (maxTextMessageSize > 0) { - // validate it - if (requestedSize > maxTextMessageSize) { - throw new MessageTooLargeException("Text message size [" + requestedSize + "] exceeds maximum size [" + maxTextMessageSize + "]"); - } - } - } - - /** - * Make a copy of the policy, with current values. - * - * @return the cloned copy of the policy. - */ - public WebSocketPolicy clonePolicy() { - WebSocketPolicy clone = new WebSocketPolicy(this.behavior); - clone.idleTimeout = this.getIdleTimeout(); - clone.maxTextMessageSize = this.getMaxTextMessageSize(); - clone.maxTextMessageBufferSize = this.getMaxTextMessageBufferSize(); - clone.maxBinaryMessageSize = this.getMaxBinaryMessageSize(); - clone.maxBinaryMessageBufferSize = this.getMaxBinaryMessageBufferSize(); - clone.inputBufferSize = this.getInputBufferSize(); - clone.asyncWriteTimeout = this.getAsyncWriteTimeout(); - return clone; - } - - /** - * Make a copy of the policy, with current values, but a different behavior. - * - * @param behavior the behavior to copy/clone - * @return the cloned policy with a new behavior. - * @deprecated use {@link #delegateAs(WebSocketBehavior)} instead - */ - @Deprecated - public WebSocketPolicy clonePolicy(WebSocketBehavior behavior) { - return delegateAs(behavior); - } - - public WebSocketPolicy delegateAs(WebSocketBehavior behavior) { - if (behavior == this.behavior) - return this; - - return new WebSocketPolicy.Delegated(this, behavior); - } - - /** - * The timeout in ms (milliseconds) for async write operations. - *

    - * Negative values indicate a disabled timeout. - * - * @return the timeout for async write operations. negative values indicate disabled timeout. - */ - @Deprecated - public long getAsyncWriteTimeout() { - return asyncWriteTimeout; - } - - public WebSocketBehavior getBehavior() { - return behavior; - } - - /** - * The time in ms (milliseconds) that a websocket connection may be idle before being closed automatically. - * - * @return the timeout in milliseconds for idle timeout. - */ - public long getIdleTimeout() { - return idleTimeout; - } - - /** - * The size of the input (read from network layer) buffer size. - *

    - * This is the raw read operation buffer size, before the parsing of the websocket frames. - * - * @return the raw network bytes read operation buffer size. - */ - public int getInputBufferSize() { - return inputBufferSize; - } - - /** - * Get the maximum size of a binary message buffer (for streaming writing) - * - * @return the maximum size of a binary message buffer - */ - public int getMaxBinaryMessageBufferSize() { - return maxBinaryMessageBufferSize; - } - - /** - * Get the maximum size of a binary message during parsing. - *

    - * This is a memory conservation option, memory over this limit will not be - * allocated by handling binary messages. This applies to individual frames, - * whole message handling, and partial message handling. - *

    - *

    - * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * - * @return the maximum size of a binary message - */ - public int getMaxBinaryMessageSize() { - return maxBinaryMessageSize; - } - - /** - * Get the maximum size of a text message buffer (for streaming writing) - * - * @return the maximum size of a text message buffer - */ - public int getMaxTextMessageBufferSize() { - return maxTextMessageBufferSize; - } - - /** - * Get the maximum size of a text message during parsing. - *

    - * This is a memory conservation option, memory over this limit will not be - * allocated by handling text messages. This applies to individual frames, - * whole message handling, and partial message handling. - *

    - *

    - * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * - * @return the maximum size of a text message. - */ - public int getMaxTextMessageSize() { - return maxTextMessageSize; - } - - /** - * The timeout in ms (milliseconds) for async write operations. - *

    - * Negative values indicate a disabled timeout. - * - * @param ms the timeout in milliseconds - */ - public void setAsyncWriteTimeout(long ms) { - assertLessThan("AsyncWriteTimeout", ms, "IdleTimeout", idleTimeout); - this.asyncWriteTimeout = ms; - } - - /** - * The time in ms (milliseconds) that a websocket may be idle before closing. - * - * @param ms the timeout in milliseconds - */ - public void setIdleTimeout(long ms) { - assertGreaterThan("IdleTimeout", ms, 0); - this.idleTimeout = ms; - } - - /** - * The size of the input (read from network layer) buffer size. - * - * @param size the size in bytes - */ - public void setInputBufferSize(int size) { - assertGreaterThan("InputBufferSize", size, 1); - this.inputBufferSize = size; - } - - /** - * The maximum size of a binary message buffer. - *

    - * Used ONLY for stream based binary message writing. - * - * @param size the maximum size of the binary message buffer - */ - public void setMaxBinaryMessageBufferSize(int size) { - assertGreaterThan("MaxBinaryMessageBufferSize", size, 1); - - this.maxBinaryMessageBufferSize = size; - } - - /** - * The maximum size of a binary message during parsing. - *

    - * This is a memory conservation option, memory over this limit will not be - * allocated by handling binary messages. This applies to individual frames, - * whole message handling, and partial message handling. - *

    - *

    - * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * - * @param size the maximum allowed size of a binary message. - */ - public void setMaxBinaryMessageSize(int size) { - assertGreaterThan("MaxBinaryMessageSize", size, -1); - - this.maxBinaryMessageSize = size; - } - - /** - * The maximum size of a text message buffer. - *

    - * Used ONLY for stream based text message writing. - * - * @param size the maximum size of the text message buffer - */ - public void setMaxTextMessageBufferSize(int size) { - assertGreaterThan("MaxTextMessageBufferSize", size, 1); - - this.maxTextMessageBufferSize = size; - } - - /** - * The maximum size of a text message during parsing. - *

    - * This is a memory conservation option, memory over this limit will not be - * allocated by handling text messages. This applies to individual frames, - * whole message handling, and partial message handling. - *

    - *

    - * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

    - * - * @param size the maximum allowed size of a text message. - */ - public void setMaxTextMessageSize(int size) { - assertGreaterThan("MaxTextMessageSize", size, -1); - - this.maxTextMessageSize = size; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(this.getClass().getSimpleName()); - builder.append("@").append(Integer.toHexString(hashCode())); - builder.append("[behavior=").append(getBehavior()); - builder.append(",maxTextMessageSize=").append(getMaxTextMessageSize()); - builder.append(",maxTextMessageBufferSize=").append(getMaxTextMessageBufferSize()); - builder.append(",maxBinaryMessageSize=").append(getMaxBinaryMessageSize()); - builder.append(",maxBinaryMessageBufferSize=").append(getMaxTextMessageBufferSize()); - builder.append(",asyncWriteTimeout=").append(getAsyncWriteTimeout()); - builder.append(",idleTimeout=").append(getIdleTimeout()); - builder.append(",inputBufferSize=").append(getInputBufferSize()); - builder.append("]"); - return builder.toString(); - } - - /** - * Allows Behavior to be changed, but the settings to delegated. - *

    - * This rears its ugly head when a JSR356 Server Container is used as a - * JSR356 Client Container. - * The JSR356 Server Container is Behavior SERVER, but its container - * level Policy is shared with the JSR356 Client Container as well. - * This allows a delegate to the policy with a different behavior. - *

    - */ - private class Delegated extends WebSocketPolicy { - private final WebSocketPolicy delegated; - - public Delegated(WebSocketPolicy policy, WebSocketBehavior behavior) { - super(behavior); - this.delegated = policy; - } - - @Override - public void assertValidBinaryMessageSize(int requestedSize) { - delegated.assertValidBinaryMessageSize(requestedSize); - } - - @Override - public void assertValidTextMessageSize(int requestedSize) { - delegated.assertValidTextMessageSize(requestedSize); - } - - @Override - public WebSocketPolicy clonePolicy() { - return delegated.clonePolicy(); - } - - @Override - public WebSocketPolicy clonePolicy(WebSocketBehavior behavior) { - return delegated.clonePolicy(behavior); - } - - @Override - public WebSocketPolicy delegateAs(WebSocketBehavior behavior) { - return delegated.delegateAs(behavior); - } - - @Override - public long getAsyncWriteTimeout() { - return delegated.getAsyncWriteTimeout(); - } - - @Override - public long getIdleTimeout() { - return delegated.getIdleTimeout(); - } - - @Override - public int getInputBufferSize() { - return delegated.getInputBufferSize(); - } - - @Override - public int getMaxBinaryMessageBufferSize() { - return delegated.getMaxBinaryMessageBufferSize(); - } - - @Override - public int getMaxBinaryMessageSize() { - return delegated.getMaxBinaryMessageSize(); - } - - @Override - public int getMaxTextMessageBufferSize() { - return delegated.getMaxTextMessageBufferSize(); - } - - @Override - public int getMaxTextMessageSize() { - return delegated.getMaxTextMessageSize(); - } - - @Override - public void setAsyncWriteTimeout(long ms) { - delegated.setAsyncWriteTimeout(ms); - } - - @Override - public void setIdleTimeout(long ms) { - delegated.setIdleTimeout(ms); - } - - @Override - public void setInputBufferSize(int size) { - delegated.setInputBufferSize(size); - } - - @Override - public void setMaxBinaryMessageBufferSize(int size) { - delegated.setMaxBinaryMessageBufferSize(size); - } - - @Override - public void setMaxBinaryMessageSize(int size) { - delegated.setMaxBinaryMessageSize(size); - } - - @Override - public void setMaxTextMessageBufferSize(int size) { - delegated.setMaxTextMessageBufferSize(size); - } - - @Override - public void setMaxTextMessageSize(int size) { - delegated.setMaxTextMessageSize(size); - } - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/ConnectionState.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/ConnectionState.java deleted file mode 100644 index fa3883064..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/ConnectionState.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.fireflysource.net.websocket.common.stream; - -import com.fireflysource.net.websocket.common.model.CloseInfo; - -/** - * Connection states as outlined in RFC6455. - */ -public enum ConnectionState { - /** - * [RFC] Initial state of a connection, the upgrade request / response is in progress - */ - CONNECTING, - /** - * [Impl] Intermediate state between CONNECTING and OPEN, used to indicate that a upgrade request/response is successful, but the end-user provided socket's - * onOpen code has yet to run. - *

    - * This state is to allow the local socket to initiate messages and frames, but to NOT start reading yet. - */ - CONNECTED, - /** - * [RFC] The websocket connection is established and open. - *

    - * This indicates that the Upgrade has succeed, and the end-user provided socket's onOpen code has completed. - *

    - * It is now time to start reading from the remote endpoint. - */ - OPEN, - /** - * [RFC] The websocket closing handshake is started. - *

    - * This can be considered a half-closed state. - *

    - * When receiving this as an event on {@link IOState.ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using - * the {@link CloseInfo} available from {@link IOState#getCloseInfo()} - */ - CLOSING, - /** - * [RFC] The websocket connection is closed. - *

    - * Connection should be disconnected and no further reads or writes should occur. - */ - CLOSED -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/ExtensionNegotiator.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/ExtensionNegotiator.java deleted file mode 100644 index 836194222..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/ExtensionNegotiator.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.fireflysource.net.websocket.common.stream; - - -import com.fireflysource.common.collection.CollectionUtils; -import com.fireflysource.common.object.Assert; -import com.fireflysource.net.websocket.common.decoder.Parser; -import com.fireflysource.net.websocket.common.encoder.Generator; -import com.fireflysource.net.websocket.common.extension.AbstractExtension; -import com.fireflysource.net.websocket.common.extension.ExtensionFactory; -import com.fireflysource.net.websocket.common.extension.WebSocketExtensionFactory; -import com.fireflysource.net.websocket.common.model.*; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - - -/** - * @author Pengtao Qiu - */ -public class ExtensionNegotiator { - - private ExtensionFactory factory; - private IncomingFrames nextIncomingFrames; - private OutgoingFrames nextOutgoingFrames; - private IncomingFrames incomingFrames; - private OutgoingFrames outgoingFrames; - - public ExtensionNegotiator() { - this(new WebSocketExtensionFactory()); - } - - public ExtensionNegotiator(ExtensionFactory factory) { - this.factory = factory; - } - - public ExtensionFactory getFactory() { - return factory; - } - - public void setFactory(ExtensionFactory factory) { - this.factory = factory; - } - - public List createExtensionConfigs(List rawSecWebSocketExtensions) { - return ExtensionConfig - .parseList(rawSecWebSocketExtensions) - .stream() - .filter(c -> factory.isAvailable(c.getName())) - .collect(Collectors.toList()); - } - - public void configureExtensions(List rawSecWebSocketExtensions, Parser parser, Generator generator, WebSocketPolicy policy) { - Assert.notNull(nextIncomingFrames, "The next incoming frames MUST be not null"); - Assert.notNull(nextOutgoingFrames, "The next outgoing frames MUST be not null"); - - List extensionConfigs = createExtensionConfigs(rawSecWebSocketExtensions); - if (CollectionUtils.isEmpty(extensionConfigs)) { - incomingFrames = nextIncomingFrames; - outgoingFrames = nextOutgoingFrames; - } else { - List incomingExtensions = createExtensions(extensionConfigs, policy); - List outgoingExtensions = createExtensions(extensionConfigs, policy); - - Collections.reverse(incomingExtensions); - - parser.configureFromExtensions(incomingExtensions); - generator.configureFromExtensions(outgoingExtensions); - - int lastIncoming = incomingExtensions.size() - 1; - for (int i = 0; i < incomingExtensions.size(); i++) { - int next = i + 1; - Extension extension = incomingExtensions.get(i); - if (next <= lastIncoming) { - extension.setNextIncomingFrames(incomingExtensions.get(next)); - } else { - extension.setNextIncomingFrames(nextIncomingFrames); - } - } - - int lastOutgoing = outgoingExtensions.size() - 1; - for (int i = 0; i < outgoingExtensions.size(); i++) { - int next = i + 1; - Extension extension = outgoingExtensions.get(i); - if (next <= lastOutgoing) { - extension.setNextOutgoingFrames(outgoingExtensions.get(next)); - } else { - extension.setNextOutgoingFrames(nextOutgoingFrames); - } - } - - incomingFrames = incomingExtensions.get(0); - outgoingFrames = outgoingExtensions.get(0); - } - } - - private List createExtensions(List extensionConfigs, WebSocketPolicy policy) { - return extensionConfigs - .stream() - .map(c -> { - Extension e = factory.newInstance(c); - if (e instanceof AbstractExtension) { - AbstractExtension abstractExtension = (AbstractExtension) e; - abstractExtension.setConfig(c); - abstractExtension.setPolicy(policy); - } - return e; - }) - .collect(Collectors.toList()); - } - - public IncomingFrames getNextIncomingFrames() { - return nextIncomingFrames; - } - - public void setNextIncomingFrames(IncomingFrames nextIncomingFrames) { - this.nextIncomingFrames = nextIncomingFrames; - } - - public OutgoingFrames getNextOutgoingFrames() { - return nextOutgoingFrames; - } - - public void setNextOutgoingFrames(OutgoingFrames nextOutgoingFrames) { - this.nextOutgoingFrames = nextOutgoingFrames; - } - - public IncomingFrames getIncomingFrames() { - return incomingFrames; - } - - public OutgoingFrames getOutgoingFrames() { - return outgoingFrames; - } - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/IOState.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/IOState.java deleted file mode 100644 index bf14ca191..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/stream/IOState.java +++ /dev/null @@ -1,491 +0,0 @@ -package com.fireflysource.net.websocket.common.stream; - -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.WebSocketConnectionState; -import com.fireflysource.net.websocket.common.model.CloseInfo; -import com.fireflysource.net.websocket.common.model.StatusCode; - -import java.io.EOFException; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Simple state tracker for Input / Output and {@link ConnectionState}. - *

    - * Use the various known .on*() methods to trigger a state change. - *

      - *
    • {@link #onOpen()} - connection has been opened
    • - *
    - */ -public class IOState implements WebSocketConnectionState { - /** - * The source of a close handshake. (ie: who initiated it). - */ - private enum CloseHandshakeSource { - /** - * No close handshake initiated (yet) - */ - NONE, - /** - * Local side initiated the close handshake - */ - LOCAL, - /** - * Remote side initiated the close handshake - */ - REMOTE, - /** - * An abnormal close situation (disconnect, timeout, etc...) - */ - ABNORMAL - } - - public interface ConnectionStateListener { - void onConnectionStateChange(ConnectionState state); - } - - private static LazyLogger LOG = SystemLogger.create(IOState.class); - - private ConnectionState state; - private final List listeners = new LinkedList<>(); - - /** - * Is input on websocket available (for reading frames). - * Used to determine close handshake completion, and track half-close states - */ - private boolean inputAvailable; - /** - * Is output on websocket available (for writing frames). - * Used to determine close handshake completion, and track half-closed states. - */ - private boolean outputAvailable; - /** - * Initiator of the close handshake. - * Used to determine who initiated a close handshake for reply reasons. - */ - private CloseHandshakeSource closeHandshakeSource; - /** - * The close info for the initiator of the close handshake. - * It is possible in abnormal close scenarios to have a different - * final close info that is used to notify the WS-Endpoint's onClose() - * events with. - */ - private CloseInfo closeInfo; - /** - * Atomic reference to the final close info. - * This can only be set once, and is used for the WS-Endpoint's onClose() - * event. - */ - private AtomicReference finalClose = new AtomicReference<>(); - /** - * Tracker for if the close handshake was completed successfully by - * both sides. False if close was sudden or abnormal. - */ - private boolean cleanClose; - - /** - * Create a new IOState, initialized to {@link ConnectionState#CONNECTING} - */ - public IOState() { - this.state = ConnectionState.CONNECTING; - this.inputAvailable = false; - this.outputAvailable = false; - this.closeHandshakeSource = CloseHandshakeSource.NONE; - this.closeInfo = null; - this.cleanClose = false; - } - - public void addListener(ConnectionStateListener listener) { - listeners.add(listener); - } - - public CloseInfo getCloseInfo() { - CloseInfo ci = finalClose.get(); - if (ci != null) { - return ci; - } - return closeInfo; - } - - public ConnectionState getConnectionState() { - return state; - } - - public boolean isClosed() { - return (state == ConnectionState.CLOSED); - } - - public boolean isInputAvailable() { - return inputAvailable; - } - - public boolean isOpen() { - return !isClosed(); - } - - public boolean isOutputAvailable() { - return outputAvailable; - } - - private void notifyStateListeners(ConnectionState state) { - if (LOG.isDebugEnabled()) - LOG.debug("Notify State Listeners: {}", state); - for (ConnectionStateListener listener : listeners) { - if (LOG.isDebugEnabled()) { - LOG.debug("{}.onConnectionStateChange({})", listener.getClass().getSimpleName(), state.name()); - } - try { - listener.onConnectionStateChange(state); - } catch (Exception e) { - LOG.error("handle websocket connection state change event exception.", e); - } - } - } - - /** - * A websocket connection has been disconnected for abnormal close reasons. - *

    - * This is the low level disconnect of the socket. It could be the result of a normal close operation, from an IO error, or even from a timeout. - * - * @param close the close information - */ - public void onAbnormalClose(CloseInfo close) { - if (LOG.isDebugEnabled()) - LOG.debug("onAbnormalClose({})", close); - - if (this.state == ConnectionState.CLOSED) { - // already closed - return; - } - - if (this.state == ConnectionState.OPEN) { - this.cleanClose = false; - } - - this.state = ConnectionState.CLOSED; - finalClose.compareAndSet(null, close); - this.inputAvailable = false; - this.outputAvailable = false; - this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; - ConnectionState event = this.state; - notifyStateListeners(event); - } - - /** - * A close handshake has been issued from the local endpoint - * - * @param closeInfo the close information - */ - public void onCloseLocal(CloseInfo closeInfo) { - boolean open = false; - - ConnectionState initialState = this.state; - if (LOG.isDebugEnabled()) - LOG.debug("onCloseLocal({}) : {}", closeInfo, initialState); - if (initialState == ConnectionState.CLOSED) { - // already closed - if (LOG.isDebugEnabled()) - LOG.debug("already closed"); - return; - } - - if (initialState == ConnectionState.CONNECTED) { - // fast close. a local close request from end-user onConnect/onOpen method - if (LOG.isDebugEnabled()) - LOG.debug("FastClose in CONNECTED detected"); - open = true; - } - - if (open) - openAndCloseLocal(closeInfo); - else - closeLocal(closeInfo); - } - - private void openAndCloseLocal(CloseInfo closeInfo) { - // Force the state open (to allow read/write to endpoint) - onOpen(); - if (LOG.isDebugEnabled()) - LOG.debug("FastClose continuing with Closure"); - closeLocal(closeInfo); - } - - private void closeLocal(CloseInfo closeInfo) { - ConnectionState event = null; - ConnectionState abnormalEvent = null; - - if (LOG.isDebugEnabled()) - LOG.debug("onCloseLocal(), input={}, output={}", inputAvailable, outputAvailable); - - this.closeInfo = closeInfo; - - // Turn off further output. - outputAvailable = false; - - if (closeHandshakeSource == CloseHandshakeSource.NONE) { - closeHandshakeSource = CloseHandshakeSource.LOCAL; - } - - if (!inputAvailable) { - if (LOG.isDebugEnabled()) - LOG.debug("Close Handshake satisfied, disconnecting"); - cleanClose = true; - this.state = ConnectionState.CLOSED; - finalClose.compareAndSet(null, closeInfo); - event = this.state; - } else if (this.state == ConnectionState.OPEN) { - // We are now entering CLOSING (or half-closed). - this.state = ConnectionState.CLOSING; - event = this.state; - - // If abnormal, we don't expect an answer. - if (closeInfo.isAbnormal()) { - abnormalEvent = ConnectionState.CLOSED; - finalClose.compareAndSet(null, closeInfo); - cleanClose = false; - outputAvailable = false; - inputAvailable = false; - closeHandshakeSource = CloseHandshakeSource.ABNORMAL; - } - } - - // Only notify on state change events - if (event != null) { - notifyStateListeners(event); - if (abnormalEvent != null) { - notifyStateListeners(abnormalEvent); - } - } - } - - /** - * A close handshake has been received from the remote endpoint - * - * @param closeInfo the close information - */ - public void onCloseRemote(CloseInfo closeInfo) { - if (LOG.isDebugEnabled()) - LOG.debug("onCloseRemote({})", closeInfo); - - if (this.state == ConnectionState.CLOSED) { - // already closed - return; - } - - if (LOG.isDebugEnabled()) - LOG.debug("onCloseRemote(), input={}, output={}", inputAvailable, outputAvailable); - - this.closeInfo = closeInfo; - - // turn off further input - inputAvailable = false; - - if (closeHandshakeSource == CloseHandshakeSource.NONE) { - closeHandshakeSource = CloseHandshakeSource.REMOTE; - } - - ConnectionState event = null; - if (!outputAvailable) { - LOG.debug("Close Handshake satisfied, disconnecting"); - cleanClose = true; - state = ConnectionState.CLOSED; - finalClose.compareAndSet(null, closeInfo); - event = this.state; - } else if (this.state == ConnectionState.OPEN) { - // We are now entering CLOSING (or half-closed) - this.state = ConnectionState.CLOSING; - event = this.state; - } - - - // Only notify on state change events - if (event != null) { - notifyStateListeners(event); - } - } - - /** - * WebSocket has successfully upgraded, but the end-user onOpen call hasn't run yet. - *

    - * This is an intermediate state between the RFC's {@link ConnectionState#CONNECTING} and {@link ConnectionState#OPEN} - */ - public void onConnected() { - if (this.state != ConnectionState.CONNECTING) { - LOG.debug("Unable to set to connected, not in CONNECTING state: {}", this.state); - return; - } - - this.state = ConnectionState.CONNECTED; - inputAvailable = false; // cannot read (yet) - outputAvailable = true; // write allowed - ConnectionState event = this.state; - notifyStateListeners(event); - } - - /** - * A websocket connection has finished its upgrade handshake, and is now open. - */ - public void onOpen() { - if (LOG.isDebugEnabled()) - LOG.debug("onOpened()"); - - if (this.state == ConnectionState.OPEN) { - // already opened - return; - } - - if (this.state != ConnectionState.CONNECTED) { - LOG.debug("Unable to open, not in CONNECTED state: {}", this.state); - return; - } - - this.state = ConnectionState.OPEN; - this.inputAvailable = true; - this.outputAvailable = true; - ConnectionState event = this.state; - - notifyStateListeners(event); - } - - /** - * The local endpoint has reached a read failure. - *

    - * This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect. - * - * @param t the read failure - */ - public void onReadFailure(Throwable t) { - if (this.state == ConnectionState.CLOSED) { - // already closed - return; - } - - // Build out Close Reason - String reason = "WebSocket Read Failure"; - if (t instanceof EOFException) { - reason = "WebSocket Read EOF"; - Throwable cause = t.getCause(); - if ((cause != null) && (StringUtils.hasText(cause.getMessage()))) { - reason = "EOF: " + cause.getMessage(); - } - } else { - if (StringUtils.hasText(t.getMessage())) { - reason = t.getMessage(); - } - } - - CloseInfo close = new CloseInfo(StatusCode.ABNORMAL, reason); - finalClose.compareAndSet(null, close); - closeAndNotify(close); - } - - /** - * The local endpoint has reached a write failure. - *

    - * A low level I/O failure, or even a firefly side EndPoint close (from idle timeout) are a few scenarios - * - * @param t the throwable that caused the write failure - */ - public void onWriteFailure(Throwable t) { - if (this.state == ConnectionState.CLOSED) { - // already closed - return; - } - - // Build out Close Reason - String reason = "WebSocket Write Failure"; - if (t instanceof EOFException) { - reason = "WebSocket Write EOF"; - Throwable cause = t.getCause(); - if ((cause != null) && (StringUtils.hasText(cause.getMessage()))) { - reason = "EOF: " + cause.getMessage(); - } - } else { - if (StringUtils.hasText(t.getMessage())) { - reason = t.getMessage(); - } - } - - CloseInfo close = new CloseInfo(StatusCode.ABNORMAL, reason); - - finalClose.compareAndSet(null, close); - - this.cleanClose = false; - this.state = ConnectionState.CLOSED; - this.inputAvailable = false; - this.outputAvailable = false; - this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; - ConnectionState event = this.state; - - notifyStateListeners(event); - } - - public void onDisconnected() { - if (this.state == ConnectionState.CLOSED) { - // already closed - return; - } - - CloseInfo close = new CloseInfo(StatusCode.ABNORMAL, "Disconnected"); - closeAndNotify(close); - } - - private void closeAndNotify(CloseInfo close) { - this.cleanClose = false; - this.state = ConnectionState.CLOSED; - this.closeInfo = close; - this.inputAvailable = false; - this.outputAvailable = false; - this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; - ConnectionState event = this.state; - notifyStateListeners(event); - } - - public boolean isAbnormalClose() { - return closeHandshakeSource == CloseHandshakeSource.ABNORMAL; - } - - public boolean isCleanClose() { - return cleanClose; - } - - public boolean isLocalCloseInitiated() { - return closeHandshakeSource == CloseHandshakeSource.LOCAL; - } - - public boolean isRemoteCloseInitiated() { - return closeHandshakeSource == CloseHandshakeSource.REMOTE; - } - - @Override - public String toString() { - StringBuilder str = new StringBuilder(); - str.append(this.getClass().getSimpleName()); - str.append("@").append(Integer.toHexString(hashCode())); - str.append("[").append(state); - str.append(','); - if (!inputAvailable) { - str.append('!'); - } - str.append("in,"); - if (!outputAvailable) { - str.append('!'); - } - str.append("out"); - if ((state == ConnectionState.CLOSED) || (state == ConnectionState.CLOSING)) { - CloseInfo ci = finalClose.get(); - if (ci != null) { - str.append(",finalClose=").append(ci); - } else { - str.append(",close=").append(closeInfo); - } - str.append(",clean=").append(cleanClose); - str.append(",closeSource=").append(closeHandshakeSource); - } - str.append(']'); - return str.toString(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/utils/QuoteUtil.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/utils/QuoteUtil.java deleted file mode 100644 index 463efef98..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/utils/QuoteUtil.java +++ /dev/null @@ -1,375 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.NoSuchElementException; - -/** - * Provide some consistent Http header value and Extension configuration parameter quoting support. - *

      - *
    • ABNF defined extension parameter parsing requirements of RFC-6455 (WebSocket) ABNF, is slightly different than the ABNF parsing defined in RFC-2616 - * (HTTP/1.1).
    • - *
    • Future HTTPbis ABNF changes for parsing will impact QuotedStringTokenizer
    • - *
    - * It was decided to keep this implementation separate for the above reasons. - */ -public class QuoteUtil { - private static class DeQuotingStringIterator implements Iterator { - private enum State { - START, - TOKEN, - QUOTE_SINGLE, - QUOTE_DOUBLE - } - - private final String input; - private final String delims; - private StringBuilder token; - private boolean hasToken = false; - private int i = 0; - - public DeQuotingStringIterator(String input, String delims) { - this.input = input; - this.delims = delims; - int len = input.length(); - token = new StringBuilder(len > 1024 ? 512 : len / 2); - } - - private void appendToken(char c) { - if (hasToken) { - token.append(c); - } else { - if (Character.isWhitespace(c)) { - return; // skip whitespace at start of token. - } else { - token.append(c); - hasToken = true; - } - } - } - - @Override - public boolean hasNext() { - // already found a token - if (hasToken) { - return true; - } - - State state = State.START; - boolean escape = false; - int inputLen = input.length(); - - while (i < inputLen) { - char c = input.charAt(i++); - - switch (state) { - case START: { - if (c == '\'') { - state = State.QUOTE_SINGLE; - appendToken(c); - } else if (c == '\"') { - state = State.QUOTE_DOUBLE; - appendToken(c); - } else { - appendToken(c); - state = State.TOKEN; - } - break; - } - case TOKEN: { - if (delims.indexOf(c) >= 0) { - // System.out.printf("hasNext/t: %b [%s]%n",hasToken,token); - return hasToken; - } else if (c == '\'') { - state = State.QUOTE_SINGLE; - } else if (c == '\"') { - state = State.QUOTE_DOUBLE; - } - appendToken(c); - break; - } - case QUOTE_SINGLE: { - if (escape) { - escape = false; - appendToken(c); - } else if (c == '\'') { - appendToken(c); - state = State.TOKEN; - } else if (c == '\\') { - escape = true; - } else { - appendToken(c); - } - break; - } - case QUOTE_DOUBLE: { - if (escape) { - escape = false; - appendToken(c); - } else if (c == '\"') { - appendToken(c); - state = State.TOKEN; - } else if (c == '\\') { - escape = true; - } else { - appendToken(c); - } - break; - } - } - // System.out.printf("%s <%s> : [%s]%n",state,c,token); - } - // System.out.printf("hasNext/e: %b [%s]%n",hasToken,token); - return hasToken; - } - - @Override - public String next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - String ret = token.toString(); - token.setLength(0); - hasToken = false; - return QuoteUtil.dequote(ret.trim()); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Remove not supported with this iterator"); - } - } - - /** - * ABNF from RFC 2616, RFC 822, and RFC 6455 specified characters requiring quoting. - */ - public static final String ABNF_REQUIRED_QUOTING = "\"'\\\n\r\t\f\b%+ ;="; - - private static final char UNICODE_TAG = 0xFFFF; - private static final char[] escapes = new char[32]; - - static { - Arrays.fill(escapes, UNICODE_TAG); - // non-unicode - escapes['\b'] = 'b'; - escapes['\t'] = 't'; - escapes['\n'] = 'n'; - escapes['\f'] = 'f'; - escapes['\r'] = 'r'; - } - - private static int dehex(byte b) { - if ((b >= '0') && (b <= '9')) { - return (byte) (b - '0'); - } - if ((b >= 'a') && (b <= 'f')) { - return (byte) ((b - 'a') + 10); - } - if ((b >= 'A') && (b <= 'F')) { - return (byte) ((b - 'A') + 10); - } - throw new IllegalArgumentException("!hex:" + Integer.toHexString(0xff & b)); - } - - /** - * Remove quotes from a string, only if the input string start with and end with the same quote character. - * - * @param str the string to remove surrounding quotes from - * @return the de-quoted string - */ - public static String dequote(String str) { - char start = str.charAt(0); - if ((start == '\'') || (start == '\"')) { - // possibly quoted - char end = str.charAt(str.length() - 1); - if (start == end) { - // dequote - return str.substring(1, str.length() - 1); - } - } - return str; - } - - public static void escape(StringBuilder buf, String str) { - for (char c : str.toCharArray()) { - if (c >= 32) { - // non special character - if ((c == '"') || (c == '\\')) { - buf.append('\\'); - } - buf.append(c); - } else { - // special characters, requiring escaping - char escaped = escapes[c]; - - // is this a unicode escape? - if (escaped == UNICODE_TAG) { - buf.append("\\u00"); - if (c < 0x10) { - buf.append('0'); - } - buf.append(Integer.toString(c, 16)); // hex - } else { - // normal escape - buf.append('\\').append(escaped); - } - } - } - } - - /** - * Simple quote of a string, escaping where needed. - * - * @param buf the StringBuilder to append to - * @param str the string to quote - */ - public static void quote(StringBuilder buf, String str) { - buf.append('"'); - escape(buf, str); - buf.append('"'); - } - - /** - * Append into buf the provided string, adding quotes if needed. - *

    - * Quoting is determined if any of the characters in the delim are found in the input str. - * - * @param buf the buffer to append to - * @param str the string to possibly quote - * @param delim the delimiter characters that will trigger automatic quoting - */ - public static void quoteIfNeeded(StringBuilder buf, String str, String delim) { - if (str == null) { - return; - } - // check for delimiters in input string - int len = str.length(); - if (len == 0) { - return; - } - int ch; - for (int i = 0; i < len; i++) { - ch = str.codePointAt(i); - if (delim.indexOf(ch) >= 0) { - // found a delimiter codepoint. we need to quote it. - quote(buf, str); - return; - } - } - - // no special delimiters used, no quote needed. - buf.append(str); - } - - /** - * Create an iterator of the input string, breaking apart the string at the provided delimiters, removing quotes and triming the parts of the string as - * needed. - * - * @param str the input string to split apart - * @param delims the delimiter characters to split the string on - * @return the iterator of the parts of the string, trimmed, with quotes around the string part removed, and unescaped - */ - public static Iterator splitAt(String str, String delims) { - return new DeQuotingStringIterator(str.trim(), delims); - } - - public static String unescape(String str) { - if (str == null) { - // nothing there - return null; - } - - int len = str.length(); - if (len <= 1) { - // impossible to be escaped - return str; - } - - StringBuilder ret = new StringBuilder(len - 2); - boolean escaped = false; - char c; - for (int i = 0; i < len; i++) { - c = str.charAt(i); - if (escaped) { - escaped = false; - switch (c) { - case 'n': - ret.append('\n'); - break; - case 'r': - ret.append('\r'); - break; - case 't': - ret.append('\t'); - break; - case 'f': - ret.append('\f'); - break; - case 'b': - ret.append('\b'); - break; - case '\\': - ret.append('\\'); - break; - case '/': - ret.append('/'); - break; - case '"': - ret.append('"'); - break; - case 'u': - ret.append((char) ((dehex((byte) str.charAt(i++)) << 24) + (dehex((byte) str.charAt(i++)) << 16) + (dehex((byte) str.charAt(i++)) << 8) + (dehex((byte) str - .charAt(i++))))); - break; - default: - ret.append(c); - } - } else if (c == '\\') { - escaped = true; - } else { - ret.append(c); - } - } - return ret.toString(); - } - - public static String join(Object[] objs, String delim) { - if (objs == null) { - return ""; - } - StringBuilder ret = new StringBuilder(); - int len = objs.length; - for (int i = 0; i < len; i++) { - if (i > 0) { - ret.append(delim); - } - if (objs[i] instanceof String) { - ret.append('"').append(objs[i]).append('"'); - } else { - ret.append(objs[i]); - } - } - return ret.toString(); - } - - public static String join(Collection objs, String delim) { - if (objs == null) { - return ""; - } - StringBuilder ret = new StringBuilder(); - boolean needDelim = false; - for (Object obj : objs) { - if (needDelim) { - ret.append(delim); - } - if (obj instanceof String) { - ret.append('"').append(obj).append('"'); - } else { - ret.append(obj); - } - needDelim = true; - } - return ret.toString(); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/utils/WSURI.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/common/utils/WSURI.java deleted file mode 100644 index f2fe069d9..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/common/utils/WSURI.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Objects; - - -/** - * Utility methods for converting a {@link URI} between an HTTP(S) and WS(S) URI. - */ -public final class WSURI { - /** - * Convert to HTTP http or https scheme URIs. - *

    - * Converting ws and wss URIs to their HTTP equivalent - * - * @param inputUri the input URI - * @return the HTTP scheme URI for the input URI. - * @throws URISyntaxException if unable to convert the input URI - */ - public static URI toHttp(final URI inputUri) throws URISyntaxException { - Objects.requireNonNull(inputUri, "Input URI must not be null"); - String wsScheme = inputUri.getScheme(); - if ("http".equalsIgnoreCase(wsScheme) || "https".equalsIgnoreCase(wsScheme)) { - // leave alone - return inputUri; - } - - if ("ws".equalsIgnoreCase(wsScheme)) { - // convert to http - return new URI("http" + inputUri.toString().substring(wsScheme.length())); - } - - if ("wss".equalsIgnoreCase(wsScheme)) { - // convert to https - return new URI("https" + inputUri.toString().substring(wsScheme.length())); - } - - throw new URISyntaxException(inputUri.toString(), "Unrecognized WebSocket scheme"); - } - - /** - * Convert to WebSocket ws or wss scheme URIs - *

    - * Converting http and https URIs to their WebSocket equivalent - * - * @param inputUrl the input URI - * @return the WebSocket scheme URI for the input URI. - * @throws URISyntaxException if unable to convert the input URI - */ - public static URI toWebsocket(CharSequence inputUrl) throws URISyntaxException { - return toWebsocket(new URI(inputUrl.toString())); - } - - /** - * Convert to WebSocket ws or wss scheme URIs - *

    - * Converting http and https URIs to their WebSocket equivalent - * - * @param inputUrl the input URI - * @param query the optional query string - * @return the WebSocket scheme URI for the input URI. - * @throws URISyntaxException if unable to convert the input URI - */ - public static URI toWebsocket(CharSequence inputUrl, String query) throws URISyntaxException { - if (query == null) { - return toWebsocket(new URI(inputUrl.toString())); - } - return toWebsocket(new URI(inputUrl.toString() + '?' + query)); - } - - /** - * Convert to WebSocket ws or wss scheme URIs - * - *

    - * Converting http and https URIs to their WebSocket equivalent - * - * @param inputUri the input URI - * @return the WebSocket scheme URI for the input URI. - * @throws URISyntaxException if unable to convert the input URI - */ - public static URI toWebsocket(final URI inputUri) throws URISyntaxException { - Objects.requireNonNull(inputUri, "Input URI must not be null"); - String httpScheme = inputUri.getScheme(); - if ("ws".equalsIgnoreCase(httpScheme) || "wss".equalsIgnoreCase(httpScheme)) { - // keep as-is - return inputUri; - } - - if ("http".equalsIgnoreCase(httpScheme)) { - // convert to ws - return new URI("ws" + inputUri.toString().substring(httpScheme.length())); - } - - if ("https".equalsIgnoreCase(httpScheme)) { - // convert to wss - return new URI("wss" + inputUri.toString().substring(httpScheme.length())); - } - - throw new URISyntaxException(inputUri.toString(), "Unrecognized HTTP scheme"); - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/ExtensionSelector.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/server/ExtensionSelector.java deleted file mode 100644 index e3a4fff90..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/ExtensionSelector.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.net.websocket.server; - -import java.util.List; - -/** - * @author Pengtao Qiu - */ -public interface ExtensionSelector { - - /** - * Select the supported extensions. - * - * @param extensions The client supported extensions. - * @return The server selected extensions. - */ - List select(List extensions); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/SubProtocolSelector.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/server/SubProtocolSelector.java deleted file mode 100644 index bfac743d5..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/SubProtocolSelector.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.net.websocket.server; - -import java.util.List; - -/** - * @author Pengtao Qiu - */ -public interface SubProtocolSelector { - - /** - * Select the supported sub protocols. - * - * @param protocols The client supported sub protocols. - * @return The server selected sub protocols. - */ - List select(List protocols); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketManager.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketManager.java deleted file mode 100644 index 375768b83..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketManager.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.websocket.server; - -/** - * @author Pengtao Qiu - */ -public interface WebSocketManager extends Cloneable { - - /** - * Find the websocket handler. - * - * @param path The request path. - * @return The websocket handler. - */ - WebSocketServerConnectionHandler findWebSocketHandler(String path); - - /** - * Register the websocket handler. - * - * @param connectionHandler The websocket handler. - */ - void register(WebSocketServerConnectionHandler connectionHandler); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionBuilder.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionBuilder.java deleted file mode 100644 index b4093d8e2..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.fireflysource.net.websocket.server; - -import com.fireflysource.net.http.server.HttpServer; -import com.fireflysource.net.websocket.common.WebSocketMessageHandler; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -/** - * @author Pengtao Qiu - */ -public interface WebSocketServerConnectionBuilder { - - /** - * Set the websocket url. - * - * @param url The websocket url. - * @return The websocket server connection builder. - */ - WebSocketServerConnectionBuilder url(String url); - - /** - * Select the extensions. - * - * @param selector The websocket extension selector. - * @return The websocket server connection builder. - */ - WebSocketServerConnectionBuilder onExtensionSelect(ExtensionSelector selector); - - /** - * Select the sub protocols. - * - * @param selector The websocket sub protocol selector. - * @return The websocket server connection builder. - */ - WebSocketServerConnectionBuilder onSubProtocolSelect(SubProtocolSelector selector); - - /** - * Set the websocket policy. - * - * @param policy The websocket policy. - * @return The websocket server connection builder. - */ - WebSocketServerConnectionBuilder policy(WebSocketPolicy policy); - - /** - * Set the websocket message handler. - * - * @param handler The websocket message handler. - * @return The websocket server connection builder. - */ - WebSocketServerConnectionBuilder onMessage(WebSocketMessageHandler handler); - - /** - * Set the websocket connection listener. - * - * @param listener The websocket connection listener. - * @return The HTTP server. - */ - HttpServer onAccept(WebSocketServerConnectionListener listener); - -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionHandler.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionHandler.java deleted file mode 100644 index 2306c8312..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.fireflysource.net.websocket.server; - -import com.fireflysource.net.websocket.common.WebSocketMessageHandler; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -/** - * @author Pengtao Qiu - */ -public class WebSocketServerConnectionHandler { - - private String url; - private ExtensionSelector extensionSelector; - private SubProtocolSelector subProtocolSelector; - private WebSocketPolicy policy; - private WebSocketServerConnectionListener connectionListener; - private WebSocketMessageHandler messageHandler; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public ExtensionSelector getExtensionSelector() { - return extensionSelector; - } - - public void setExtensionSelector(ExtensionSelector extensionSelector) { - this.extensionSelector = extensionSelector; - } - - public SubProtocolSelector getSubProtocolSelector() { - return subProtocolSelector; - } - - public void setSubProtocolSelector(SubProtocolSelector subProtocolSelector) { - this.subProtocolSelector = subProtocolSelector; - } - - public WebSocketPolicy getPolicy() { - return policy; - } - - public void setPolicy(WebSocketPolicy policy) { - this.policy = policy; - } - - public WebSocketServerConnectionListener getConnectionListener() { - return connectionListener; - } - - public void setConnectionListener(WebSocketServerConnectionListener connectionListener) { - this.connectionListener = connectionListener; - } - - public WebSocketMessageHandler getMessageHandler() { - return messageHandler; - } - - public void setMessageHandler(WebSocketMessageHandler messageHandler) { - this.messageHandler = messageHandler; - } -} diff --git a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionListener.java b/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionListener.java deleted file mode 100644 index 25f1d1629..000000000 --- a/firefly-net/src/main/java/com/fireflysource/net/websocket/server/WebSocketServerConnectionListener.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.net.websocket.server; - -import com.fireflysource.net.websocket.common.WebSocketConnection; - -import java.util.concurrent.CompletableFuture; - -/** - * @author Pengtao Qiu - */ -public interface WebSocketServerConnectionListener { - - CompletableFuture accept(WebSocketConnection connection); - -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/FireflyExtensions.kt b/firefly-net/src/main/kotlin/com/fireflysource/FireflyExtensions.kt deleted file mode 100644 index cd03f5e2c..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/FireflyExtensions.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource - -/** - * @author Pengtao Qiu - */ -typealias fx = `$` -typealias future = `$`.future -typealias consumer = `$`.consumer -typealias logger = `$`.logger \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/CommonTcpChannelGroup.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/CommonTcpChannelGroup.kt deleted file mode 100644 index bb6a7bbd3..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/CommonTcpChannelGroup.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.fireflysource.net - -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.lifecycle.ShutdownTasks -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.net.http.server.HttpServerFactory -import com.fireflysource.net.tcp.TcpClient -import com.fireflysource.net.tcp.TcpClientFactory -import com.fireflysource.net.tcp.TcpServer -import com.fireflysource.net.tcp.TcpServerFactory -import com.fireflysource.net.tcp.aio.AioTcpChannelGroup -import com.fireflysource.net.tcp.aio.TcpConfig - -/** - * @author Pengtao Qiu - */ -object CommonTcpChannelGroup : AbstractLifeCycle() { - - val group = AioTcpChannelGroup("common-tcp-channel-group") - val httpClient: HttpClient by lazy { createHttpClient() } - - init { - start() - } - - @JvmOverloads - fun createTcpServer(config: TcpConfig = TcpConfig()): TcpServer { - val server = TcpServerFactory.create(config) - server.tcpChannelGroup(group).stopTcpChannelGroup(false) - return server - } - - @JvmOverloads - fun createTcpClient(config: TcpConfig = TcpConfig()): TcpClient { - val client = TcpClientFactory.create(config) - client.tcpChannelGroup(group).stopTcpChannelGroup(false) - return client - } - - @JvmOverloads - fun createHttpServer(httpConfig: HttpConfig = HttpConfig()): HttpServer { - httpConfig.tcpChannelGroup = group - httpConfig.isStopTcpChannelGroup = false - return HttpServerFactory.create(httpConfig) - } - - @JvmOverloads - fun createHttpClient(httpConfig: HttpConfig = HttpConfig()): HttpClient { - httpConfig.tcpChannelGroup = group - httpConfig.isStopTcpChannelGroup = false - return HttpClientFactory.create(httpConfig) - } - - override fun destroy() { - httpClient.stop() - group.stop() - } - - override fun init() { - ShutdownTasks.register(CommonTcpChannelGroup::stop) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AbstractHttpClientConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AbstractHttpClientConnection.kt deleted file mode 100644 index 830c5e99d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AbstractHttpClientConnection.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.net.http.client.HttpClientConnection -import com.fireflysource.net.http.client.HttpClientRequestBuilder -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.common.model.HttpVersion - -interface AbstractHttpClientConnection : HttpClientConnection { - - override fun request(method: String, httpURI: HttpURI): HttpClientRequestBuilder { - Assert.hasText(httpURI.path, "The http path must be not null.") - return AsyncHttpClientConnectionRequestBuilder(this, method, httpURI, HttpVersion.HTTP_1_1) - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AbstractHttpClientRequestBuilder.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AbstractHttpClientRequestBuilder.kt deleted file mode 100644 index 945776d57..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AbstractHttpClientRequestBuilder.kt +++ /dev/null @@ -1,185 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.* -import com.fireflysource.net.http.client.impl.content.provider.ByteBufferContentProvider -import com.fireflysource.net.http.client.impl.content.provider.MultiPartContentProvider -import com.fireflysource.net.http.client.impl.content.provider.StringContentProvider -import com.fireflysource.net.http.common.model.* -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.function.BiConsumer -import java.util.function.Supplier - -@Suppress("ReplacePutWithAssignment") -abstract class AbstractHttpClientRequestBuilder( - method: String, - uri: HttpURI, - httpVersion: HttpVersion -) : HttpClientRequestBuilder { - - private val multiPartContentProvider: MultiPartContentProvider by lazy { MultiPartContentProvider() } - - val httpRequest: AsyncHttpClientRequest = AsyncHttpClientRequest() - - init { - httpRequest.method = method - httpRequest.uri = uri - httpRequest.httpVersion = httpVersion - } - - override fun cookies(cookies: MutableList?): HttpClientRequestBuilder { - httpRequest.cookies = cookies - return this - } - - override fun put(name: String, list: MutableList): HttpClientRequestBuilder { - httpRequest.httpFields.put(name, list) - return this - } - - override fun put(header: HttpHeader, value: String): HttpClientRequestBuilder { - httpRequest.httpFields.put(header, value) - return this - } - - override fun put(name: String, value: String): HttpClientRequestBuilder { - httpRequest.httpFields.put(name, value) - return this - } - - override fun put(field: HttpField): HttpClientRequestBuilder { - httpRequest.httpFields.put(field) - return this - } - - override fun addAll(fields: HttpFields): HttpClientRequestBuilder { - httpRequest.httpFields.addAll(fields) - return this - } - - override fun add(field: HttpField): HttpClientRequestBuilder { - httpRequest.httpFields.add(field) - return this - } - - override fun addCsv(header: HttpHeader, vararg values: String): HttpClientRequestBuilder { - httpRequest.httpFields.addCSV(header, *values) - return this - } - - override fun addCsv(header: String, vararg values: String): HttpClientRequestBuilder { - httpRequest.httpFields.addCSV(header, *values) - return this - } - - override fun trailerSupplier(trailerSupplier: Supplier?): HttpClientRequestBuilder { - httpRequest.trailerSupplier = trailerSupplier - return this - } - - override fun body(content: String): HttpClientRequestBuilder = body(content, StandardCharsets.UTF_8) - - override fun body(content: String, charset: Charset): HttpClientRequestBuilder = - contentProvider(StringContentProvider(content, charset)) - - override fun body(buffer: ByteBuffer): HttpClientRequestBuilder = contentProvider(ByteBufferContentProvider(buffer)) - - override fun contentProvider(contentProvider: HttpClientContentProvider?): HttpClientRequestBuilder { - httpRequest.contentProvider = contentProvider - return this - } - - override fun addPart( - name: String, - content: HttpClientContentProvider, - fields: HttpFields? - ): HttpClientRequestBuilder { - contentProvider(multiPartContentProvider) - multiPartContentProvider.addPart(name, content, fields) - return this - } - - override fun addFilePart( - name: String, - fileName: String, - content: HttpClientContentProvider, - fields: HttpFields? - ): HttpClientRequestBuilder { - contentProvider(multiPartContentProvider) - multiPartContentProvider.addFilePart(name, fileName, content, fields) - return this - } - - override fun addFormInput(name: String, value: String): HttpClientRequestBuilder { - httpRequest.formInputs.add(name, value) - return this - } - - override fun addFormInputs(name: String, values: MutableList): HttpClientRequestBuilder { - httpRequest.formInputs.addValues(name, values) - return this - } - - override fun putFormInput(name: String, value: String): HttpClientRequestBuilder { - httpRequest.formInputs.put(name, value) - return this - } - - override fun putFormInputs(name: String, values: MutableList): HttpClientRequestBuilder { - httpRequest.formInputs.putValues(name, values) - return this - } - - override fun removeFormInput(name: String): HttpClientRequestBuilder { - httpRequest.formInputs.remove(name) - return this - } - - override fun addQueryString(name: String, value: String): HttpClientRequestBuilder { - httpRequest.queryStrings.add(name, value) - return this - } - - override fun addQueryStrings(name: String, values: MutableList): HttpClientRequestBuilder { - httpRequest.queryStrings.addValues(name, values) - return this - } - - override fun putQueryString(name: String, value: String): HttpClientRequestBuilder { - httpRequest.queryStrings.put(name, value) - return this - } - - override fun putQueryStrings(name: String, values: MutableList): HttpClientRequestBuilder { - httpRequest.queryStrings[name] = values - return this - } - - override fun removeQueryString(name: String): HttpClientRequestBuilder { - httpRequest.queryStrings.remove(name) - return this - } - - override fun contentHandler(contentHandler: HttpClientContentHandler?): HttpClientRequestBuilder { - httpRequest.contentHandler = contentHandler - return this - } - - override fun http2Settings(http2Settings: Map?): HttpClientRequestBuilder { - httpRequest.http2Settings = http2Settings - return this - } - - override fun upgradeHttp2(): HttpClientRequestBuilder { - HttpProtocolNegotiator.addHttp2UpgradeHeader(httpRequest) - return this - } - - override fun onHeaderComplete(headerComplete: BiConsumer): HttpClientRequestBuilder { - httpRequest.headerComplete = headerComplete - return this - } - - override fun getHttpClientRequest(): HttpClientRequest = httpRequest -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClient.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClient.kt deleted file mode 100644 index d8e4ceb92..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClient.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.client.HttpClientConnection -import com.fireflysource.net.http.client.HttpClientRequestBuilder -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.common.model.HttpVersion -import com.fireflysource.net.tcp.TcpClientConnectionFactory -import com.fireflysource.net.tcp.aio.AioTcpChannelGroup -import com.fireflysource.net.websocket.client.WebSocketClientConnectionBuilder -import com.fireflysource.net.websocket.client.impl.AsyncWebSocketClientConnectionBuilder -import com.fireflysource.net.websocket.client.impl.AsyncWebSocketClientConnectionManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.util.concurrent.CompletableFuture - -class AsyncHttpClient(private val config: HttpConfig = HttpConfig()) : HttpClient, AbstractLifeCycle() { - - companion object { - private val log = SystemLogger.create(AsyncHttpClient::class.java) - } - - private val connectionFactory = TcpClientConnectionFactory( - createTcpChannelGroup(), - config.isStopTcpChannelGroup, - config.timeout, - config.secureEngineFactory - ) - private val httpClientConnectionManager = AsyncHttpClientConnectionManager(config, connectionFactory) - private val webSocketClientConnectionManager = AsyncWebSocketClientConnectionManager(config, connectionFactory) - - init { - start() - } - - private fun createTcpChannelGroup() = - if (config.tcpChannelGroup != null) config.tcpChannelGroup - else AioTcpChannelGroup("async-http-client") - - override fun request(method: String, httpURI: HttpURI): HttpClientRequestBuilder { - Assert.hasText(httpURI.path, "The http path must be not null.") - return AsyncHttpClientRequestBuilder(httpClientConnectionManager, method, httpURI, HttpVersion.HTTP_1_1) - } - - override fun createHttpClientConnection( - httpURI: HttpURI, - supportedProtocols: List - ): CompletableFuture { - return httpClientConnectionManager.createHttpClientConnection(httpURI, supportedProtocols) - } - - override fun websocket(): WebSocketClientConnectionBuilder { - return AsyncWebSocketClientConnectionBuilder(webSocketClientConnectionManager) - } - - override fun websocket(url: String): WebSocketClientConnectionBuilder { - return AsyncWebSocketClientConnectionBuilder(webSocketClientConnectionManager).url(url) - } - - override fun init() { - log.info { "AsyncHttpClient startup. Config: $config" } - } - - override fun destroy() { - httpClientConnectionManager.stop() - webSocketClientConnectionManager.stop() - } -} - -fun HttpClient.connectAsync(uri: String, block: suspend CoroutineScope.(HttpClientConnection) -> Unit) { - this.createHttpClientConnection(uri) - .thenAccept { connection -> connection.coroutineScope.launch { block(connection) } } -} - -fun HttpClient.connectAsync( - httpURI: HttpURI, - supportedProtocols: List, - block: suspend CoroutineScope.(HttpClientConnection) -> Unit -) { - this.createHttpClientConnection(httpURI, supportedProtocols) - .thenAccept { connection -> connection.coroutineScope.launch { block(connection) } } -} - -fun HttpClient.connectAsync( - httpURI: HttpURI, - block: suspend CoroutineScope.(HttpClientConnection) -> Unit -) { - this.createHttpClientConnection(httpURI) - .thenAccept { connection -> connection.coroutineScope.launch { block(connection) } } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientConnectionManager.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientConnectionManager.kt deleted file mode 100644 index 99b3687df..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientConnectionManager.kt +++ /dev/null @@ -1,271 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.coroutine.CoroutineDispatchers -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.HttpClientConnection -import com.fireflysource.net.http.client.HttpClientConnectionManager -import com.fireflysource.net.http.client.HttpClientRequest -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.client.impl.exception.UnhandledRequestException -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.exception.MissingRemoteHostException -import com.fireflysource.net.http.common.exception.MissingRemotePortException -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.common.model.isCloseConnection -import com.fireflysource.net.tcp.TcpClientConnectionFactory -import com.fireflysource.net.tcp.aio.ApplicationProtocol.HTTP1 -import com.fireflysource.net.tcp.aio.defaultSupportedProtocols -import com.fireflysource.net.tcp.aio.isSecureProtocol -import com.fireflysource.net.tcp.aio.schemaDefaultPort -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.asCompletableFuture -import kotlinx.coroutines.future.await -import java.net.InetSocketAddress -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.TimeUnit -import kotlin.math.absoluteValue - -class AsyncHttpClientConnectionManager( - private val config: HttpConfig, - private val connectionFactory: TcpClientConnectionFactory -) : HttpClientConnectionManager, AbstractLifeCycle() { - - companion object { - private val log = SystemLogger.create(AsyncHttpClientConnectionManager::class.java) - } - - private val connectionPoolMap = ConcurrentHashMap() - private val httpClientConnectionFactory = HttpClientConnectionFactory(config, connectionFactory) - private val scope = - CoroutineScope(CoroutineName("Firefly-HTTP-client-connection-manager") + CoroutineDispatchers.singleThread) - - init { - start() - } - - override fun send(request: HttpClientRequest): CompletableFuture { - val address = buildAddress(request.uri) - val nonPersistence = request.httpFields.isCloseConnection(request.httpVersion) - return if (nonPersistence) sendByNonPersistenceConnection(address, request) - else sendByPool(address, request) - } - - override fun createHttpClientConnection( - httpURI: HttpURI, - supportedProtocols: List - ): CompletableFuture { - return createHttpClientConnection(buildAddress(httpURI), supportedProtocols) - } - - private fun sendByPool(address: Address, request: HttpClientRequest): CompletableFuture { - val pool = connectionPoolMap.computeIfAbsent(address) { HttpClientConnectionPool(it) } - val message = RequestMessage(request, CompletableFuture()) - pool.sendMessage(message) - return message.response - } - - private fun sendByNonPersistenceConnection(address: Address, request: HttpClientRequest) = - createHttpClientConnection(address, listOf(HTTP1.value)).thenCompose { sendAndCloseConnection(it, request) } - - private fun sendAndCloseConnection(connection: HttpClientConnection, request: HttpClientRequest) = - connection.send(request).thenCompose { response -> connection.closeAsync().thenApply { response } } - - - private fun buildAddress(uri: HttpURI): Address { - if (uri.host.isNullOrBlank()) { - throw MissingRemoteHostException("The host is missing. uri: $uri") - } - val port: Int = if (uri.port > 0) { - uri.port - } else { - schemaDefaultPort[uri.scheme] ?: throw MissingRemotePortException("The address port is missing. uri: $uri") - } - val socketAddress = InetSocketAddress(uri.host, port) - val secure = isSecureProtocol(uri.scheme) - return Address(socketAddress, secure) - } - - private inner class HttpClientConnectionPool(val address: Address) : AbstractLifeCycle() { - private var i = 0 - private val httpClientConnections: Array = arrayOfNulls(config.connectionPoolSize) - private val channel: Channel = Channel(Channel.UNLIMITED) - private val checkJob = scope.launch { - delay(TimeUnit.SECONDS.toMillis(config.checkConnectionLiveInterval)) - sendMessage(CheckConnectionLiveMessage) - } - - init { - start() - } - - fun sendMessage(message: ClientRequestMessage) { - channel.trySend(message) - } - - override fun init() { - scope.launch { - requestLoop@ while (true) { - when (val message = channel.receive()) { - is RequestMessage -> handleRequest(message) - is UnhandledRequestMessage -> processUnhandledRequestInConnection(message) - is CheckConnectionLiveMessage -> checkConnectionLive() - is StopMessage -> { - onStop() - break@requestLoop - } - } - } - }.invokeOnCompletion { cause -> - if (cause != null) { - log.info { "The HTTP client connection pool job completion. cause: ${cause.message}" } - } - processUnhandledRequestInPool() - } - } - - override fun destroy() { - sendMessage(StopMessage) - checkJob.cancel() - } - - private fun onStop() { - httpClientConnections.forEach { it?.closeAsync() } - processUnhandledRequestInPool() - } - - private suspend fun handleRequest(message: RequestMessage) { - try { - val index = getIndex() - val connection = getConnection(index) - sendRequest(connection, message, index) - } catch (ex: Throwable) { - handleException(message, ex) - } - } - - private fun sendRequest(connection: HttpClientConnection, message: RequestMessage, index: Int) { - val request = message.request - val future = message.response - connection.send(request).handle { response, ex -> - if (ex != null) { - if (ex is UnhandledRequestException || ex.cause is UnhandledRequestException) { - sendMessage(UnhandledRequestMessage(request, future, connection.id, index)) - } else { - handleException(message, ex) - } - } else future.complete(response) - response - } - } - - private suspend fun processUnhandledRequestInConnection(message: UnhandledRequestMessage) { - val index = message.index - val connection = getConnection(index) - val newConnection = if (connection.id == message.connectionId) { - createConnection(index) - } else connection - val newMessage = RequestMessage(message.request, message.response) - sendRequest(newConnection, newMessage, index) - } - - private fun handleException(message: RequestMessage, ex: Throwable) { - val request = message.request - val future = message.response - if (future.isDone) return - - if (message.retry <= config.clientRetryCount) { - val retryCount = message.retry + 1 - log.warn { - "HTTP client request failure. The client will retry request. " + - "retryCount: $retryCount, " + - "url: ${request.uri}, " + - "info: ${ex.javaClass.name} ${ex.message}" - } - sendMessage(RequestMessage(request, future, retryCount)) - } else { - log.warn { - "HTTP client request failure. " + - "url: ${request.uri}, " + - "info: ${ex.javaClass.name} ${ex.message}" - } - future.completeExceptionally(ex) - } - } - - private fun getIndex(): Int { - val index = i.absoluteValue % config.connectionPoolSize - i++ - return index - } - - private suspend fun getConnection(index: Int): HttpClientConnection { - val oldConnection = httpClientConnections[index] - return if (oldConnection != null) { - if (oldConnection.isInvalid) createConnection(index) - else oldConnection - } else createConnection(index) - } - - private suspend fun createConnection(index: Int): HttpClientConnection { - val newConnection = createHttpClientConnection(address).await() - httpClientConnections[index] = newConnection - return newConnection - } - - private suspend fun checkConnectionLive() { - val maxIndex = config.connectionPoolSize - 1 - (0..maxIndex).forEach { index -> - try { - getConnection(index) - } catch (e: Exception) { - log.error(e) { "create http client connection failure. $address" } - } - } - } - - private fun processUnhandledRequestInPool() { - channel.consumeAll { message -> - if (message is RequestMessage) { - message.response.completeExceptionally(UnhandledRequestException("The HTTP client connection pool is shutdown. This request does not send.")) - } - } - } - } - - private fun createHttpClientConnection( - address: Address, - supportedProtocols: List = defaultSupportedProtocols - ): CompletableFuture { - return scope.async { httpClientConnectionFactory.createHttpClientConnection(address, supportedProtocols) } - .asCompletableFuture() - } - - override fun init() { - connectionFactory.start() - } - - override fun destroy() { - connectionPoolMap.values.forEach { it.stop() } - connectionPoolMap.clear() - connectionFactory.stop() - scope.cancel() - } -} - -sealed class ClientRequestMessage -class RequestMessage( - val request: HttpClientRequest, val response: CompletableFuture, - val retry: Int = 0 -) : ClientRequestMessage() - -class UnhandledRequestMessage( - val request: HttpClientRequest, val response: CompletableFuture, - val connectionId: Int, val index: Int -) : ClientRequestMessage() - -object CheckConnectionLiveMessage : ClientRequestMessage() -object StopMessage : ClientRequestMessage() \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientConnectionRequestBuilder.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientConnectionRequestBuilder.kt deleted file mode 100644 index eab0eeff9..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientConnectionRequestBuilder.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.HttpClientConnection -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.common.model.HttpVersion -import java.util.concurrent.CompletableFuture - -class AsyncHttpClientConnectionRequestBuilder( - private val connection: HttpClientConnection, - method: String, - uri: HttpURI, - httpVersion: HttpVersion -) : AbstractHttpClientRequestBuilder(method, uri, httpVersion) { - - override fun submit(): CompletableFuture = connection.send(httpRequest) - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientRequest.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientRequest.kt deleted file mode 100644 index 91da8ff5d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientRequest.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.HttpClientContentHandler -import com.fireflysource.net.http.client.HttpClientContentProvider -import com.fireflysource.net.http.client.HttpClientRequest -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.client.impl.content.handler.StringContentHandler -import com.fireflysource.net.http.client.impl.content.provider.MultiPartContentProvider -import com.fireflysource.net.http.client.impl.content.provider.StringContentProvider -import com.fireflysource.net.http.common.codec.CookieGenerator -import com.fireflysource.net.http.common.codec.UrlEncoded -import com.fireflysource.net.http.common.model.* -import java.nio.charset.StandardCharsets -import java.util.function.BiConsumer -import java.util.function.Supplier - -class AsyncHttpClientRequest : HttpClientRequest { - - companion object { - private val defaultHttpUri = HttpURI("http://localhost:8080/") - private val defaultMethod = HttpMethod.GET.value - } - - private var method: String = defaultMethod - private var uri: HttpURI = defaultHttpUri - private var httpVersion: HttpVersion = HttpVersion.HTTP_1_1 - private var queryStrings: UrlEncoded? = null - private var formInputs: UrlEncoded? = null - private var httpFields: HttpFields = HttpFields() - private var cookies: MutableList? = null - private var trailerSupplier: Supplier? = null - private var contentProvider: HttpClientContentProvider? = null - private var contentHandler: HttpClientContentHandler? = null - private var http2Settings: Map? = null - private var headerComplete: BiConsumer = BiConsumer { _, _ -> } - - override fun getMethod(): String = method - - override fun setMethod(method: String) { - this.method = method - } - - override fun getURI(): HttpURI = uri - - override fun setURI(uri: HttpURI) { - this.uri = uri - } - - override fun getHttpVersion(): HttpVersion = httpVersion - - override fun setHttpVersion(httpVersion: HttpVersion) { - this.httpVersion = httpVersion - } - - override fun getQueryStrings(): UrlEncoded { - val query = this.queryStrings - return if (query != null) { - query - } else { - val urlEncoded = UrlEncoded() - this.queryStrings = urlEncoded - urlEncoded - } - } - - override fun setQueryStrings(queryStrings: UrlEncoded?) { - this.queryStrings = queryStrings - } - - override fun getFormInputs(): UrlEncoded { - val form = this.formInputs - return if (form != null) { - form - } else { - val urlEncoded = UrlEncoded() - this.formInputs = urlEncoded - urlEncoded - } - } - - override fun setFormInputs(formInputs: UrlEncoded?) { - this.formInputs = formInputs - } - - override fun getHttpFields(): HttpFields = httpFields - - override fun setHttpFields(httpFields: HttpFields) { - this.httpFields = httpFields - } - - override fun getCookies(): MutableList? = cookies - - override fun setCookies(cookies: MutableList?) { - this.cookies = cookies - } - - override fun getTrailerSupplier(): Supplier? = trailerSupplier - - override fun setTrailerSupplier(trailerSupplier: Supplier?) { - this.trailerSupplier = trailerSupplier - } - - override fun setContentProvider(contentProvider: HttpClientContentProvider?) { - this.contentProvider = contentProvider - } - - override fun getContentProvider(): HttpClientContentProvider? = contentProvider - - override fun setContentHandler(contentHandler: HttpClientContentHandler?) { - this.contentHandler = contentHandler - } - - override fun getContentHandler(): HttpClientContentHandler? = contentHandler - - override fun setHttp2Settings(http2Settings: Map?) { - this.http2Settings = http2Settings - } - - override fun getHttp2Settings(): Map? = http2Settings - - override fun setHeaderComplete(headerComplete: BiConsumer) { - this.headerComplete = headerComplete - } - - override fun getHeaderComplete(): BiConsumer { - return headerComplete - } -} - -fun toMetaDataRequest(request: HttpClientRequest): MetaData.Request { - if (request.formInputs.size > 0) { - val formStr = request.formInputs.encode(StandardCharsets.UTF_8, true) - val stringContentProvider = StringContentProvider(formStr, StandardCharsets.UTF_8) - request.contentProvider = stringContentProvider - request.httpFields.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.value) - request.httpFields.put(HttpHeader.CONTENT_LENGTH, stringContentProvider.length().toString()) - } else { - val provider = request.contentProvider - if (provider != null) { - if (provider is MultiPartContentProvider) { - request.httpFields.put(HttpHeader.CONTENT_TYPE, provider.contentType) - if (provider.length() >= 0) { - request.httpFields.put(HttpHeader.CONTENT_LENGTH, provider.length().toString()) - } - } else { - val contentLength = provider.length() - if (contentLength >= 0) { - request.httpFields.put(HttpHeader.CONTENT_LENGTH, contentLength.toString()) - } else { - request.httpFields.put(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED.value) - } - } - } - } - - val uri = if (request.queryStrings.size > 0) { - val uri = HttpURI(request.uri) - if (request.uri.hasQuery()) { - uri.query = request.uri.query + "&" + request.queryStrings.encode(StandardCharsets.UTF_8, true) - } else { - uri.query = request.queryStrings.encode(StandardCharsets.UTF_8, true) - } - uri - } else request.uri - - if (request.cookies != null) { - request.httpFields.put(HttpHeader.COOKIE, CookieGenerator.generateCookies(request.cookies)) - } - - if (request.contentHandler == null) { - request.contentHandler = StringContentHandler(Long.MAX_VALUE) - } - - val len = request.contentProvider?.length() ?: -1 - val metaDataReq = MetaData.Request(request.method, uri, request.httpVersion, request.httpFields, len) - metaDataReq.trailerSupplier = request.trailerSupplier - return metaDataReq -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientRequestBuilder.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientRequestBuilder.kt deleted file mode 100644 index 36a61a78c..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientRequestBuilder.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.HttpClientConnectionManager -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.common.model.HttpVersion -import java.util.concurrent.CompletableFuture - -class AsyncHttpClientRequestBuilder( - private val connectionManager: HttpClientConnectionManager, - method: String, - uri: HttpURI, - httpVersion: HttpVersion -) : AbstractHttpClientRequestBuilder(method, uri, httpVersion) { - - override fun submit(): CompletableFuture = connectionManager.send(httpRequest) - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientResponse.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientResponse.kt deleted file mode 100644 index fc307805b..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/AsyncHttpClientResponse.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.HttpClientContentHandler -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.client.impl.content.handler.ByteBufferContentHandler -import com.fireflysource.net.http.client.impl.content.handler.StringContentHandler -import com.fireflysource.net.http.common.codec.CookieParser -import com.fireflysource.net.http.common.model.* -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.function.Supplier - -class AsyncHttpClientResponse( - val response: MetaData.Response, - private var contentHandler: HttpClientContentHandler? -) : HttpClientResponse { - - private val cookieList: List by lazy { - httpFields.getValuesList(HttpHeader.SET_COOKIE).map { - CookieParser.parseSetCookie(it) - } - } - - override fun getStatus(): Int = response.status - - override fun getReason(): String = - Optional.ofNullable(response.reason).orElseGet { HttpStatus.getMessage(response.status) } - - override fun getHttpVersion(): HttpVersion = response.httpVersion - - override fun getHttpFields(): HttpFields = response.fields - - override fun getCookies(): List = cookieList - - override fun getContentLength(): Long = response.contentLength - - override fun getTrailerSupplier(): Supplier = - Optional.ofNullable(response.trailerSupplier).orElseGet { Supplier { HttpFields() } } - - override fun getStringBody(): String = getStringBody(StandardCharsets.UTF_8) - - override fun getStringBody(charset: Charset): String = Optional - .ofNullable(contentHandler) - .filter { it is StringContentHandler } - .map { it as StringContentHandler } - .map { it.toString(charset, getContentEncoding()) } - .orElse("") - - override fun getBody(): List = Optional - .ofNullable(contentHandler) - .filter { it is ByteBufferContentHandler } - .map { it as ByteBufferContentHandler } - .map { it.getByteBuffers(getContentEncoding()) } - .orElse(listOf()) - - override fun getContentHandler(): HttpClientContentHandler? { - return contentHandler - } - - override fun setContentHandler(contentHandler: HttpClientContentHandler?) { - this.contentHandler = contentHandler - } - - private fun getContentEncoding(): Optional { - return Optional.ofNullable(this.httpFields[HttpHeader.CONTENT_ENCODING]) - .map { it.trim() } - .map { it.lowercase(Locale.getDefault()) } - .flatMap { ContentEncoding.from(it) } - } - - override fun toString(): String { - return """ - |response: ----------------- - |$status $reason $httpVersion - |$httpFields - |$stringBody - |${trailerSupplier.get()} - |end response -------------- - """.trimMargin() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http1ClientConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http1ClientConnection.kt deleted file mode 100644 index e2edc2411..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http1ClientConnection.kt +++ /dev/null @@ -1,555 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.codec.base64.Base64Utils -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.io.useAwait -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.Connection -import com.fireflysource.net.http.client.HttpClientContentProvider -import com.fireflysource.net.http.client.HttpClientRequest -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.expectUpgradeHttp2 -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.expectUpgradeWebsocket -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.isUpgradeSuccess -import com.fireflysource.net.http.client.impl.exception.UnhandledRequestException -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.TcpBasedHttpConnection -import com.fireflysource.net.http.common.exception.Http1GeneratingResultException -import com.fireflysource.net.http.common.exception.NotSupportHttpVersionException -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.common.v1.decoder.HttpParser -import com.fireflysource.net.http.common.v1.decoder.parseAll -import com.fireflysource.net.http.common.v1.encoder.HttpGenerator -import com.fireflysource.net.http.common.v1.encoder.HttpGenerator.Result.* -import com.fireflysource.net.http.common.v1.encoder.HttpGenerator.State.* -import com.fireflysource.net.http.common.v1.encoder.assert -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpCoroutineDispatcher -import com.fireflysource.net.websocket.client.WebSocketClientRequest -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.exception.UpgradeWebSocketConnectionException -import com.fireflysource.net.websocket.common.impl.AsyncWebSocketConnection -import com.fireflysource.net.websocket.common.model.AcceptHash -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout -import java.io.IOException -import java.nio.ByteBuffer -import java.time.Duration -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ThreadLocalRandom - -class Http1ClientConnection( - private val config: HttpConfig, - private val tcpConnection: TcpConnection -) : Connection by tcpConnection, TcpCoroutineDispatcher by tcpConnection, TcpBasedHttpConnection, - AbstractHttpClientConnection { - - companion object { - private val log = SystemLogger.create(Http1ClientConnection::class.java) - } - - private val generator = HttpGenerator() - - private val headerBuffer = BufferUtils.allocateDirect(config.headerBufferSize) - private val contentBuffer: ByteBuffer by lazy(LazyThreadSafetyMode.NONE) { BufferUtils.allocateDirect(config.contentBufferSize) } - private val chunkBuffer: ByteBuffer by lazy(LazyThreadSafetyMode.NONE) { BufferUtils.allocateDirect(HttpGenerator.CHUNK_SIZE) } - - private val handler = Http1ClientResponseHandler() - private val parser = HttpParser(handler) - private val requestChannel = Channel(Channel.UNLIMITED) - - @Volatile - private var httpVersion: HttpVersion = HttpVersion.HTTP_1_1 - - @Volatile - private var http2ClientConnection: Http2ClientConnection? = null - - private var upgradeWebSocketSuccess: Boolean = false - - init { - handleMessage() - } - - private fun handleMessage() = coroutineScope.launch { - while (true) { - when (val message = requestChannel.receive()) { - is RequestMessage -> { - val exit = handleRequest(message) - if (exit) { - break - } - } - is Stop -> break - } - } - }.invokeOnCompletion { e -> - if (e != null) { - log.info { "The HTTP1 message job completion exception. id: $id info: ${e.javaClass.name} ${e.message}" } - } else { - log.info("The HTTP1 message job completion. id: $id") - } - processUnhandledRequest() - } - - private suspend fun handleRequest(message: RequestMessage): Boolean { - return try { - log.debug { - """ - |handle http1 request: - |${message.request.method} ${message.request.uri} ${message.request.httpVersion} - |${message.request.fields} - """.trimMargin() - } - - handler.init( - message.httpClientRequest, - message.expectServerAcceptsContent, - message.isHttpTunnel - ) - if (message.expectServerAcceptsContent) { - // flush content data after the server response 100 continue. - generateRequestAndWaitServerAccept(message) - waitResponse(message) - } else { - // avoid the request can not respond the server error code if the I/O exception happened during the client sends data. - val result = coroutineScope.async { waitResponse(message) } - generateRequest(message) - result.await() - } - } catch (e: IOException) { - log.info { "The TCP connection IO exception. id: $id info: ${e.javaClass.name} ${e.message}" } - completeResponseExceptionally(message, e) - true - } catch (e: Exception) { - log.error { "HTTP1 client handler exception. id: $id info: ${e.javaClass.name} ${e.message}" } - completeResponseExceptionally(message, e) - true - } finally { - handler.reset() - parser.reset() - generator.reset() - } - } - - private fun processUnhandledRequest() { - when { - isUpgradeToHttp2Success() -> requestChannel.consumeAll { message -> - if (message is RequestMessage && !message.response.isDone) { - log.info { "Client sends remaining request via HTTP2 protocol. id: $id, path: ${message.httpClientRequest.uri.path}" } - val future = message.response - sendRequestViaHttp2(message.httpClientRequest) - .thenAccept { future.complete(it) } - .exceptionallyAccept { future.completeExceptionally(it) } - } - } - else -> requestChannel.consumeAll { message -> - if (message is RequestMessage && !message.response.isDone) { - message.response.completeExceptionally( - UnhandledRequestException( - "The HTTP1 connection has closed. This request does not send." - ) - ) - } - } - } - } - - private fun completeResponseExceptionally(message: RequestMessage, e: Exception) { - if (!message.response.isDone) { - message.response.completeExceptionally(e) - } - closeAsync() - } - - private suspend fun waitResponse(message: RequestMessage): Boolean { - val response = parseResponse(message) - val isNonPersistence = complete(response, message) - return when { - isNonPersistence -> true - message.expectUpgradeHttp2 && isUpgradeToHttp2Success() -> true - message.expectUpgradeWebSocket && upgradeWebSocketSuccess -> true - message.isHttpTunnel -> true - else -> false - } - } - - private suspend fun complete(response: HttpClientResponse, message: RequestMessage): Boolean { - val request = message.request - val isCloseConnection = - response.httpFields.isCloseConnection(response.httpVersion) || request.fields.isCloseConnection(request.httpVersion) - val isNonPersistence = !message.isHttpTunnel && isCloseConnection - if (isNonPersistence) { - this.useAwait { message.response.complete(response) } - log.debug { "HTTP1 connection closed. id: $id, closed: ${this.isClosed}" } - } else { - message.response.complete(response) - } - return isNonPersistence - } - - private suspend fun parseResponse(message: RequestMessage): HttpClientResponse { - val remainingData = parser.parseAll(tcpConnection) - val response = handler.complete() - return when { - message.expectUpgradeHttp2 -> upgradeHttp2Connection(response, message, remainingData) - message.expectUpgradeWebSocket -> upgradeWebSocketConnection(response, message, remainingData) - else -> response - } - } - - private fun upgradeWebSocketConnection( - response: HttpClientResponse, - message: RequestMessage, - remainingData: ByteBuffer? - ): HttpClientResponse { - if (response.status != HttpStatus.SWITCHING_PROTOCOLS_101) { - val e = - UpgradeWebSocketConnectionException("The upgrade response status is not 101. status: ${response.status}") - message.webSocketClientConnection?.completeExceptionally(e) - return response - } - - if (!response.httpFields.contains(HttpHeader.CONNECTION, "Upgrade")) { - val e = - UpgradeWebSocketConnectionException("The upgrade response does not contain the Connection Upgrade field.") - message.webSocketClientConnection?.completeExceptionally(e) - return response - } - - if (!response.httpFields.contains(HttpHeader.UPGRADE, "websocket")) { - val e = - UpgradeWebSocketConnectionException("The upgrade response does not contain the UPGRADE websocket field.") - message.webSocketClientConnection?.completeExceptionally(e) - return response - } - - if (!response.httpFields.contains(HttpHeader.SEC_WEBSOCKET_ACCEPT)) { - val e = - UpgradeWebSocketConnectionException("The upgrade response does not contain the Sec-WebSocket-Accept.") - message.webSocketClientConnection?.completeExceptionally(e) - return response - } - - val clientKey = message.httpClientRequest.httpFields[HttpHeader.SEC_WEBSOCKET_KEY] - val serverKey = AcceptHash.hashKey(clientKey) - if (response.httpFields[HttpHeader.SEC_WEBSOCKET_ACCEPT] != serverKey) { - val e = UpgradeWebSocketConnectionException("The upgrade response SEC_WEBSOCKET_ACCEPT is illegal.") - message.webSocketClientConnection?.completeExceptionally(e) - return response - } - - log.info { "Upgrade websocket. Client received 101 Switching Protocols. id: $id" } - val webSocketClientRequest = message.webSocketClientRequest - requireNotNull(webSocketClientRequest) - val serverExtensions = response.httpFields.getValuesList(HttpHeader.SEC_WEBSOCKET_EXTENSIONS) - val serverSubProtocols = response.httpFields.getValuesList(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL) - val webSocketConnection = AsyncWebSocketConnection( - tcpConnection, - webSocketClientRequest.policy, - webSocketClientRequest.url, - serverExtensions ?: listOf(), - AsyncWebSocketConnection.defaultExtensionFactory, - serverSubProtocols ?: listOf(), - remainingData = remainingData - ) - webSocketConnection.setWebSocketMessageHandler(webSocketClientRequest.handler) - webSocketConnection.begin() - upgradeWebSocketSuccess = true - message.webSocketClientConnection?.complete(webSocketConnection) - return response - } - - private suspend fun upgradeHttp2Connection( - response: HttpClientResponse, - message: RequestMessage, - remainingData: ByteBuffer? - ): HttpClientResponse { - return if (isUpgradeSuccess(response)) { - log.info { "Upgrade HTTP2. Client received 101 Switching Protocols. id: $id" } - - val http2Connection = Http2ClientConnection(config, tcpConnection, priorKnowledge = false) - val responseFuture = - http2Connection.upgradeHttp2(message.httpClientRequest, remainingData) - http2ClientConnection = http2Connection - httpVersion = HttpVersion.HTTP_2 - responseFuture.await().also { log.info { "Client upgrades HTTP2 success. id: $id" } } - } else response.also { log.info { "Client upgrades HTTP2 failure. id: $id" } } - } - - - private fun isUpgradeToHttp2Success(): Boolean { - return httpVersion == HttpVersion.HTTP_2 && http2ClientConnection != null - } - - private suspend fun waitServerResponse100Continue(): Boolean { - val accepted = try { - withTimeout(Duration.ofSeconds(config.waitResponse100ContinueTimeout).toMillis()) { - parser.parseAll(tcpConnection) - handler.isServerAcceptedContent() - } - } catch (e: TimeoutCancellationException) { - log.info { "Wait server response 100 continue timeout. The client will send data." } - true - } catch (e: Exception) { - log.error { "Wait server response 100 continue failure. The client will not send data. id: $id info: ${e.javaClass.name} ${e.message}" } - false - } - if (accepted) { - parser.reset() - } - return accepted - } - - private suspend fun generateRequestAndWaitServerAccept(requestMessage: RequestMessage) { - var accepted = false - generateRequestLoop@ while (true) { - when (generator.state) { - START -> generateHeader(requestMessage) - COMMITTED -> { - if (accepted) { - generateContent(requestMessage) - } else { - if (waitServerResponse100Continue()) { - accepted = true - generateContent(requestMessage) - log.debug("HTTP1 client receives 100 continue and generates content complete. id: $id") - } else { - requestMessage.contentProvider?.closeAsync()?.await() - break@generateRequestLoop - } - } - } - COMPLETING -> completeContent() - END -> { - completeRequest(requestMessage) - break@generateRequestLoop - } - else -> throw Http1GeneratingResultException("The HTTP client generator state error. ${generator.state}") - } - } - } - - private suspend fun generateRequest(requestMessage: RequestMessage) { - generateRequestLoop@ while (true) { - when (generator.state) { - START -> generateHeader(requestMessage) - COMMITTED -> generateContent(requestMessage) - COMPLETING -> completeContent() - END -> { - completeRequest(requestMessage) - break@generateRequestLoop - } - else -> throw Http1GeneratingResultException("The HTTP client generator state error. ${generator.state}") - } - } - } - - private suspend fun generateHeader(requestMessage: RequestMessage) { - val hasContent = (requestMessage.contentProvider != null) - generator.generateRequest(requestMessage.request, headerBuffer, null, null, !hasContent) - .assert(FLUSH) - flushHeaderBuffer() - } - - private suspend fun generateContent(requestMessage: RequestMessage) { - requireNotNull(requestMessage.contentProvider) - - val pos = contentBuffer.flipToFill() - val len = requestMessage.contentProvider.read(contentBuffer).await() - contentBuffer.flipToFlush(pos) - - val last = (len == -1) - - if (generator.isChunking) { - when (val result = generator.generateRequest(null, null, chunkBuffer, contentBuffer, last)) { - FLUSH -> flushChunkedContentBuffer() - CONTINUE -> { // ignore the generator result continue. - } - SHUTDOWN_OUT, DONE -> completeContent() - else -> throw Http1GeneratingResultException("The HTTP client generator result error. $result") - } - } else { - when (val result = generator.generateRequest(null, null, null, contentBuffer, last)) { - FLUSH -> flushContentBuffer() - CONTINUE -> { // ignore the generator result continue. - } - SHUTDOWN_OUT, DONE -> completeContent() - else -> throw Http1GeneratingResultException("The HTTP client generator result error. $result") - } - } - } - - private suspend fun completeContent() { - if (generator.isChunking) { - when (val result = generator.generateRequest(null, null, chunkBuffer, null, true)) { - FLUSH -> flushChunkBuffer() - CONTINUE, SHUTDOWN_OUT, DONE -> { // ignore the generator result done. - } - else -> throw Http1GeneratingResultException("The HTTP client generator result error. $result") - } - } else { - generator.generateRequest(null, null, null, null, true) - .assert(setOf(DONE, SHUTDOWN_OUT, CONTINUE)) - } - } - - private suspend fun completeRequest(requestMessage: RequestMessage) { - tcpConnection.flush().await() - requestMessage.contentProvider?.closeAsync()?.await() - } - - private suspend fun flushHeaderBuffer() { - if (headerBuffer.hasRemaining()) { - val size = tcpConnection.write(headerBuffer).await() - log.debug { "flush header bytes: $size" } - } - BufferUtils.clear(headerBuffer) - } - - private suspend fun flushContentBuffer() { - if (contentBuffer.hasRemaining()) { - val size = tcpConnection.write(contentBuffer).await() - log.debug { "flush content bytes: $size" } - } - BufferUtils.clear(contentBuffer) - } - - private suspend fun flushChunkedContentBuffer() { - val bufArray = arrayOf(chunkBuffer, contentBuffer) - val remaining = bufArray.sumOf { it.remaining().toLong() } - if (remaining > 0) { - val size = tcpConnection.write(bufArray, 0, bufArray.size).await() - log.debug { "flush chunked content bytes: $size" } - } - bufArray.forEach(BufferUtils::clear) - } - - private suspend fun flushChunkBuffer() { - if (chunkBuffer.hasRemaining()) { - val size = tcpConnection.write(chunkBuffer).await() - log.debug { "flush chunked bytes: $size" } - } - BufferUtils.clear(chunkBuffer) - } - - override fun getHttpVersion(): HttpVersion = httpVersion - - override fun isSecureConnection(): Boolean = tcpConnection.isSecureConnection - - override fun getTcpConnection(): TcpConnection = tcpConnection - - override fun send(request: HttpClientRequest): CompletableFuture { - return when (httpVersion) { - HttpVersion.HTTP_2 -> sendRequestViaHttp2(request) - HttpVersion.HTTP_1_1 -> sendRequestViaHttp1(request) - else -> throw NotSupportHttpVersionException("HTTP version not support. $httpVersion") - } - } - - private fun sendRequestViaHttp1(request: HttpClientRequest): CompletableFuture { - log.debug { "Send request via HTTP1 protocol. id: $id" } - if (config.isAutoGeneratedClientHttp1Headers) { - prepareHttp1Headers(request) { this.remoteAddress.hostName } - } - val future = CompletableFuture() - requestChannel.trySend(RequestMessage(request, future)) - return future - } - - private fun sendRequestViaHttp2(request: HttpClientRequest): CompletableFuture { - val http2Connection = http2ClientConnection - requireNotNull(http2Connection) - log.debug { "Send request via HTTP2 protocol. id: $id" } - return http2Connection.send(request) - } - - fun upgradeWebSocket(webSocketClientRequest: WebSocketClientRequest): CompletableFuture { - val request = AsyncHttpClientRequest() - request.method = HttpMethod.GET.value - request.uri = HttpURI(webSocketClientRequest.url) - request.httpFields = HttpFields() - request.httpFields.put(HttpHeader.HOST, request.uri.host) - request.httpFields.put(HttpHeader.CONNECTION, "Upgrade") - request.httpFields.put(HttpHeader.UPGRADE, "websocket") - request.httpFields.put(HttpHeader.SEC_WEBSOCKET_VERSION, "13") - request.httpFields.put(HttpHeader.SEC_WEBSOCKET_KEY, genRandomWebSocketKey()) - if (!webSocketClientRequest.extensions.isNullOrEmpty()) { - request.httpFields.put( - HttpHeader.SEC_WEBSOCKET_EXTENSIONS, - webSocketClientRequest.extensions.joinToString(", ") - ) - } - if (!webSocketClientRequest.subProtocols.isNullOrEmpty()) { - request.httpFields.put( - HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL, - webSocketClientRequest.subProtocols.joinToString(", ") - ) - } - - val websocketFuture = CompletableFuture() - val responseFuture = CompletableFuture() - val message = RequestMessage( - httpClientRequest = request, - response = responseFuture, - webSocketClientConnection = websocketFuture, - webSocketClientRequest = webSocketClientRequest - ) - requestChannel.trySend(message) - return websocketFuture - } - - fun dispose() { - requestChannel.trySend(Stop) - } - - private fun genRandomWebSocketKey(): String { - val bytes = ByteArray(16) - ThreadLocalRandom.current().nextBytes(bytes) - return String(Base64Utils.encode(bytes)) - } - - sealed interface Http1ClientConnectionMessage - - private data class RequestMessage( - val httpClientRequest: HttpClientRequest, - val response: CompletableFuture, - val request: MetaData.Request = toMetaDataRequest(httpClientRequest), - val contentProvider: HttpClientContentProvider? = httpClientRequest.contentProvider, - val expectServerAcceptsContent: Boolean = httpClientRequest.httpFields.expectServerAcceptsContent(), - val expectUpgradeHttp2: Boolean = expectUpgradeHttp2(httpClientRequest), - val expectUpgradeWebSocket: Boolean = expectUpgradeWebsocket(httpClientRequest), - val isHttpTunnel: Boolean = httpClientRequest.method.equals(HttpMethod.CONNECT.value), - val webSocketClientConnection: CompletableFuture? = null, - val webSocketClientRequest: WebSocketClientRequest? = null - ) : Http1ClientConnectionMessage - - private object Stop : Http1ClientConnectionMessage -} - -fun prepareHttp1Headers(request: HttpClientRequest, defaultHost: () -> String) { - if (request.httpFields.getValuesList(HttpHeader.HOST.value).isEmpty()) { - val host = if (request.uri.host.isNullOrBlank()) defaultHost.invoke() else request.uri.host - request.httpFields.put(HttpHeader.HOST, host) - } - - val connectionValues = request.httpFields.getCSV(HttpHeader.CONNECTION, false) - if (connectionValues.isNotEmpty()) { - if (!connectionValues.contains(HttpHeaderValue.KEEP_ALIVE.value)) { - val newValues = mutableListOf() - newValues.addAll(connectionValues) - newValues.add(HttpHeaderValue.KEEP_ALIVE.value) - request.httpFields.remove(HttpHeader.CONNECTION) - request.httpFields.addCSV(HttpHeader.CONNECTION, *newValues.toTypedArray()) - } - } else { - request.httpFields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.value) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http1ClientResponseHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http1ClientResponseHandler.kt deleted file mode 100644 index 8fc580f6e..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http1ClientResponseHandler.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.HttpClientRequest -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.common.v1.decoder.HttpParser -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import java.nio.ByteBuffer -import java.util.function.Supplier - -class Http1ClientResponseHandler : HttpParser.ResponseHandler { - - private val response: MetaData.Response = MetaData.Response(HttpVersion.HTTP_1_1, 0, HttpFields()) - private var expectServerAcceptsContent = false - private var httpClientResponse: AsyncHttpClientResponse? = null - private val trailers = HttpFields() - private val responseChannel: Channel = Channel(Channel.UNLIMITED) - private var isServerAcceptedContent: Boolean = false - private var isHttpTunnel: Boolean = false - private var httpRequest: HttpClientRequest? = null - - fun init(httpRequest: HttpClientRequest, expectServerAcceptsContent: Boolean, isHttpTunnel: Boolean) { - this.httpRequest = httpRequest - this.expectServerAcceptsContent = expectServerAcceptsContent - this.isHttpTunnel = isHttpTunnel - } - - override fun getHeaderCacheSize(): Int { - return 4096 - } - - override fun startResponse(version: HttpVersion, status: Int, reason: String): Boolean { - fun updateResponseLine() { - response.httpVersion = version - response.status = status - response.reason = reason - } - - if (expectServerAcceptsContent) { - if (status == HttpStatus.CONTINUE_100) { - isServerAcceptedContent = true - } else { - isServerAcceptedContent = false - updateResponseLine() - } - expectServerAcceptsContent = false - } else updateResponseLine() - return status == HttpStatus.CONTINUE_100 - } - - override fun parsedHeader(field: HttpField) { - response.fields.add(field) - } - - override fun headerComplete(): Boolean { - val httpClientResponse = AsyncHttpClientResponse(MetaData.Response(response), httpRequest?.contentHandler) - this.httpClientResponse = httpClientResponse - return if (isHttpTunnel) { - responseChannel.trySend(httpClientResponse) - true - } else { - val request = httpRequest - requireNotNull(request) - request.headerComplete.accept(request, httpClientResponse) - false - } - } - - override fun content(buffer: ByteBuffer): Boolean { - httpRequest?.contentHandler?.accept(buffer, httpClientResponse) - return false - } - - override fun contentComplete(): Boolean { - return false - } - - override fun parsedTrailer(field: HttpField) { - trailers.add(field) - } - - override fun messageComplete(): Boolean { - val clientResponse = httpClientResponse - requireNotNull(clientResponse) - val trailer = HttpFields(trailers) - clientResponse.response.trailerSupplier = Supplier { trailer } - responseChannel.trySend(clientResponse) - return true - } - - override fun badMessage(failure: BadMessageException) { - throw failure - } - - override fun earlyEOF() { - throw BadMessageException(HttpStatus.BAD_REQUEST_400) - } - - suspend fun complete(): HttpClientResponse { - val response = responseChannel.receive() - httpRequest?.contentHandler?.closeAsync()?.await() - return response - } - - fun isServerAcceptedContent(): Boolean = isServerAcceptedContent - - fun reset() { - response.recycle() - httpRequest = null - trailers.clear() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http2ClientConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http2ClientConnection.kt deleted file mode 100644 index 588bd1755..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http2ClientConnection.kt +++ /dev/null @@ -1,228 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.io.useAwait -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.* -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.HttpConfig.DEFAULT_WINDOW_SIZE -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v2.decoder.Parser -import com.fireflysource.net.http.common.v2.frame.* -import com.fireflysource.net.http.common.v2.stream.* -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.aio.AdaptiveBufferSize -import kotlinx.coroutines.async -import kotlinx.coroutines.future.asCompletableFuture -import kotlinx.coroutines.future.await -import java.nio.ByteBuffer -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.function.UnaryOperator - -class Http2ClientConnection( - config: HttpConfig, - tcpConnection: TcpConnection, - flowControl: FlowControl = BufferedFlowControlStrategy(), - listener: Http2Connection.Listener = defaultHttp2ConnectionListener, - priorKnowledge: Boolean = true -) : AsyncHttp2Connection(1, config, tcpConnection, flowControl, listener), AbstractHttpClientConnection { - - companion object { - private val log = SystemLogger.create(Http2ClientConnection::class.java) - } - - private val parser: Parser = Parser(this, config.maxDynamicTableSize, config.maxHeaderSize) - private val adaptiveBufferSize = AdaptiveBufferSize() - - init { - if (priorKnowledge) { - sendConnectionPreface() - parser.init(UnaryOperator.identity()) - launchParserJob(parser) - } - } - - fun upgradeHttp2(request: HttpClientRequest, frameBytes: ByteBuffer?): CompletableFuture { - val streamId = getNextStreamId() - val future = CompletableFuture() - val streamListener = Http2ClientStreamListener(request, future) - createLocalStream(streamId, streamListener) - sendConnectionPreface() - parser.init(UnaryOperator.identity()) - if (frameBytes != null) { - log.debug { "Upgrade HTTP2 and received frame data. id: $id, remaining: ${frameBytes.remaining()}" } - while (frameBytes.hasRemaining()) { - parser.parse(frameBytes) - } - } - launchParserJob(parser) - return future - } - - private fun sendConnectionPreface() { - val settings = notifyPreface() - - val maxFrameLength = settings[SettingsFrame.MAX_FRAME_SIZE] - if (maxFrameLength != null) { - parser.maxFrameLength = maxFrameLength - } - - val prefaceFrame = PrefaceFrame() - val settingsFrame = SettingsFrame(settings, false) - val windowDelta = initialSessionRecvWindow - DEFAULT_WINDOW_SIZE - if (windowDelta > 0) { - val windowUpdateFrame = WindowUpdateFrame(0, windowDelta) - updateRecvWindow(windowDelta) - sendControlFrame(null, prefaceFrame, settingsFrame, windowUpdateFrame) - .thenAccept { log.info { "send connection preface success. id: $id, settings: $settingsFrame" } } - .exceptionallyAccept { log.error(it) { "send connection preface exception. id: $id" } } - } else { - sendControlFrame(null, prefaceFrame, settingsFrame) - .thenAccept { log.info { "send connection preface success. id: $id, settings: $settingsFrame" } } - .exceptionallyAccept { log.error(it) { "send connection preface exception. id: $id" } } - } - } - - override fun onHeaders(frame: HeadersFrame) { - log.debug { "Received $frame" } - - // HEADERS can be received for normal and pushed responses. - val streamId = frame.streamId - val stream = getStream(streamId) - if (stream != null && stream is AsyncHttp2Stream) { - val metaData = frame.metaData - if (metaData.isRequest) { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_response") - } else { - stream.process(frame, discard()) - notifyHeaders(stream, frame) - } - } else { - log.debug { "Stream: $streamId not found" } - if (isClientStream(streamId)) { - // The normal stream. Headers or trailers arriving after the stream has been reset are ignored. - if (!isLocalStreamClosed(streamId)) { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame") - } - } else { - // The pushed stream. Headers or trailers arriving after the stream has been reset are ignored. - if (!isRemoteStreamClosed(streamId)) { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_headers_frame") - } - } - } - } - - - // promise frame - override fun onPushPromise(frame: PushPromiseFrame) { - log.debug { "Received $frame" } - - val stream = getStream(frame.streamId) - if (stream == null) { - log.debug { "Ignoring $frame, stream: ${frame.streamId} not found" } - } else { - val pushStream = createRemoteStream(frame.promisedStreamId) - if (pushStream != null && pushStream is AsyncHttp2Stream) { - pushStream.process(frame, discard()) - pushStream.listener = notifyPush(stream, pushStream, frame) - } - } - } - - private fun notifyPush(stream: Stream, pushStream: Stream, frame: PushPromiseFrame): Stream.Listener { - return try { - val listener = (stream as AsyncHttp2Stream).listener - listener.onPush(pushStream, frame) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - AsyncHttp2Stream.defaultStreamListener - } - } - - override fun onResetForUnknownStream(frame: ResetFrame) { - val streamId = frame.streamId - val closed = if (isClientStream(streamId)) isLocalStreamClosed(streamId) else isRemoteStreamClosed(streamId) - if (closed) { - notifyReset(this, frame) - } else { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame") - } - } - - override fun send(request: HttpClientRequest): CompletableFuture { - val metaDataRequest: MetaData.Request = toMetaDataRequest(request) - val contentProvider: HttpClientContentProvider? = request.contentProvider - val lastHeaders = contentProvider == null && metaDataRequest.trailerSupplier == null - val headersFrame = HeadersFrame(metaDataRequest, null, lastHeaders) - - val future = CompletableFuture() - val streamListener = Http2ClientStreamListener(request, future) - val serverAccepted = streamListener.serverAccepted - newStream(headersFrame, streamListener) - .thenCompose { newStream -> serverAccepted.thenApply { Pair(newStream, it) } } - .thenCompose { generateContent(contentProvider, metaDataRequest, it.first, it.second) } - .thenAccept { generateTrailer(metaDataRequest, it.first, it.second) } - .exceptionallyAccept { - log.error(it) { "The HTTP2 client connection creates local stream failure. id: $id " } - future.completeExceptionally(it) - } - return future - } - - private fun generateContent( - contentProvider: HttpClientContentProvider?, - metaDataRequest: MetaData.Request, - newStream: Stream, - serverAccept: Boolean - ) = tcpConnection.coroutineScope.async { - if (contentProvider != null && serverAccept) { - contentProvider.useAwait { - val byteBuffers = LinkedList() - readLoop@ while (true) { - val contentBuffer = BufferUtils.allocate(adaptiveBufferSize.getBufferSize()) - val pos = contentBuffer.flipToFill() - val length = contentProvider.read(contentBuffer).await() - contentBuffer.flipToFlush(pos) - adaptiveBufferSize.update(length) - - when { - length > 0 -> byteBuffers.offer(contentBuffer) - length < 0 -> break@readLoop - } - - if (byteBuffers.size > 1) { - val dataFrame = DataFrame(newStream.id, byteBuffers.poll(), false) - newStream.data(dataFrame) - } - } - - val last = metaDataRequest.trailerSupplier == null - if (byteBuffers.isNotEmpty()) { - val dataFrame = DataFrame(newStream.id, byteBuffers.poll(), last) - newStream.data(dataFrame) - } else { - val empty = ByteBuffer.allocate(0) - val dataFrame = DataFrame(newStream.id, empty, last) - newStream.data(dataFrame) - } - } - } - Pair(newStream, serverAccept) - }.asCompletableFuture() - - private fun generateTrailer(metaDataRequest: MetaData.Request, newStream: Stream, serverAccept: Boolean) { - val trailerSupplier = metaDataRequest.trailerSupplier - if (trailerSupplier != null && serverAccept) { - val trailerMetaData = MetaData.Request(trailerSupplier.get()) - trailerMetaData.isOnlyTrailer = true - val headersFrameTrailer = HeadersFrame(newStream.id, trailerMetaData, null, true) - newStream.headers(headersFrameTrailer, discard()) - } - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http2ClientStreamListener.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http2ClientStreamListener.kt deleted file mode 100644 index 11e7c3841..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/Http2ClientStreamListener.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.HttpClientRequest -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.common.v2.frame.DataFrame -import com.fireflysource.net.http.common.v2.frame.ErrorCode -import com.fireflysource.net.http.common.v2.frame.HeadersFrame -import com.fireflysource.net.http.common.v2.frame.ResetFrame -import com.fireflysource.net.http.common.v2.stream.Stream -import java.util.concurrent.CompletableFuture -import java.util.function.Consumer - -class Http2ClientStreamListener( - private val request: HttpClientRequest, - private val future: CompletableFuture -) : Stream.Listener.Adapter() { - - companion object { - private val log = SystemLogger.create(Http2ClientStreamListener::class.java) - private val defaultServerAccepted: CompletableFuture by lazy { - val future = CompletableFuture() - future.complete(true) - future - } - } - - private val expectServerAcceptsContent: Boolean = request.httpFields.expectServerAcceptsContent() - private val response = - AsyncHttpClientResponse(MetaData.Response(HttpVersion.HTTP_2, 0, HttpFields()), request.contentHandler) - private val metaDataResponse = response.response - val serverAccepted = if (expectServerAcceptsContent) CompletableFuture() else defaultServerAccepted - private val trailer = HttpFields() - private var theFirstHeader = true - private var receivedData = false - - private fun onMessageComplete() { - val contentHandler = request.contentHandler - if (contentHandler != null) { - contentHandler.closeAsync() - .thenAccept { future.complete(response) } - .exceptionallyAccept { future.completeExceptionally(it) } - } else future.complete(response) - } - - private fun handleResponseHeaders(stream: Stream, frame: HeadersFrame) { - when (val metaData = frame.metaData) { - is MetaData.Response -> { - metaDataResponse.status = metaData.status - metaDataResponse.reason = metaData.reason - metaDataResponse.fields.addAll(metaData.fields) - request.headerComplete.accept(request, response) - } - is MetaData.Request -> handleError(stream, "The HTTP2 client must receive response metadata.") - else -> { - if (receivedData) { - if (metaDataResponse.trailerSupplier == null) { - metaDataResponse.setTrailerSupplier { trailer } - } - trailer.addAll(metaData.fields) - } else metaDataResponse.fields.addAll(metaData.fields) - } - } - if (frame.isEndStream) onMessageComplete() - } - - private fun handleError(stream: Stream, message: String) { - val resetFrame = ResetFrame(stream.id, ErrorCode.INTERNAL_ERROR.code) - stream.reset(resetFrame) { - val exception = IllegalStateException(message) - future.completeExceptionally(exception) - } - } - - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - if (theFirstHeader) { - theFirstHeader = false - val metaData = frame.metaData - if (metaData is MetaData.Response) { - if (expectServerAcceptsContent) { - if (metaData.status == HttpStatus.CONTINUE_100) { - log.debug { "Client received 100 continue response. stream: $stream" } - if (frame.isEndStream) { - serverAccepted.complete(false) - handleError(stream, "The remote stream closed. id: ${stream.id}") - } else serverAccepted.complete(true) - } else { - serverAccepted.complete(false) - handleResponseHeaders(stream, frame) - } - } else { - serverAccepted.complete(true) - handleResponseHeaders(stream, frame) - } - } else { - serverAccepted.complete(false) - handleError(stream, "The HTTP2 client must receive response metadata.") - } - } else { - handleResponseHeaders(stream, frame) - } - } - - override fun onData(stream: Stream, frame: DataFrame, result: Consumer>) { - try { - receivedData = true - request.contentHandler?.accept(frame.data, response) - if (frame.isEndStream) onMessageComplete() - } finally { - result.accept(Result.SUCCESS) - } - } - - override fun onReset(stream: Stream, frame: ResetFrame) { - if (!future.isDone) { - val error = ErrorCode.toString(frame.error, "http2_request_error") - val exception = IllegalStateException(error) - future.completeExceptionally(exception) - } - } - - override fun onIdleTimeout(stream: Stream, x: Throwable): Boolean { - if (!future.isDone) { - val exception = IllegalStateException("http2_stream_timeout") - future.completeExceptionally(exception) - } - return true - } - - override fun onTerminal(stream: Stream) { - if (!future.isDone) { - val exception = IllegalStateException("The stream is closed") - future.completeExceptionally(exception) - } - } - - override fun onFailure(stream: Stream, error: Int, reason: String, result: Consumer>) { - if (!future.isDone) { - val exception = IllegalStateException("The stream is failure") - future.completeExceptionally(exception) - } - result.accept(Result.SUCCESS) - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/HttpClientConnectionFactory.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/HttpClientConnectionFactory.kt deleted file mode 100644 index 0571d52b4..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/HttpClientConnectionFactory.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.common.codec.base64.Base64 -import com.fireflysource.common.string.StringUtils -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.HttpClientConnection -import com.fireflysource.net.http.client.impl.exception.HttpTunnelHandshakeException -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.tcp.TcpClientConnectionFactory -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.aio.ApplicationProtocol -import com.fireflysource.net.tcp.aio.createSecureTcpConnection -import com.fireflysource.net.tcp.aio.defaultSupportedProtocols -import com.fireflysource.net.tcp.secure.DefaultSecureEngineFactorySelector -import kotlinx.coroutines.future.await -import java.net.InetSocketAddress -import java.util.concurrent.CompletableFuture - -class HttpClientConnectionFactory( - private val httpConfig: HttpConfig, - private val connectionFactory: TcpClientConnectionFactory -) { - - companion object { - private val log = SystemLogger.create(HttpClientConnectionFactory::class.java) - } - - suspend fun createHttpClientConnection( - address: Address, - supportedProtocols: List = defaultSupportedProtocols - ): HttpClientConnection { - return if (isProxyEnabled()) { - createProxyHttpClientConnection(address, supportedProtocols.ifEmpty { defaultSupportedProtocols }) - } else { - createDirectHttpClientConnection(address, supportedProtocols.ifEmpty { defaultSupportedProtocols }) - } - } - - private fun isProxyEnabled(): Boolean { - return httpConfig.proxyConfig != null && StringUtils.hasText(httpConfig.proxyConfig.host) && httpConfig.proxyConfig.port > 0 - } - - private suspend fun createDirectHttpClientConnection( - address: Address, - supportedProtocols: List = defaultSupportedProtocols - ): HttpClientConnection { - return connectionFactory.connect(address.socketAddress, address.secure, supportedProtocols) - .thenCompose { createHttpClientConnection(it) }.await() - } - - private suspend fun createProxyHttpClientConnection( - address: Address, - supportedProtocols: List = defaultSupportedProtocols - ): HttpClientConnection { - val proxyAddress = getProxyAddress() - val proxyTcpConnection = connectionFactory.connect(proxyAddress, false).await() - val httpConnection = createHttp1ClientConnection(proxyTcpConnection) - return if (address.secure) { - try { - val success = - beginHttpTunnelHandshake(address.socketAddress.hostName, address.socketAddress.port, httpConnection) - if (success) { - val secureEngineFactory = if (connectionFactory.secureEngineFactory == null) - DefaultSecureEngineFactorySelector.createSecureEngineFactory(true) - else connectionFactory.secureEngineFactory - - val secureTcpConnection = createSecureTcpConnection( - proxyTcpConnection, - "", - 0, - true, - supportedProtocols.ifEmpty { defaultSupportedProtocols }, - secureEngineFactory - ) - createHttpClientConnection(secureTcpConnection).await() - } else { - throw HttpTunnelHandshakeException("HTTP tunnel handshake failure.") - } - } finally { - httpConnection.dispose() - } - } else httpConnection - } - - private fun getProxyAddress(): InetSocketAddress { - val proxyConfig = httpConfig.proxyConfig - requireNotNull(proxyConfig) - Assert.hasText(proxyConfig.host, "The proxy host must be not null") - Assert.isTrue(proxyConfig.port > 0, "The proxy port must be greater than 0") - - return InetSocketAddress(proxyConfig.host, proxyConfig.port) - } - - private suspend fun beginHttpTunnelHandshake( - host: String, - port: Int, - httpConnection: HttpClientConnection - ): Boolean { - val request = AsyncHttpClientRequest() - request.method = HttpMethod.CONNECT.value - request.uri = HttpURI("$host:$port") - request.httpFields.put(HttpHeader.HOST, host) - request.httpFields.put(HttpHeader.PROXY_CONNECTION, "keep-alive") - val auth = httpConfig.proxyConfig.proxyAuthentication - if (auth != null && auth.username != null && auth.password != null) { - val authStr = Base64.encodeBase64String("${auth.username}:${auth.password}".toByteArray()) - request.httpFields.put(HttpHeader.PROXY_AUTHORIZATION, "Basic $authStr") - } - val response = httpConnection.send(request).await() - log.info("HTTP tunnel handshake result: ${response.status}, host: $host, port: $port") - return response.status == HttpStatus.OK_200 - } - - private fun createHttpClientConnection(connection: TcpConnection): CompletableFuture { - return if (connection.isSecureConnection) { - connection.beginHandshake().thenApply { applicationProtocol -> - when (applicationProtocol) { - ApplicationProtocol.HTTP2.value -> createHttp2ClientConnection(connection) - else -> createHttp1ClientConnection(connection) - } - } - } else CompletableFuture.completedFuture(createHttp1ClientConnection(connection)) - } - - private fun createHttp1ClientConnection(connection: TcpConnection) = Http1ClientConnection(httpConfig, connection) - - private fun createHttp2ClientConnection(connection: TcpConnection) = Http2ClientConnection(httpConfig, connection) -} - -data class Address(val socketAddress: InetSocketAddress, val secure: Boolean) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/HttpProtocolNegotiator.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/HttpProtocolNegotiator.kt deleted file mode 100644 index b5906474c..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/HttpProtocolNegotiator.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.codec.base64.Base64Utils -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.client.HttpClientRequest -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpHeaderValue -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.v2.encoder.SettingsGenerator.generateSettingsBody -import com.fireflysource.net.http.common.v2.frame.SettingsFrame -import com.fireflysource.net.http.server.HttpServerRequest - -object HttpProtocolNegotiator { - val defaultSettingsFrameBytes: ByteArray = - BufferUtils.toArray(generateSettingsBody(SettingsFrame.DEFAULT_SETTINGS_FRAME.settings)) - - fun addHttp2UpgradeHeader(request: HttpClientRequest) { - // detect the protocol version using Connection and Upgrade HTTP headers - val oldValues: List = request.httpFields.getCSV(HttpHeader.CONNECTION, false) - if (oldValues.isNotEmpty()) { - val newValues = mutableListOf() - newValues.addAll(oldValues) - newValues.add("Upgrade") - newValues.add("HTTP2-Settings") - request.httpFields.remove(HttpHeader.CONNECTION) - request.httpFields.addCSV(HttpHeader.CONNECTION, *newValues.toTypedArray()) - } else { - request.httpFields.addCSV(HttpHeader.CONNECTION, "Upgrade", "HTTP2-Settings") - } - request.httpFields.put(HttpHeader.UPGRADE, "h2c") - - // generate http2 settings base64 - val bytes = if (request.http2Settings.isNullOrEmpty()) { - defaultSettingsFrameBytes - } else { - BufferUtils.toArray(generateSettingsBody(request.http2Settings)) - } - - val base64 = Base64Utils.encodeToUrlSafeString(bytes) - request.httpFields.put(HttpHeader.HTTP2_SETTINGS, base64) - } - - fun removeHttp2UpgradeHeader(request: HttpClientRequest) { - request.httpFields.remove(HttpHeader.HTTP2_SETTINGS) - request.httpFields.remove(HttpHeader.UPGRADE) - request.httpFields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.value) - } - - fun isUpgradeSuccess(response: HttpClientResponse): Boolean { - return response.status == HttpStatus.SWITCHING_PROTOCOLS_101 - } - - fun expectUpgradeHttp2(request: HttpClientRequest): Boolean { - return request.httpFields.contains(HttpHeader.CONNECTION, "Upgrade") - && request.httpFields.contains(HttpHeader.UPGRADE, "h2c") - } - - fun expectUpgradeHttp2(request: HttpServerRequest): Boolean { - return request.httpFields.contains(HttpHeader.CONNECTION, "Upgrade") - && request.httpFields.contains(HttpHeader.UPGRADE, "h2c") - && request.httpFields.contains(HttpHeader.HTTP2_SETTINGS) - } - - fun expectUpgradeWebsocket(request: HttpClientRequest): Boolean { - return request.method == HttpMethod.GET.value - && request.httpFields.contains(HttpHeader.UPGRADE, "websocket") - && request.httpFields.contains(HttpHeader.SEC_WEBSOCKET_VERSION, "13") - && request.httpFields.contains(HttpHeader.SEC_WEBSOCKET_KEY) - } - - fun expectUpgradeWebsocket(request: HttpServerRequest): Boolean { - return request.method == HttpMethod.GET.value - && request.httpFields.contains(HttpHeader.UPGRADE, "websocket") - && request.httpFields.contains(HttpHeader.SEC_WEBSOCKET_VERSION, "13") - && request.httpFields.contains(HttpHeader.SEC_WEBSOCKET_KEY) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/ByteBufferContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/ByteBufferContentHandler.kt deleted file mode 100644 index ab463f868..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/ByteBufferContentHandler.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.handler - -import com.fireflysource.net.http.client.HttpClientContentHandler -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.content.handler.AbstractByteBufferContentHandler - -open class ByteBufferContentHandler(maxRequestBodySize: Long = 200 * 1024 * 1024) : - AbstractByteBufferContentHandler(maxRequestBodySize), HttpClientContentHandler \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/FileContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/FileContentHandler.kt deleted file mode 100644 index f029d112d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/FileContentHandler.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.handler - -import com.fireflysource.net.http.client.HttpClientContentHandler -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.content.handler.AbstractFileContentHandler -import java.nio.file.OpenOption -import java.nio.file.Path - -class FileContentHandler(path: Path, vararg options: OpenOption) : - AbstractFileContentHandler(path, *options), HttpClientContentHandler \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/StringContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/StringContentHandler.kt deleted file mode 100644 index 4383e0458..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/handler/StringContentHandler.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.handler - -class StringContentHandler(maxRequestBodySize: Long = 200 * 1024 * 1024) : ByteBufferContentHandler(maxRequestBodySize) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/ByteBufferContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/ByteBufferContentProvider.kt deleted file mode 100644 index ea09f59b1..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/ByteBufferContentProvider.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.net.http.client.HttpClientContentProvider -import com.fireflysource.net.http.common.content.provider.AbstractByteBufferContentProvider -import java.nio.ByteBuffer - -open class ByteBufferContentProvider(content: ByteBuffer) : - AbstractByteBufferContentProvider(content), HttpClientContentProvider \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/FileContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/FileContentProvider.kt deleted file mode 100644 index c9c15252b..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/FileContentProvider.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.net.http.client.HttpClientContentProvider -import com.fireflysource.net.http.common.content.provider.AbstractFileContentProvider -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import java.nio.file.Files -import java.nio.file.OpenOption -import java.nio.file.Path - -class FileContentProvider( - path: Path, - options: Set, - position: Long, - length: Long, - scope: CoroutineScope = CoroutineScope(CoroutineName("Firefly-file-content-provider")) -) : AbstractFileContentProvider(path, options, position, length, scope), HttpClientContentProvider { - - constructor(path: Path, vararg options: OpenOption) : this(path, options.toSet(), 0, Files.size(path)) - - constructor( - path: Path, - options: Set, - position: Long, - length: Long - ) : this(path, options, position, length, CoroutineScope(CoroutineName("Firefly-file-content-provider"))) -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/MultiPartContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/MultiPartContentProvider.kt deleted file mode 100644 index a9e3acc68..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/MultiPartContentProvider.kt +++ /dev/null @@ -1,302 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.common.coroutine.event -import com.fireflysource.common.exception.UnsupportedOperationException -import com.fireflysource.common.io.AsyncCloseable -import com.fireflysource.net.http.client.HttpClientContentProvider -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.HttpHeader -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import java.io.ByteArrayOutputStream -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.concurrent.CompletableFuture - - -class MultiPartContentProvider : HttpClientContentProvider { - - companion object { - private const val newLine = "\r\n" - private val colonSpaceBytes: ByteArray = byteArrayOf(':'.code.toByte(), ' '.code.toByte()) - private val newLineBytes: ByteArray = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte()) - } - - val contentType: String - private val firstBoundary: ByteArray - private val middleBoundary: ByteArray - private val onlyBoundary: ByteArray - private val lastBoundary: ByteArray - private val parts: MutableList = LinkedList() - - private var index = 0 - private var state = State.FIRST_BOUNDARY - private var open = true - - private val multiPartChannel: Channel = Channel(Channel.UNLIMITED) - private val generatingJob: Job - - init { - val boundary = makeBoundary() - this.contentType = "multipart/form-data; boundary=$boundary" - - val firstBoundaryLine = "--$boundary$newLine" - this.firstBoundary = firstBoundaryLine.toByteArray(StandardCharsets.US_ASCII) - - val middleBoundaryLine = newLine + firstBoundaryLine - this.middleBoundary = middleBoundaryLine.toByteArray(StandardCharsets.US_ASCII) - - val onlyBoundaryLine = "--$boundary--$newLine" - this.onlyBoundary = onlyBoundaryLine.toByteArray(StandardCharsets.US_ASCII) - - val lastBoundaryLine = newLine + onlyBoundaryLine - this.lastBoundary = lastBoundaryLine.toByteArray(StandardCharsets.US_ASCII) - - generatingJob = event { - readMessageLoop@ while (true) { - when (val readMultiPartMessage = multiPartChannel.receive()) { - is GenerateMultiPart -> { - val (buf, future) = readMultiPartMessage - try { - val len = generate(buf) - future.complete(len) - } catch (e: Exception) { - future.completeExceptionally(e) - } - } - is EndMultiPartProvider -> { - open = false - state = State.COMPLETE - parts.forEach { part -> part.closeAsync().await() } - break@readMessageLoop - } - } - - } - } - } - - /** - *

    Adds a field part with the given {@code name} as field name, and the given - * {@code content} as part content.

    - * - * @param name the part name - * @param content the part content - * @param fields the headers associated with this part - */ - fun addPart(name: String, content: HttpClientContentProvider, fields: HttpFields?) { - parts.add(Part(name, null, content, fields, "text/plain")) - } - - /** - *

    Adds a file part with the given {@code name} as field name, the given - * {@code fileName} as file name, and the given {@code content} as part content.

    - * - * @param name the part name - * @param fileName the file name associated to this part - * @param content the part content - * @param fields the headers associated with this part - */ - fun addFilePart(name: String, fileName: String?, content: HttpClientContentProvider, fields: HttpFields?) { - parts.add(Part(name, fileName, content, fields, "application/octet-stream")) - } - - override fun length(): Long { - // Compute the length, if possible. - if (parts.isEmpty()) { - return onlyBoundary.size.toLong() - } else { - var result: Long = 0 - for (i in 0 until parts.size) { - result += if (i == 0) firstBoundary.size.toLong() else middleBoundary.size.toLong() - val part = parts[i] - val partLength = part.length - result += partLength - if (partLength < 0) { - result = -1 - break - } - } - if (result > 0) { - result += lastBoundary.size.toLong() - } - return result - } - } - - override fun isOpen(): Boolean = open - - override fun toByteBuffer(): ByteBuffer { - throw UnsupportedOperationException("The multi part content does not support this method") - } - - private suspend fun closeAwait() { - close() - generatingJob.join() - } - - override fun closeAsync(): CompletableFuture { - val future = CompletableFuture() - event { - closeAwait() - future.complete(null) - } - return future - } - - override fun close() { - multiPartChannel.trySend(EndMultiPartProvider) - } - - override fun read(byteBuffer: ByteBuffer): CompletableFuture { - if (!isOpen) { - return endStream() - } - - if (state == State.COMPLETE) { - return endStream() - } - - val future = CompletableFuture() - multiPartChannel.trySend(GenerateMultiPart(byteBuffer, future)) - return future - } - - private suspend fun generate(byteBuffer: ByteBuffer): Int { - while (true) { - when (state) { - State.FIRST_BOUNDARY -> { - return if (parts.isEmpty()) { - state = State.COMPLETE - byteBuffer.put(onlyBoundary) - onlyBoundary.size - } else { - state = State.HEADERS - byteBuffer.put(firstBoundary) - firstBoundary.size - } - } - State.HEADERS -> { - val part = parts[index] - state = State.CONTENT - byteBuffer.put(part.headers) - return part.headers.size - } - State.CONTENT -> { - val part = parts[index] - val len = part.content.read(byteBuffer).await() - if (len >= 0) { - return len - } else { - ++index - state = if (index == parts.size) State.LAST_BOUNDARY else State.MIDDLE_BOUNDARY - } - } - State.MIDDLE_BOUNDARY -> { - state = State.HEADERS - byteBuffer.put(middleBoundary) - return middleBoundary.size - } - State.LAST_BOUNDARY -> { - state = State.COMPLETE - byteBuffer.put(lastBoundary) - return lastBoundary.size - } - State.COMPLETE -> { - open = false - return -1 - } - } - } - } - - private fun makeBoundary(): String { - val random = Random() - val builder = StringBuilder("FireflyHttpClientBoundary") - val length = builder.length - while (builder.length < length + 16) { - val rnd = random.nextLong() - builder.append((if (rnd < 0) -rnd else rnd).toString(36)) - } - builder.setLength(length + 16) - return builder.toString() - } - - private class Part( - name: String, - fileName: String?, - val content: HttpClientContentProvider, - fields: HttpFields?, - val contentType: String - ) : AsyncCloseable { - val headers: ByteArray - val length: Long - - init { - // Compute the Content-Disposition. - var contentDisposition = "Content-Disposition: form-data; name=\"$name\"" - if (fileName != null) { - contentDisposition += "; filename=\"$fileName\"" - } - contentDisposition += newLine - - // Compute the Content-Type. - var contentType = fields?.get(HttpHeader.CONTENT_TYPE) - if (contentType == null) { - contentType = this.contentType - } - contentType = "Content-Type: $contentType$newLine" - - // Compute the headers - if (fields == null || fields.size() == 0) { - var headers = contentDisposition - headers += contentType - headers += newLine - this.headers = headers.toByteArray(StandardCharsets.UTF_8) - } else { - val buffer = ByteArrayOutputStream((fields.size() + 1) * contentDisposition.length) - buffer.write(contentDisposition.toByteArray(StandardCharsets.UTF_8)) - buffer.write(contentType.toByteArray(StandardCharsets.UTF_8)) - for (field in fields) { - if (HttpHeader.CONTENT_TYPE == field.header) { - continue - } - buffer.write(field.name.toByteArray(StandardCharsets.US_ASCII)) - buffer.write(colonSpaceBytes) - val value = field.value - if (value != null) { - buffer.write(value.toByteArray(StandardCharsets.UTF_8)) - } - buffer.write(newLineBytes) - } - buffer.write(newLineBytes) - headers = buffer.toByteArray() - } - - length = if (content.length() >= 0) headers.size + content.length() else -1 - } - - override fun closeAsync(): CompletableFuture { - return content.closeAsync() - } - - override fun close() { - content.close() - } - - } - - private enum class State { - FIRST_BOUNDARY, HEADERS, CONTENT, MIDDLE_BOUNDARY, LAST_BOUNDARY, COMPLETE - } -} - -sealed class MultiPartProviderMessage - -data class GenerateMultiPart( - val byteBuffer: ByteBuffer, val future: CompletableFuture -) : MultiPartProviderMessage() - -object EndMultiPartProvider : MultiPartProviderMessage() \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/StringContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/StringContentProvider.kt deleted file mode 100644 index bd599f3c6..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/content/provider/StringContentProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.client.HttpClientContentProvider -import com.fireflysource.net.http.common.content.provider.AbstractByteBufferContentProvider -import java.nio.charset.Charset - -class StringContentProvider(val content: String, val charset: Charset) : - AbstractByteBufferContentProvider(BufferUtils.toBuffer(content, charset)), HttpClientContentProvider \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/exception/HttpTunnelHandshakeException.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/exception/HttpTunnelHandshakeException.kt deleted file mode 100644 index fa5e4cf6a..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/exception/HttpTunnelHandshakeException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.fireflysource.net.http.client.impl.exception - -class HttpTunnelHandshakeException(message: String) : IllegalStateException(message) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/exception/UnhandledRequestException.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/exception/UnhandledRequestException.kt deleted file mode 100644 index 86e3eb69a..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/client/impl/exception/UnhandledRequestException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fireflysource.net.http.client.impl.exception - -/** - * @author Pengtao Qiu - */ -class UnhandledRequestException(message: String) : IllegalStateException(message) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/handler/AbstractByteBufferContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/handler/AbstractByteBufferContentHandler.kt deleted file mode 100644 index cbead7f82..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/handler/AbstractByteBufferContentHandler.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.fireflysource.net.http.common.content.handler - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.codec.ContentEncoded -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.ContentEncoding -import com.fireflysource.net.http.common.model.HttpStatus -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.concurrent.CompletableFuture - -abstract class AbstractByteBufferContentHandler( - private val maxRequestBodySize: Long = 200 * 1024 * 1024 -) : HttpContentHandler { - - private val byteBufferList = LinkedList() - private var requestSize: Long = 0 - private val utf8String: String by lazy { toString(StandardCharsets.UTF_8) } - - override fun accept(byteBuffer: ByteBuffer, u: T) { - requestSize += byteBuffer.remaining() - if (requestSize > maxRequestBodySize) { - throw BadMessageException(HttpStatus.PAYLOAD_TOO_LARGE_413) - } - - byteBufferList.add(byteBuffer) - } - - override fun closeAsync(): CompletableFuture = Result.DONE - - override fun close() { - } - - fun getByteBuffers(encoding: Optional = Optional.empty()): List { - return if (encoding.isPresent) { - val data = decode(encoding.get()) - listOf(ByteBuffer.wrap(data)) - } else byteBufferList.map { it.duplicate() } - } - - fun toString(charset: Charset, encoding: Optional = Optional.empty()): String { - return if (encoding.isPresent) { - val data = decode(encoding.get()) - String(data, charset) - } else BufferUtils.toString(byteBufferList.map { it.duplicate() }, charset) - } - - private fun decode(encoding: ContentEncoding): ByteArray { - val buffer = BufferUtils.merge(byteBufferList.map { it.duplicate() }) - val bytes = BufferUtils.toArray(buffer) - return ContentEncoded.decode(bytes, encoding) - } - - override fun toString(): String = utf8String -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/handler/AbstractFileContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/handler/AbstractFileContentHandler.kt deleted file mode 100644 index 175e92c0d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/handler/AbstractFileContentHandler.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.fireflysource.net.http.common.content.handler - -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.common.io.closeAsync -import com.fireflysource.common.io.openFileChannelAsync -import com.fireflysource.common.io.writeAwait -import com.fireflysource.common.sys.SystemLogger -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import java.nio.ByteBuffer -import java.nio.file.OpenOption -import java.nio.file.Path -import java.util.concurrent.CompletableFuture - -abstract class AbstractFileContentHandler(val path: Path, vararg options: OpenOption) : HttpContentHandler { - - companion object { - private val log = SystemLogger.create(AbstractFileContentHandler::class.java) - } - - private val inputChannel: Channel = Channel(Channel.UNLIMITED) - private val scope: CoroutineScope = CoroutineScope(CoroutineName("Firefly-file-content-handler")) - private val writeJob: Job = scope.launch { - val fileChannel = openFileChannelAsync(path, *options).await() - var pos = 0L - - suspend fun closeFileChannel() { - try { - fileChannel.closeAsync().join() - } catch (e: Exception) { - log.error(e) { "close file channel exception." } - } - } - - suspend fun write(): Boolean { - var closed = false - when (val writeFileMessage = inputChannel.receive()) { - is WriteFileRequest -> { - val buf = writeFileMessage.buffer - flushDataLoop@ while (buf.hasRemaining()) { - val len = fileChannel.writeAwait(buf, pos) - if (len < 0) { - closeFileChannel() - closed = true - } - pos += len - } - } - is EndWriteFile -> { - closeFileChannel() - closed = true - } - } - return closed - } - - writeMessageLoop@ while (true) { - val closed = try { - write() - } catch (e: Exception) { - log.error(e) { "read file exception." } - closeFileChannel() - true - } - if (closed) break@writeMessageLoop - } - } - - - override fun accept(buffer: ByteBuffer, t: T) { - inputChannel.trySend(WriteFileRequest(buffer)) - } - - override fun closeAsync(): CompletableFuture = - scope.launch { closeAwait() }.asVoidFuture().thenAccept { scope.cancel() } - - override fun close() { - inputChannel.trySend(EndWriteFile) - } - - private suspend fun closeAwait() { - close() - writeJob.join() - } - -} - -sealed interface WriteFileMessage -@JvmInline -value class WriteFileRequest(val buffer: ByteBuffer) : WriteFileMessage -object EndWriteFile : WriteFileMessage diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/provider/AbstractByteBufferContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/provider/AbstractByteBufferContentProvider.kt deleted file mode 100644 index 6bda2525c..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/provider/AbstractByteBufferContentProvider.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.http.common.content.provider - -import com.fireflysource.common.io.InputChannel -import com.fireflysource.common.sys.Result -import java.nio.ByteBuffer -import java.util.concurrent.CompletableFuture -import kotlin.math.min - -abstract class AbstractByteBufferContentProvider(private val content: ByteBuffer) : InputChannel { - private val buffer = content.duplicate() - private val length = content.remaining().toLong() - private var open = true - - override fun isOpen(): Boolean = open - - override fun close() { - open = false - } - - open fun length(): Long = length - - open fun toByteBuffer(): ByteBuffer = buffer.duplicate() - - override fun read(byteBuffer: ByteBuffer): CompletableFuture { - if (!isOpen) { - return endStream() - } - - if (!content.hasRemaining()) { - return endStream() - } - - if (!byteBuffer.hasRemaining()) { - val future = CompletableFuture() - future.complete(0) - return future - } - - val len = min(content.remaining(), byteBuffer.remaining()) - val to = ByteArray(len) - content.get(to) - byteBuffer.put(to) - - val future = CompletableFuture() - future.complete(len) - return future - } - - override fun closeAsync(): CompletableFuture { - close() - return Result.DONE - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/provider/AbstractFileContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/provider/AbstractFileContentProvider.kt deleted file mode 100644 index 356546a43..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/content/provider/AbstractFileContentProvider.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.fireflysource.net.http.common.content.provider - -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.common.coroutine.clear -import com.fireflysource.common.exception.UnsupportedOperationException -import com.fireflysource.common.io.InputChannel -import com.fireflysource.common.io.closeAsync -import com.fireflysource.common.io.openFileChannelAsync -import com.fireflysource.common.io.readAwait -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import java.nio.ByteBuffer -import java.nio.file.Files -import java.nio.file.OpenOption -import java.nio.file.Path -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean - -abstract class AbstractFileContentProvider( - val path: Path, - val options: Set, - var position: Long, - val length: Long, - val scope: CoroutineScope = CoroutineScope(CoroutineName("Firefly-file-content-provider")) -) : InputChannel { - - private val readChannel: Channel = Channel(Channel.UNLIMITED) - private val readJob: Job - private val closed = AtomicBoolean(false) - private val lastPosition = position + length - - constructor(path: Path, vararg options: OpenOption) : this(path, options.toSet(), 0, Files.size(path)) - - init { - readJob = scope.launch { - val fileChannel = openFileChannelAsync(path, options).await() - - readMessageLoop@ while (true) { - when (val readFileMessage = readChannel.receive()) { - is ReadFileRequest -> { - val (buf, future) = readFileMessage - - suspend fun endRead() { - fileChannel.closeAsync().join() - closed.set(true) - future.complete(-1) - } - - if (position >= lastPosition) { - endRead() - break@readMessageLoop - } - try { - val len = fileChannel.readAwait(buf, position) - if (len < 0) { - endRead() - break@readMessageLoop - } else { - position += len - future.complete(len) - } - } catch (e: Exception) { - future.completeExceptionally(e) - } - } - is EndReadFile -> { - fileChannel.closeAsync().join() - closed.set(true) - break@readMessageLoop - } - } - } - - readChannel.clear() - } - } - - fun length(): Long = length - - override fun isOpen(): Boolean = !closed.get() - - fun toByteBuffer(): ByteBuffer { - throw UnsupportedOperationException("The file content does not support this method") - } - - override fun closeAsync(): CompletableFuture = - scope.launch { closeAwait() }.asVoidFuture().thenAccept { scope.cancel() } - - override fun close() { - if (isOpen) readChannel.trySend(EndReadFile) - } - - private suspend fun closeAwait() { - close() - readJob.join() - } - - override fun read(byteBuffer: ByteBuffer): CompletableFuture { - val future = CompletableFuture() - readChannel.trySend(ReadFileRequest(byteBuffer, future)) - return future - } - -} - -sealed class ReadFileMessage -data class ReadFileRequest(val buffer: ByteBuffer, val future: CompletableFuture) : ReadFileMessage() -object EndReadFile : ReadFileMessage() \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/Http1GeneratingResultException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/Http1GeneratingResultException.java deleted file mode 100644 index 58422a173..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/Http1GeneratingResultException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class Http1GeneratingResultException extends RuntimeException { - - public Http1GeneratingResultException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/Http2StreamFrameProcessException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/Http2StreamFrameProcessException.java deleted file mode 100644 index 56fc218b5..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/Http2StreamFrameProcessException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class Http2StreamFrameProcessException extends RuntimeException { - - public Http2StreamFrameProcessException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/HttpServerConnectionListenerNotSetException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/HttpServerConnectionListenerNotSetException.java deleted file mode 100644 index 2c1bcd256..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/HttpServerConnectionListenerNotSetException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class HttpServerConnectionListenerNotSetException extends RuntimeException { - - public HttpServerConnectionListenerNotSetException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/HttpServerResponseNotCommitException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/HttpServerResponseNotCommitException.java deleted file mode 100644 index a0acaeca3..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/HttpServerResponseNotCommitException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class HttpServerResponseNotCommitException extends RuntimeException { - - public HttpServerResponseNotCommitException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/MissingRemoteHostException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/MissingRemoteHostException.java deleted file mode 100644 index 931ec8ff6..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/MissingRemoteHostException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class MissingRemoteHostException extends RuntimeException { - - public MissingRemoteHostException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/MissingRemotePortException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/MissingRemotePortException.java deleted file mode 100644 index 0be296ecb..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/MissingRemotePortException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class MissingRemotePortException extends RuntimeException { - - public MissingRemotePortException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/NotSupportHttpVersionException.java b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/NotSupportHttpVersionException.java deleted file mode 100644 index 4df3a1139..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/exception/NotSupportHttpVersionException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.common.exception; - -public class NotSupportHttpVersionException extends RuntimeException { - - public NotSupportHttpVersionException(String message) { - super(message); - } -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/model/HttpFieldsExtension.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/model/HttpFieldsExtension.kt deleted file mode 100644 index 7faed74c3..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/model/HttpFieldsExtension.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.net.http.common.model - -import com.fireflysource.net.http.common.model.HttpHeader.CONNECTION -import com.fireflysource.net.http.common.model.HttpHeader.EXPECT -import com.fireflysource.net.http.common.model.HttpHeaderValue.CLOSE -import com.fireflysource.net.http.common.model.HttpHeaderValue.CONTINUE -import com.fireflysource.net.http.common.model.HttpVersion.HTTP_0_9 -import com.fireflysource.net.http.common.model.HttpVersion.HTTP_1_0 - -fun HttpFields.expectServerAcceptsContent(): Boolean { - return this.contains(EXPECT, CONTINUE.value) -} - -fun HttpFields.isCloseConnection(version: HttpVersion): Boolean = when (version) { - HTTP_0_9, HTTP_1_0 -> !this.contains(CONNECTION, HttpHeaderValue.KEEP_ALIVE.value) - else -> this.contains(CONNECTION, CLOSE.value) -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v1/decoder/HttpParserExtension.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v1/decoder/HttpParserExtension.kt deleted file mode 100644 index 17bde44e9..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v1/decoder/HttpParserExtension.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.net.http.common.v1.decoder - -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.tcp.TcpConnection -import kotlinx.coroutines.future.await -import java.nio.ByteBuffer - -suspend fun HttpParser.parseAll(tcpConnection: TcpConnection): ByteBuffer? { - var lastBuffer: ByteBuffer? = null - readLoop@ while (!isState(HttpParser.State.END)) { - val buffer = tcpConnection.read().await() - lastBuffer = buffer - parseLoop@ while (buffer.remaining() > 0) { - val beforeRemaining = buffer.remaining() - val exit = this.parseNext(buffer) - val afterRemaining = buffer.remaining() - when { - exit -> break@readLoop - isState(HttpParser.State.END) -> break@readLoop - beforeRemaining == afterRemaining -> throw BadMessageException("The received data cannot be consumed") - } - } - } - return lastBuffer -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorExtension.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorExtension.kt deleted file mode 100644 index 0ee6fda97..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorExtension.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.net.http.common.v1.encoder - -import com.fireflysource.net.http.common.exception.Http1GeneratingResultException - -fun HttpGenerator.Result.assert(expectResult: HttpGenerator.Result) { - if (this != expectResult) { - throw Http1GeneratingResultException("The HTTP generator result is $this, but expect $expectResult") - } -} - -fun HttpGenerator.Result.assert(expectResults: Set) { - if (!expectResults.contains(this)) { - throw Http1GeneratingResultException("The HTTP generator result is $this, but expect $expectResults") - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AbstractFlowControlStrategy.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AbstractFlowControlStrategy.kt deleted file mode 100644 index 687df9dbe..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AbstractFlowControlStrategy.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream - -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.HttpConfig.DEFAULT_WINDOW_SIZE -import com.fireflysource.net.http.common.v2.frame.WindowUpdateFrame - -abstract class AbstractFlowControlStrategy( - protected var initialStreamRecvWindow: Int = DEFAULT_WINDOW_SIZE -) : FlowControl { - - companion object { - private val log = SystemLogger.create(AbstractFlowControlStrategy::class.java) - } - - private var initialStreamSendWindow: Int = DEFAULT_WINDOW_SIZE - - override fun onStreamCreated(stream: Stream) { - val http2Stream = stream as AsyncHttp2Stream - http2Stream.updateSendWindow(initialStreamSendWindow) - http2Stream.updateRecvWindow(initialStreamRecvWindow) - } - - override fun onStreamDestroyed(stream: Stream) { - } - - override fun updateInitialStreamWindow( - http2Connection: Http2Connection, - initialStreamWindow: Int, - local: Boolean - ) { - val previousInitialStreamWindow: Int - if (local) { - previousInitialStreamWindow = initialStreamRecvWindow - initialStreamRecvWindow = initialStreamWindow - } else { - previousInitialStreamWindow = initialStreamSendWindow - initialStreamSendWindow = initialStreamWindow - } - - val delta = initialStreamWindow - previousInitialStreamWindow - if (delta == 0) return - - http2Connection.streams.forEach { stream -> - if (local) { - val http2Stream = stream as AsyncHttp2Stream - http2Stream.updateRecvWindow(delta) - log.debug { "Updated initial stream recv window $previousInitialStreamWindow -> $initialStreamWindow for $http2Stream" } - } else { - (http2Connection as AsyncHttp2Connection).onWindowUpdate(stream, WindowUpdateFrame(stream.id, delta)) - } - } - } - - override fun onWindowUpdate(http2Connection: Http2Connection, stream: Stream?, frame: WindowUpdateFrame) { - if (frame.isStreamWindowUpdate) { - // The stream may have been removed concurrently. - if (stream != null && stream is AsyncHttp2Stream) { - stream.updateSendWindow(frame.windowDelta) - } - } else { - (http2Connection as AsyncHttp2Connection).updateSendWindow(frame.windowDelta) - } - } - - override fun onDataReceived(http2Connection: Http2Connection, stream: Stream?, length: Int) { - val connection = http2Connection as AsyncHttp2Connection - var oldSize: Int = connection.updateRecvWindow(-length) - log.debug { "Data received, $length bytes, updated session recv window $oldSize -> ${oldSize - length} for $connection" } - - if (stream != null && stream is AsyncHttp2Stream) { - oldSize = stream.updateRecvWindow(-length) - log.debug { "Data received, $length bytes, updated stream recv window $oldSize -> ${oldSize - length} for $stream" } - } - } - - override fun onDataSending(stream: Stream, length: Int) { - if (length == 0) return - - val http2Stream = stream as AsyncHttp2Stream - val connection = http2Stream.http2Connection as AsyncHttp2Connection - val oldSessionWindow = connection.updateSendWindow(-length) - val newSessionWindow = oldSessionWindow - length - log.debug { "Sending, session send window $oldSessionWindow -> $newSessionWindow for $connection" } - - val oldStreamWindow = http2Stream.updateSendWindow(-length) - val newStreamWindow = oldStreamWindow - length - log.debug { "Sending, stream send window $oldStreamWindow -> $newStreamWindow for $stream" } - } - - override fun onDataSent(stream: Stream, length: Int) { - } - - override fun windowUpdate(http2Connection: Http2Connection, stream: Stream?, frame: WindowUpdateFrame) { - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AsyncHttp2Connection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AsyncHttp2Connection.kt deleted file mode 100644 index 7a5f8bc1b..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AsyncHttp2Connection.kt +++ /dev/null @@ -1,943 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream - -import com.fireflysource.common.concurrent.AtomicBiInteger -import com.fireflysource.common.concurrent.Atomics -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.math.MathUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.* -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.Connection -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.TcpBasedHttpConnection -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.HttpVersion -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v2.decoder.Parser -import com.fireflysource.net.http.common.v2.encoder.Generator -import com.fireflysource.net.http.common.v2.frame.* -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpCoroutineDispatcher -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.io.IOException -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicReference -import java.util.function.Consumer -import kotlin.math.min - -abstract class AsyncHttp2Connection( - private val initStreamId: Int, - val config: HttpConfig, - private val tcpConnection: TcpConnection, - private val flowControl: FlowControl, - private val listener: Http2Connection.Listener -) : Connection by tcpConnection, TcpCoroutineDispatcher by tcpConnection, Http2Connection, TcpBasedHttpConnection, - Parser.Listener { - - companion object { - private val log = SystemLogger.create(AsyncHttp2Connection::class.java) - val defaultHttp2ConnectionListener = object : Http2Connection.Listener.Adapter() { - override fun onReset(http2Connection: Http2Connection, frame: ResetFrame) { - log.info { "HTTP2 connection received reset frame. $frame" } - } - - override fun onFailure(http2Connection: Http2Connection, e: Throwable) { - log.error(e) { "HTTP2 connection exception. ${http2Connection.id}" } - } - } - } - - private val localStreamId = AtomicInteger(initStreamId) - private val http2StreamMap = ConcurrentHashMap() - private val localStreamCount = AtomicInteger() - private val remoteStreamCount = AtomicBiInteger() - private val lastRemoteStreamId = AtomicInteger() - private val closeState = AtomicReference(CloseState.NOT_CLOSED) - - private val sendWindow = AtomicInteger(HttpConfig.DEFAULT_WINDOW_SIZE) - private val recvWindow = AtomicInteger(HttpConfig.DEFAULT_WINDOW_SIZE) - - private val generator = Generator(config.maxDynamicTableSize, config.maxHeaderBlockFragment) - - private var maxLocalStreams: Int = -1 - private var maxRemoteStreams: Int = -1 - private var streamIdleTimeout: Long = config.streamIdleTimeout - private var pushEnabled: Boolean = false - private var closeFrame: GoAwayFrame? = null - protected val initialSessionRecvWindow: Int = config.initialSessionRecvWindow - - private val flusher = FrameEntryFlusher() - - init { - tcpConnection.onClose { clearStreams() } - } - - private inner class FrameEntryFlusher { - - private val frameEntryChannel = Channel(Channel.UNLIMITED) - - init { - launchEntryFlushJob() - } - - private fun launchEntryFlushJob() = tcpConnection.coroutineScope.launch { - while (true) { - when (val frameEntry = frameEntryChannel.receive()) { - is ControlFrameEntry -> flushControlFrameEntry(frameEntry) - is DataFrameEntry -> flushOrStashDataFrameEntry(frameEntry) - is OnWindowUpdateMessage -> onWindowUpdateMessage(frameEntry) - } - } - }.invokeOnCompletion { terminate() } - - private suspend fun flushOrStashDataFrameEntry(frameEntry: DataFrameEntry) { - try { - val http2Stream = frameEntry.stream as AsyncHttp2Stream - val isEmpty = http2Stream.flushStashedDataFrameEntries() - if (isEmpty) { - val success = flushDataFrame(frameEntry) - if (!success) { - http2Stream.stashFrameEntry(frameEntry) - val dataRemaining = frameEntry.dataRemaining - log.debug { "Send data frame failure. Stash a data frame. remaining: $dataRemaining, stream: $http2Stream" } - } - } else { - http2Stream.stashFrameEntry(frameEntry) - log.debug { "Stashed data frame is not empty. Stash a data frame. stream: $http2Stream" } - } - } catch (e: Exception) { - log.error { "flush data frame exception. ${e.javaClass.name} ${e.message}" } - frameEntry.result.accept(createFailedResult(-1L, e)) - } - } - - private fun AsyncHttp2Stream.stashFrameEntry(frameEntry: DataFrameEntry) { - this.stashedDataFrames.offer(frameEntry) - } - - private suspend fun AsyncHttp2Stream.flushStashedDataFrameEntries(): Boolean { - val stashedDataFrames = this.stashedDataFrames - flush@ while (stashedDataFrames.isNotEmpty()) { - val stashedFrameEntry = stashedDataFrames.peek() - if (stashedFrameEntry != null) { - val success = flushDataFrame(stashedFrameEntry) - if (success) { - val entry = stashedDataFrames.poll() - val dataRemaining = entry.dataRemaining - val writtenBytes = entry.writtenBytes - log.debug { "Poll a stashed data frame. remaining: ${dataRemaining}, written: $writtenBytes" } - } else break@flush - } else break@flush - } - return stashedDataFrames.isEmpty() - } - - private suspend fun flushDataFrame(frameEntry: DataFrameEntry): Boolean { - val dataFrame = frameEntry.frame - val stream = frameEntry.stream as AsyncHttp2Stream - val dataRemaining = frameEntry.dataRemaining - - val connectionSendWindow = getSendWindow() - val streamSendWindow = stream.getSendWindow() - val window = min(streamSendWindow, connectionSendWindow) - - log.debug { "Flush data frame. window: $window, remaining: $dataRemaining" } - if (window <= 0 && dataRemaining > 0) { - log.debug { "The sending window not enough. stream: $stream" } - return false - } - - val length = min(dataRemaining, window) - val frameBytes = generator.data(dataFrame, length) - val dataLength = frameBytes.dataLength - log.debug { "Before flush data frame. window: $window, remaining: $dataRemaining, dataLength: $dataLength" } - - flowControl.onDataSending(stream, dataLength) - stream.updateClose(dataFrame.isEndStream, CloseState.Event.BEFORE_SEND) - - val writtenBytes = writeAndFlush(frameBytes.byteBuffers, dataFrame) - frameEntry.dataRemaining -= dataLength - frameEntry.writtenBytes += writtenBytes - - flowControl.onDataSent(stream, dataLength) - val currentRemaining = frameEntry.dataRemaining - log.debug { "After flush data frame. window: $window, remaining: $currentRemaining, dataLength: $dataLength" } - - return if (currentRemaining == 0) { - // Only now we can update the close state and eventually remove the stream. - if (stream.updateClose(dataFrame.isEndStream, CloseState.Event.AFTER_SEND)) { - removeStream(stream) - } - frameEntry.result.accept(Result(true, frameEntry.writtenBytes, null)) - log.debug { "Flush all data success. stream: $stream" } - true - } else { - log.debug { "Flush data success. stream: $stream, remaining: $currentRemaining" } - false - } - } - - fun onWindowUpdate(stream: Stream?, frame: WindowUpdateFrame) { - frameEntryChannel.trySend(OnWindowUpdateMessage(stream, frame)) - } - - private suspend fun onWindowUpdateMessage(onWindowUpdateMessage: OnWindowUpdateMessage) { - val (stream, frame) = onWindowUpdateMessage - flowControl.onWindowUpdate(this@AsyncHttp2Connection, stream, frame) - if (stream != null) { - val http2Stream = stream as AsyncHttp2Stream - log.debug { "Flush stream stashed data frames. stream: $http2Stream" } - http2Stream.flushStashedDataFrameEntries() - } else { - if (frame.isSessionWindowUpdate) { - log.debug { "Flush all streams stashed data frames. id: ${tcpConnection.id}" } - streams.map { it as AsyncHttp2Stream }.forEach { it.flushStashedDataFrameEntries() } - } - } - log.debug { "Update send window and flush stashed data frame success. frame: $frame" } - } - - private suspend fun flushControlFrameEntry(frameEntry: ControlFrameEntry) { - try { - val length = flushControlFrames(frameEntry) - frameEntry.result.accept(Result(true, length, null)) - } catch (e: Exception) { - frameEntry.result.accept(createFailedResult(-1, e)) - } - } - - private suspend fun flushControlFrames(frameEntry: ControlFrameEntry): Long { - val stream = frameEntry.stream - var writtenBytes = 0L - frameLoop@ for (frame in frameEntry.frames) { - when (frame.type) { - FrameType.HEADERS -> { - val headersFrame = frame as HeadersFrame - if (stream != null && stream is AsyncHttp2Stream) { - stream.updateClose(headersFrame.isEndStream, CloseState.Event.BEFORE_SEND) - } - } - FrameType.SETTINGS -> { - val settingsFrame = frame as SettingsFrame - val initialWindow = settingsFrame.settings[SettingsFrame.INITIAL_WINDOW_SIZE] - if (initialWindow != null) { - flowControl.updateInitialStreamWindow(this@AsyncHttp2Connection, initialWindow, true) - } - } - FrameType.DISCONNECT -> { - terminate() - break@frameLoop - } - else -> { - // ignore the other control frame types - } - } - - val byteBuffers = generator.control(frame).byteBuffers - val bytes = writeAndFlush(byteBuffers, frame) - writtenBytes += bytes - - when (frame.type) { - FrameType.HEADERS -> { - val headersFrame = frame as HeadersFrame - if (stream != null && stream is AsyncHttp2Stream) { - onStreamOpened(stream) - if (stream.updateClose(headersFrame.isEndStream, CloseState.Event.AFTER_SEND)) { - removeStream(stream) - } - } - } - FrameType.RST_STREAM -> { - if (stream != null && stream is AsyncHttp2Stream) { - stream.close() - removeStream(stream) - } - } - FrameType.PUSH_PROMISE -> { - if (stream != null && stream is AsyncHttp2Stream) { - stream.updateClose(true, CloseState.Event.RECEIVED) - } - } - FrameType.GO_AWAY -> { - tcpConnection.close { - log.info { "Send go away frame and close TCP connection success" } - } - } - FrameType.WINDOW_UPDATE -> { - flowControl.windowUpdate(this@AsyncHttp2Connection, stream, frame as WindowUpdateFrame) - } - else -> { - // ignore the other control frame types - } - } - } - - return writtenBytes - } - - private suspend fun writeAndFlush(byteBuffers: List, frame: Frame): Long { - val flush = when (frame) { - is HeadersFrame -> { - if (frame.isEndStream) true - else { - val metaData = frame.metaData - metaData is MetaData.Response && metaData.status == HttpStatus.CONTINUE_100 - } - } - is DataFrame -> frame.isEndStream - else -> true - } - return if (flush) tcpConnection.writeAndFlush(byteBuffers, 0, byteBuffers.size).await() - else tcpConnection.write(byteBuffers, 0, byteBuffers.size).await() - } - - fun sendControlFrame(stream: Stream?, vararg frames: Frame): CompletableFuture { - val future = CompletableFuture() - frameEntryChannel.trySend(ControlFrameEntry(stream, arrayOf(*frames), futureToConsumer(future))) - return future - } - - fun sendDataFrame(stream: Stream, frame: DataFrame): CompletableFuture { - val future = CompletableFuture() - frameEntryChannel.trySend(DataFrameEntry(stream, frame, futureToConsumer(future))) - return future - } - } - - fun sendControlFrame(stream: Stream?, vararg frames: Frame): CompletableFuture = - flusher.sendControlFrame(stream, *frames) - - fun launchParserJob(parser: Parser) = tcpConnection.coroutineScope.launch { - recvLoop@ while (true) { - val buffer = try { - tcpConnection.read().await() - } catch (e: Exception) { - break@recvLoop - } - parsingLoop@ while (buffer.hasRemaining()) { - parser.parse(buffer) - } - } - } - - override fun isSecureConnection(): Boolean = tcpConnection.isSecureConnection - - override fun getHttpVersion(): HttpVersion = HttpVersion.HTTP_2 - - override fun getTcpConnection(): TcpConnection = tcpConnection - - - fun notifyPreface(): MutableMap { - return try { - val settings = listener.onPreface(this) ?: newDefaultSettings() - val initialWindowSize = settings[SettingsFrame.INITIAL_WINDOW_SIZE] ?: config.initialStreamRecvWindow - flowControl.updateInitialStreamWindow(this, initialWindowSize, true) - settings - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - newDefaultSettings() - } - } - - private fun newDefaultSettings(): MutableMap { - val settings = HashMap(SettingsFrame.DEFAULT_SETTINGS_FRAME.settings) - settings[SettingsFrame.INITIAL_WINDOW_SIZE] = config.initialStreamRecvWindow - settings[SettingsFrame.MAX_CONCURRENT_STREAMS] = config.maxConcurrentStreams - return settings - } - - // stream management - override fun getStreams(): MutableCollection = http2StreamMap.values - - override fun getStream(streamId: Int): Stream? = http2StreamMap[streamId] - - override fun newStream(headersFrame: HeadersFrame, promise: Consumer>, listener: Stream.Listener) { - try { - val frameStreamId = headersFrame.streamId - if (frameStreamId == 0) { - val newHeadersFrame = copyHeadersFrameAndSetCurrentStreamId(headersFrame) - val stream = createLocalStream(newHeadersFrame.streamId, listener) - sendNewHeadersFrame(stream, newHeadersFrame, promise) - } else { - val stream = createLocalStream(frameStreamId, listener) - sendNewHeadersFrame(stream, headersFrame, promise) - } - } catch (e: Exception) { - promise.accept(Result(false, null, e)) - } - } - - private fun copyHeadersFrameAndSetCurrentStreamId(headersFrame: HeadersFrame): HeadersFrame { - val nextStreamId = getNextStreamId() - val priority = headersFrame.priority - val priorityFrame = if (priority != null) { - PriorityFrame(nextStreamId, priority.parentStreamId, priority.weight, priority.isExclusive) - } else null - return HeadersFrame(nextStreamId, headersFrame.metaData, priorityFrame, headersFrame.isEndStream) - } - - private fun sendNewHeadersFrame(stream: Stream, newHeadersFrame: HeadersFrame, promise: Consumer>) { - sendControlFrame(stream, newHeadersFrame) - .thenAccept { promise.accept(Result(true, stream, null)) } - .exceptionallyAccept { promise.accept(createFailedResult(null, it)) } - } - - fun removeStream(stream: Stream) { - val removed = http2StreamMap.remove(stream.id) - if (removed != null) { - onStreamClosed(stream) - flowControl.onStreamDestroyed(stream) - log.debug { "Removed $stream" } - } - } - - protected fun getNextStreamId(): Int = getAndIncreaseStreamId(localStreamId, initStreamId) - - private fun getCurrentLocalStreamId(): Int = localStreamId.get() - - protected fun createLocalStream(streamId: Int, listener: Stream.Listener): Stream { - checkMaxLocalStreams() - val stream = AsyncHttp2Stream(this, streamId, true, listener) - if (http2StreamMap.putIfAbsent(streamId, stream) == null) { - stream.idleTimeout = streamIdleTimeout - flowControl.onStreamCreated(stream) // TODO before preface - log.debug { "Created local $stream" } - return stream - } else { - localStreamCount.decrementAndGet() - throw IllegalStateException("Duplicate stream $streamId") - } - } - - private fun checkMaxLocalStreams() { - val maxCount = maxLocalStreams - if (maxCount > 0) { - while (true) { - val localCount = localStreamCount.get() - if (localCount >= maxCount) { - throw IllegalStateException("Max local stream count $localCount exceeded $maxCount") - } - if (localStreamCount.compareAndSet(localCount, localCount + 1)) { - break - } - } - } else { - localStreamCount.incrementAndGet() - } - } - - protected fun createRemoteStream(streamId: Int): Stream? { - // SPEC: exceeding max concurrent streams treated as stream error. - if (!checkMaxRemoteStreams(streamId)) { - return null - } - val stream: Stream = AsyncHttp2Stream(this, streamId, false) - // SPEC: duplicate stream treated as connection error. - return if (http2StreamMap.putIfAbsent(streamId, stream) == null) { - updateLastRemoteStreamId(streamId) - stream.idleTimeout = streamIdleTimeout - flowControl.onStreamCreated(stream) - log.debug { "Created remote $stream" } - stream - } else { - remoteStreamCount.addAndGetHi(-1) - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream") - null - } - } - - private fun checkMaxRemoteStreams(streamId: Int): Boolean { - while (true) { - val encoded = remoteStreamCount.get() - val remoteCount = AtomicBiInteger.getHi(encoded) - val remoteClosing = AtomicBiInteger.getLo(encoded) - val maxCount: Int = maxRemoteStreams - if (maxCount >= 0 && remoteCount - remoteClosing >= maxCount) { - reset(ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), discard()) - return false - } - if (remoteStreamCount.compareAndSet(encoded, remoteCount + 1, remoteClosing)) { - break - } - } - return true - } - - protected open fun onStreamOpened(stream: Stream) {} - - protected open fun onStreamClosed(stream: Stream) {} - - protected open fun notifyNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - return try { - listener.onNewStream(stream, frame) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - AsyncHttp2Stream.defaultStreamListener - } - } - - private fun updateLastRemoteStreamId(streamId: Int) { - Atomics.updateMax(lastRemoteStreamId, streamId) - } - - fun updateStreamCount(local: Boolean, deltaStreams: Int, deltaClosing: Int) { - if (local) { - localStreamCount.addAndGet(deltaStreams) - } else { - remoteStreamCount.add(deltaStreams, deltaClosing) - } - } - - fun isClientStream(streamId: Int) = (streamId and 1 == 1) - - private fun getLastRemoteStreamId(): Int { - return lastRemoteStreamId.get() - } - - protected fun isLocalStreamClosed(streamId: Int): Boolean { - return streamId <= getCurrentLocalStreamId() - } - - protected fun isRemoteStreamClosed(streamId: Int): Boolean { - return streamId <= getLastRemoteStreamId() - } - - override fun onStreamFailure(streamId: Int, error: Int, reason: String) { - val stream = getStream(streamId) - if (stream != null && stream is AsyncHttp2Stream) { - stream.process(FailureFrame(error, reason), discard()) - } else { - reset(ResetFrame(streamId, error), discard()) - } - } - - - // reset frame - override fun onReset(frame: ResetFrame) { - log.debug { "Received $frame" } - val stream = getStream(frame.streamId) - if (stream != null && stream is AsyncHttp2Stream) { - stream.process(frame, discard()) - } else { - onResetForUnknownStream(frame) - } - } - - protected fun notifyReset(http2Connection: Http2Connection, frame: ResetFrame) { - try { - listener.onReset(http2Connection, frame) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - protected abstract fun onResetForUnknownStream(frame: ResetFrame) - - protected fun reset(frame: ResetFrame, result: Consumer>) { - sendControlFrame(getStream(frame.streamId), frame) - .thenAccept { result.accept(SUCCESS) } - .exceptionallyAccept { result.accept(createFailedResult(it)) } - } - - - // headers frame - abstract override fun onHeaders(frame: HeadersFrame) - - protected fun notifyHeaders(stream: AsyncHttp2Stream, frame: HeadersFrame) { - try { - stream.listener.onHeaders(stream, frame) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - fun push(frame: PushPromiseFrame, promise: Consumer>, listener: Stream.Listener) { - val promiseStreamId = getNextStreamId() - val pushStream = createLocalStream(promiseStreamId, listener) - val pushPromiseFrame = PushPromiseFrame(frame.streamId, promiseStreamId, frame.metaData) - sendControlFrame(pushStream, pushPromiseFrame) - .thenAccept { promise.accept(Result(true, pushStream, null)) } - .exceptionallyAccept { promise.accept(Result(false, null, it)) } - } - - - // data frame - override fun onData(frame: DataFrame) { - onData(frame, discard()) - } - - private fun onData(frame: DataFrame, result: Consumer>) { - log.debug { "Received $frame" } - val streamId = frame.streamId - val stream = getStream(streamId) - - // SPEC: the session window must be updated even if the stream is null. - // The flow control length includes the padding bytes. - val flowControlLength = frame.remaining() + frame.padding() - flowControl.onDataReceived(this, stream, flowControlLength) - - if (stream != null) { - if (getRecvWindow() < 0) { - onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "session_window_exceeded", result) - } else { - val dataResult = Consumer> { r -> - flowControl.onDataConsumed(this@AsyncHttp2Connection, stream, flowControlLength) - result.accept(r) - } - val http2Stream = stream as AsyncHttp2Stream - http2Stream.process(frame, dataResult) - } - } else { - log.debug("Stream #{} not found", streamId) - // We must enlarge the session flow control window, - // otherwise, the other requests will be stalled. - flowControl.onDataConsumed(this, null, flowControlLength) - val local = (streamId and 1) == (getCurrentLocalStreamId() and 1) - val closed = if (local) isLocalStreamClosed(streamId) else isRemoteStreamClosed(streamId) - if (closed) reset(ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), result) - else onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_data_frame", result) - } - } - - fun sendDataFrame(stream: Stream, frame: DataFrame) = flusher.sendDataFrame(stream, frame) - - - // priority frame - override fun priority(frame: PriorityFrame, result: Consumer>): Int { - val stream = http2StreamMap[frame.streamId] - return if (stream == null) { - val newStreamId = getNextStreamId() - val newFrame = PriorityFrame(newStreamId, frame.parentStreamId, frame.weight, frame.isExclusive) - sendControlFrame(null, newFrame) - .thenAccept { result.accept(SUCCESS) } - .exceptionallyAccept { result.accept(createFailedResult(it)) } - newStreamId - } else { - sendControlFrame(stream, frame) - .thenAccept { result.accept(SUCCESS) } - .exceptionallyAccept { result.accept(createFailedResult(it)) } - stream.id - } - } - - override fun onPriority(frame: PriorityFrame) { - log.debug { "Received $frame" } - } - - - // settings frame - override fun settings(frame: SettingsFrame, result: Consumer>) { - sendControlFrame(null, frame) - .thenAccept { result.accept(SUCCESS) } - .exceptionallyAccept { result.accept(createFailedResult(it)) } - } - - override fun onSettings(frame: SettingsFrame) { - // SPEC: SETTINGS frame MUST be replied. - onSettings(frame, true) - } - - fun onSettings(frame: SettingsFrame, reply: Boolean) { - log.debug { "received frame: $frame" } - if (frame.isReply) { - return - } - - frame.settings.forEach { (key, value) -> - when (key) { - SettingsFrame.HEADER_TABLE_SIZE -> { - log.debug { "Updating HPACK header table size to $value for $this" } - generator.setHeaderTableSize(value) - } - SettingsFrame.ENABLE_PUSH -> { - val enabled = value == 1 - log.debug { "${if (enabled) "Enabling" else "Disabling"} push for $this" } - pushEnabled = enabled - } - SettingsFrame.MAX_CONCURRENT_STREAMS -> { - log.debug { "Updating max local concurrent streams to $value for $this" } - maxLocalStreams = value - } - SettingsFrame.INITIAL_WINDOW_SIZE -> { - log.debug { "Updating initial window size to $value for $this" } - flowControl.updateInitialStreamWindow(this, value, false) - } - SettingsFrame.MAX_FRAME_SIZE -> { - log.debug { "Updating max frame size to $value for $this" } - generator.setMaxFrameSize(value) - } - SettingsFrame.MAX_HEADER_LIST_SIZE -> { - log.debug { "Updating max header list size to $value for $this" } - generator.setMaxHeaderListSize(value) - } - else -> { - log.debug { "Unknown setting $key:$value for $this" } - } - } - } - - notifySettings(this, frame) - - if (reply) { - val replyFrame = SettingsFrame(emptyMap(), true) - settings(replyFrame, discard()) - } - } - - private fun notifySettings(connection: Http2Connection, frame: SettingsFrame) { - try { - listener.onSettings(connection, frame) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - - // window update - override fun onWindowUpdate(frame: WindowUpdateFrame) { - log.debug { "Received $frame" } - val streamId = frame.streamId - val windowDelta = frame.windowDelta - if (frame.isStreamWindowUpdate) { - val stream = getStream(streamId) - if (stream != null && stream is AsyncHttp2Stream) { - val streamSendWindow: Int = stream.updateSendWindow(0) - if (MathUtils.sumOverflows(streamSendWindow, windowDelta)) { - reset(ResetFrame(streamId, ErrorCode.FLOW_CONTROL_ERROR.code), discard()) - } else { - stream.process(frame, discard()) - onWindowUpdate(stream, frame) - } - } else { - if (!isRemoteStreamClosed(streamId)) { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_window_update_frame") - } - } - } else { - val sessionSendWindow = getSendWindow() - if (MathUtils.sumOverflows(sessionSendWindow, windowDelta)) { - onConnectionFailure(ErrorCode.FLOW_CONTROL_ERROR.code, "invalid_flow_control_window") - } else { - onWindowUpdate(null, frame) - } - } - } - - fun onWindowUpdate(stream: Stream?, frame: WindowUpdateFrame) { - flusher.onWindowUpdate(stream, frame) - } - - fun updateRecvWindow(delta: Int): Int { - return recvWindow.getAndAdd(delta) - } - - fun updateSendWindow(delta: Int): Int { - return sendWindow.getAndAdd(delta) - } - - fun getSendWindow(): Int { - return sendWindow.get() - } - - fun getRecvWindow(): Int { - return recvWindow.get() - } - - - // ping frame - override fun ping(frame: PingFrame, result: Consumer>) { - if (frame.isReply) { - result.accept(createFailedResult(IllegalArgumentException("The reply must be false"))) - } else { - sendControlFrame(null, frame) - .thenAccept { result.accept(SUCCESS) } - .exceptionallyAccept { result.accept(createFailedResult(it)) } - } - } - - override fun onPing(frame: PingFrame) { - log.debug { "Received $frame" } - if (frame.isReply) { - notifyPing(this, frame) - } else { - sendControlFrame(null, PingFrame(frame.payload, true)) - } - } - - private fun notifyPing(connection: Http2Connection, frame: PingFrame) { - try { - listener.onPing(connection, frame) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - - // close connection - override fun close(error: Int, reason: String, result: Consumer>): Boolean { - while (true) { - when (val current: CloseState = closeState.get()) { - CloseState.NOT_CLOSED -> { - if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSED)) { - val goAwayFrame = newGoAwayFrame(CloseState.LOCALLY_CLOSED, error, reason) - closeFrame = goAwayFrame - sendControlFrame(null, goAwayFrame) - .thenAccept { result.accept(SUCCESS) } - .exceptionallyAccept { result.accept(createFailedResult(it)) } - return true - } - } - else -> { - log.debug { "Ignoring close $error/$reason, already closed" } - result.accept(SUCCESS) - return false - } - } - } - } - - suspend fun close(error: Int, reason: String): Boolean { - val future = CompletableFuture() - val success = close(error, reason, futureToConsumer(future)) - future.await() - return success - } - - private fun terminate() { - terminateLoop@ while (true) { - when (val current = closeState.get()) { - CloseState.NOT_CLOSED, CloseState.LOCALLY_CLOSED, CloseState.REMOTELY_CLOSED -> { - if (closeState.compareAndSet(current, CloseState.CLOSED)) { - clearStreams() - disconnect() - break@terminateLoop - } - } - else -> { - // ignore the other close states - break@terminateLoop - } - } - } - } - - private fun disconnect() { - log.debug { "Disconnecting $this" } - tcpConnection.close() - } - - private fun clearStreams() { - log.debug { "HTTP2 connection terminated. id: $id, stream size: ${http2StreamMap.size}" } - http2StreamMap.values.map { it as AsyncHttp2Stream }.forEach { - it.notifyTerminal(it) - it.close() - } - http2StreamMap.clear() - } - - override fun closeAsync(): CompletableFuture { - val future = CompletableFuture() - close(ErrorCode.NO_ERROR.code, "no_error", futureToConsumer(future)) - return future - } - - override fun close() { - closeAsync() - } - - - // go away frame - override fun onGoAway(frame: GoAwayFrame) { - log.debug { "Received $frame" } - closeLoop@ while (true) { - when (val current: CloseState = closeState.get()) { - CloseState.NOT_CLOSED -> { - if (closeState.compareAndSet(current, CloseState.REMOTELY_CLOSED)) { - // We received a GO_AWAY, so try to write what's in the queue and then disconnect. - closeFrame = frame - notifyClose(this, frame) { - val goAwayFrame = newGoAwayFrame(CloseState.CLOSED, ErrorCode.NO_ERROR.code, null) - val disconnectFrame = DisconnectFrame() - sendControlFrame(null, goAwayFrame, disconnectFrame) - } - break@closeLoop - } - } - else -> { - log.debug { "Ignored $frame, already closed" } - break@closeLoop - } - } - } - } - - private fun notifyClose(connection: Http2Connection, frame: GoAwayFrame, consumer: Consumer>) { - try { - listener.onClose(connection, frame, consumer) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - private fun newGoAwayFrame(closeState: CloseState, error: Int, reason: String?): GoAwayFrame { - var payload: ByteArray? = null - if (reason != null) { // Trim the reason to avoid attack vectors. - payload = reason.substring(0, min(reason.length, 32)).toByteArray(StandardCharsets.UTF_8) - } - return GoAwayFrame(closeState, getLastRemoteStreamId(), error, payload) - } - - override fun onConnectionFailure(error: Int, reason: String) { - onConnectionFailure(error, reason, discard()) - } - - protected fun onConnectionFailure(error: Int, reason: String, result: Consumer>) { - notifyFailure(this, IOException(String.format("%d/%s", error, reason))) { close(error, reason, result) } - } - - private fun notifyFailure(connection: Http2Connection, throwable: Throwable, consumer: Consumer>) { - try { - listener.onFailure(connection, throwable, consumer) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - override fun toString(): String { - return java.lang.String.format( - "%s@%x{l:%s <-> r:%s,sendWindow=%s,recvWindow=%s,streams=%d,%s,%s}", - this::class.java.simpleName, - hashCode(), - tcpConnection.localAddress, - tcpConnection.remoteAddress, - sendWindow, - recvWindow, - streams.size, - closeState.get(), - closeFrame?.toString() - ) - } -} - -fun getAndIncreaseStreamId(id: AtomicInteger, initStreamId: Int) = id.getAndUpdate { prev -> - val currentId = prev + 2 - if (currentId <= 0) initStreamId else currentId -} - -sealed class FlushFrameMessage -class ControlFrameEntry(val stream: Stream?, val frames: Array, val result: Consumer>) : - FlushFrameMessage() - -class DataFrameEntry(val stream: Stream, val frame: DataFrame, val result: Consumer>) : - FlushFrameMessage() { - var dataRemaining = frame.remaining() - var writtenBytes: Long = 0 -} - -data class OnWindowUpdateMessage(val stream: Stream?, val frame: WindowUpdateFrame) : FlushFrameMessage() \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AsyncHttp2Stream.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AsyncHttp2Stream.kt deleted file mode 100644 index 9bd7400ca..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/AsyncHttp2Stream.kt +++ /dev/null @@ -1,411 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.exception.Http2StreamFrameProcessException -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v2.frame.* -import com.fireflysource.net.http.common.v2.frame.CloseState.* -import com.fireflysource.net.http.common.v2.frame.CloseState.Event.* -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import java.io.Closeable -import java.io.IOException -import java.time.Duration -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap -import java.util.concurrent.TimeoutException -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicReference -import java.util.function.Consumer - -class AsyncHttp2Stream( - private val asyncHttp2Connection: AsyncHttp2Connection, - private val id: Int, - private val local: Boolean, - var listener: Stream.Listener = defaultStreamListener -) : Stream, Closeable { - - companion object { - private val log = SystemLogger.create(AsyncHttp2Stream::class.java) - val defaultStreamListener = Stream.Listener.Adapter() - } - - private val attributes: ConcurrentMap by lazy { ConcurrentHashMap() } - - private val sendWindow = AtomicInteger() - private val recvWindow = AtomicInteger() - private val level = AtomicInteger() - - private val closeState = AtomicReference(NOT_CLOSED) - private var localReset = false - private var remoteReset = false - - private val createTime = System.currentTimeMillis() - private var lastActiveTime = createTime - private var idleTimeout: Long = 0 - private var idleCheckJob: Job? = null - - private var dataLength = Long.MIN_VALUE - - val stashedDataFrames = LinkedList() - - - override fun getId(): Int = id - - override fun getHttp2Connection(): Http2Connection = asyncHttp2Connection - - override fun getIdleTimeout(): Long = idleTimeout - - override fun setIdleTimeout(idleTimeout: Long) { - if (idleTimeout > 0) { - this.idleTimeout = idleTimeout - val job = idleCheckJob - idleCheckJob = if (job == null) { - launchIdleCheckJob() - } else { - job.cancel(CancellationException("Set the new idle timeout. id: $id")) - launchIdleCheckJob() - } - } - } - - private fun noIdle() { - lastActiveTime = System.currentTimeMillis() - } - - private fun launchIdleCheckJob() = asyncHttp2Connection.coroutineScope.launch { - while (true) { - val timeout = Duration.ofSeconds(idleTimeout).toMillis() - val delayTime = (timeout - getIdleTime()).coerceAtLeast(0) - log.debug { "Stream idle check delay: $delayTime" } - if (delayTime > 0) { - delay(delayTime) - } - val idle = getIdleTime() - if (idle >= timeout) { - notifyIdleTimeout() - break - } - } - } - - private fun getIdleTime() = System.currentTimeMillis() - lastActiveTime - - private fun notifyIdleTimeout() { - try { - val reset = listener.onIdleTimeout(this, TimeoutException("Stream idle timeout")) - if (reset) { - val frame = ResetFrame(id, ErrorCode.CANCEL_STREAM_ERROR.code) - reset(frame, discard()) - } - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - override fun getAttribute(key: String): Any? = attributes[key] - - override fun setAttribute(key: String, value: Any) { - attributes[key] = value - } - - override fun removeAttribute(key: String): Any? = attributes.remove(key) - - - fun process(frame: Frame, result: Consumer>) { - when (frame.type) { - FrameType.HEADERS -> onHeaders(frame as HeadersFrame, result) - FrameType.DATA -> onData(frame as DataFrame, result) - FrameType.RST_STREAM -> onReset(frame as ResetFrame, result) - FrameType.PUSH_PROMISE -> { - // They are closed when receiving an end-stream DATA frame. - // Pushed streams implicitly locally closed. - // They are closed when receiving an end-stream DATA frame. - updateClose(true, AFTER_SEND) - result.accept(Result.SUCCESS) - } - FrameType.WINDOW_UPDATE -> result.accept(Result.SUCCESS) - FrameType.FAILURE -> notifyFailure(this, frame as FailureFrame, result) - else -> throw Http2StreamFrameProcessException("Process frame type error. ${frame.type}") - } - } - - // header frame - override fun headers(frame: HeadersFrame, result: Consumer>) { - try { - noIdle() - Assert.isTrue(frame.streamId == id, "The headers frame id must equal the stream id") - sendControlFrame(frame, result) - } catch (e: Exception) { - result.accept(Result.createFailedResult(e)) - } - } - - private fun onHeaders(frame: HeadersFrame, result: Consumer>) { - noIdle() - val metaData: MetaData = frame.metaData - if (metaData.isRequest || metaData.isResponse) { - val fields = metaData.fields - var length: Long = -1 - if (fields != null) { - length = fields.getLongField(HttpHeader.CONTENT_LENGTH.value) - } - dataLength = if (length >= 0) length else Long.MIN_VALUE - } - if (updateClose(frame.isEndStream, RECEIVED)) { - asyncHttp2Connection.removeStream(this) - } - result.accept(Result.SUCCESS) - } - - - // push promise frame - override fun push(frame: PushPromiseFrame, promise: Consumer>, listener: Stream.Listener) { - noIdle() - asyncHttp2Connection.push(frame, promise, listener) - } - - - // data frame - override fun data(frame: DataFrame, result: Consumer>) { - noIdle() - asyncHttp2Connection.sendDataFrame(this, frame) - .thenAccept { result.accept(Result.SUCCESS) } - .exceptionallyAccept { result.accept(Result.createFailedResult(it)) } - } - - private fun onData(frame: DataFrame, result: Consumer>) { - noIdle() - if (getRecvWindow() < 0) { - // It's a bad client, it does not deserve to be treated gently by just resetting the stream. - asyncHttp2Connection.close(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded", discard()) - result.accept(Result.createFailedResult(IOException("stream_window_exceeded"))) - return - } - - // SPEC: remotely closed streams must be replied with a reset. - if (isRemotelyClosed()) { - reset(ResetFrame(id, ErrorCode.STREAM_CLOSED_ERROR.code), discard()) - result.accept(Result.createFailedResult(IOException("stream_closed"))) - return - } - - if (isReset) { // Just drop the frame. - result.accept(Result.createFailedResult(IOException("stream_reset"))) - return - } - - if (dataLength != Long.MIN_VALUE) { - dataLength -= frame.remaining() - if (frame.isEndStream && dataLength != 0L) { - reset(ResetFrame(id, ErrorCode.PROTOCOL_ERROR.code), discard()) - result.accept(Result.createFailedResult(IOException("invalid_data_length"))) - return - } - } - - if (updateClose(frame.isEndStream, RECEIVED)) { - asyncHttp2Connection.removeStream(this) - } - - notifyData(this, frame, result) - } - - fun isRemotelyClosed(): Boolean { - val state = closeState.get() - return state === REMOTELY_CLOSED || state === CLOSING - } - - private fun notifyData(stream: Stream, frame: DataFrame, result: Consumer>) { - try { - listener.onData(stream, frame, result) - } catch (e: Throwable) { - log.error(e) { "Failure while notifying listener $listener" } - result.accept(Result.createFailedResult(e)) - } - } - - - // window update - fun updateSendWindow(delta: Int): Int = sendWindow.getAndAdd(delta) - - fun getSendWindow(): Int = sendWindow.get() - - fun updateRecvWindow(delta: Int): Int = recvWindow.getAndAdd(delta) - - fun getRecvWindow(): Int = recvWindow.get() - - fun addAndGetLevel(delta: Int): Int = level.addAndGet(delta) - - fun setLevel(level: Int) { - this.level.set(level) - } - - - // reset frame - override fun reset(frame: ResetFrame, result: Consumer>) { - if (isReset) { - result.accept(Result.createFailedResult(IllegalStateException("The stream: $id is reset"))) - return - } - localReset = true - sendControlFrame(frame, result) - } - - override fun isReset(): Boolean { - return localReset || remoteReset - } - - private fun onReset(frame: ResetFrame, result: Consumer>) { - remoteReset = true - close() - asyncHttp2Connection.removeStream(this) - notifyReset(this, frame, result) - } - - private fun notifyReset(stream: Stream, frame: ResetFrame, result: Consumer>) { - try { - listener.onReset(stream, frame, result) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - } - } - - private fun notifyFailure(stream: Stream, frame: FailureFrame, result: Consumer>) { - try { - listener.onFailure(stream, frame.error, frame.reason, result) - } catch (e: Exception) { - log.error(e) { "failure while notifying listener" } - result.accept(Result.createFailedResult(e)) - } - } - - - private fun sendControlFrame(frame: Frame, result: Consumer>) { - asyncHttp2Connection.sendControlFrame(this, frame) - .thenAccept { result.accept(Result.SUCCESS) } - .exceptionallyAccept { result.accept(Result.createFailedResult(it)) } - } - - - // close frame - override fun close() { - val oldState = closeState.getAndSet(CLOSED) - if (oldState != CLOSED) { - idleCheckJob?.cancel(CancellationException("The stream closed. id: $id")) - val deltaClosing = if (oldState == CLOSING) -1 else 0 - asyncHttp2Connection.updateStreamCount(local, -1, deltaClosing) - notifyClosed(this) - } - } - - fun updateClose(update: Boolean, event: Event): Boolean { - log.debug { "Update close for $this update=$update event=$event" } - - if (!update) { - return false - } - - return when (event) { - RECEIVED -> updateCloseAfterReceived() - BEFORE_SEND -> updateCloseBeforeSend() - AFTER_SEND -> updateCloseAfterSend() - } - } - - override fun isClosed(): Boolean { - return closeState.get() == CLOSED - } - - private fun updateCloseAfterReceived(): Boolean { - while (true) { - when (val current = closeState.get()) { - NOT_CLOSED -> if (closeState.compareAndSet(current, REMOTELY_CLOSED)) { - return false - } - LOCALLY_CLOSING -> { - if (closeState.compareAndSet(current, CLOSING)) { - asyncHttp2Connection.updateStreamCount(local, 0, 1) - return false - } - } - LOCALLY_CLOSED -> { - close() - return true - } - else -> return false - } - } - } - - private fun updateCloseBeforeSend(): Boolean { - while (true) { - when (val current = closeState.get()) { - NOT_CLOSED -> if (closeState.compareAndSet(current, LOCALLY_CLOSING)) { - return false - } - REMOTELY_CLOSED -> if (closeState.compareAndSet(current, CLOSING)) { - asyncHttp2Connection.updateStreamCount(local, 0, 1) - return false - } - else -> return false - } - } - } - - private fun updateCloseAfterSend(): Boolean { - while (true) { - when (val current = closeState.get()) { - NOT_CLOSED, LOCALLY_CLOSING -> if (closeState.compareAndSet(current, LOCALLY_CLOSED)) { - return false - } - REMOTELY_CLOSED, CLOSING -> { - close() - return true - } - else -> return false - } - } - } - - private fun notifyClosed(stream: Stream) { - try { - listener.onClosed(stream) - } catch (x: Throwable) { - log.info("Failure while notifying listener $listener", x) - } - } - - fun notifyTerminal(stream: Stream) { - try { - listener.onTerminal(stream) - } catch (x: Throwable) { - log.info("Failure while notifying listener $listener", x) - } - } - - - override fun toString(): String { - return String.format( - "%s@%x#%d{sendWindow=%s,recvWindow=%s,local=%b,reset=%b/%b,%s,age=%d}", - "AsyncHttp2Stream", - hashCode(), - getId(), - sendWindow, - recvWindow, - local, - localReset, - remoteReset, - closeState, - (System.currentTimeMillis() - createTime) - ) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/BufferedFlowControlStrategy.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/BufferedFlowControlStrategy.kt deleted file mode 100644 index 00bf579fd..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/BufferedFlowControlStrategy.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream - -import com.fireflysource.common.concurrent.Atomics -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.v2.frame.WindowUpdateFrame -import java.util.concurrent.atomic.AtomicInteger - -class BufferedFlowControlStrategy( - private val ratio: Float = 0.5f, - initialStreamRecvWindow: Int = HttpConfig.DEFAULT_WINDOW_SIZE -) : AbstractFlowControlStrategy(initialStreamRecvWindow) { - - companion object { - private val log = SystemLogger.create(BufferedFlowControlStrategy::class.java) - } - - private val maxConnectionRecvWindow = AtomicInteger(HttpConfig.DEFAULT_WINDOW_SIZE) - private val connectionLevel = AtomicInteger() - - override fun onDataConsumed(http2Connection: Http2Connection, stream: Stream?, length: Int) { - if (length <= 0) return - - val connection = http2Connection as AsyncHttp2Connection - val level = connectionLevel.addAndGet(length) - val maxLevel = (maxConnectionRecvWindow.get() * ratio).toInt() - if (level >= maxLevel) { - if (connectionLevel.compareAndSet(level, 0)) { - connection.updateRecvWindow(level) - log.debug { "Data consumed, $length bytes, updated session recv window by $level/$maxLevel for $http2Connection" } - connection.sendControlFrame(null, WindowUpdateFrame(0, level)) - } else { - log.debug { "Data consumed, $length bytes, concurrent session recv window level $level/$maxLevel for $http2Connection" } - } - } else { - log.debug { "Data consumed, $length bytes, session recv window level $level/$maxLevel for $http2Connection" } - } - - if (stream != null && stream is AsyncHttp2Stream) { - if (stream.isRemotelyClosed()) { - log.debug { "Data consumed, $length bytes, ignoring update stream recv window for remotely closed $stream" } - } else { - val streamLevel = stream.addAndGetLevel(length) - val maxStreamLevel = (initialStreamRecvWindow * ratio).toInt() - if (streamLevel >= maxStreamLevel) { - stream.setLevel(0) - stream.updateRecvWindow(streamLevel) - log.debug { "Data consumed, $length bytes, updated stream recv window by $streamLevel/$maxStreamLevel for $stream" } - connection.sendControlFrame(stream, WindowUpdateFrame(stream.getId(), streamLevel)) - } else { - log.debug { "Data consumed, $length bytes, stream recv window level $streamLevel/$maxStreamLevel for $stream" } - } - } - } - } - - override fun windowUpdate(http2Connection: Http2Connection, stream: Stream?, frame: WindowUpdateFrame) { - super.windowUpdate(http2Connection, stream, frame) - if (frame.streamId == 0) { - val connection = http2Connection as AsyncHttp2Connection - val recvWindow = connection.getRecvWindow() - Atomics.updateMax(maxConnectionRecvWindow, recvWindow) - } - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/SimpleFlowControlStrategy.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/SimpleFlowControlStrategy.kt deleted file mode 100644 index f1fe801bc..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/common/v2/stream/SimpleFlowControlStrategy.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.fireflysource.net.http.common.v2.stream - -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.v2.frame.WindowUpdateFrame - -class SimpleFlowControlStrategy( - initialStreamRecvWindow: Int = HttpConfig.DEFAULT_WINDOW_SIZE -) : AbstractFlowControlStrategy(initialStreamRecvWindow) { - - companion object { - private val log = SystemLogger.create(SimpleFlowControlStrategy::class.java) - } - - override fun onDataConsumed(http2Connection: Http2Connection, stream: Stream?, length: Int) { - log.debug { "Data consumed. length: $length, id: ${http2Connection.id}, stream: $stream" } - if (length <= 0) return - - // This is the simple algorithm for flow control. - // This method called when a whole flow controlled frame has been consumed. - // We send a WindowUpdate every time, even if the frame was very small. - val connection = http2Connection as AsyncHttp2Connection - val sessionFrame = WindowUpdateFrame(0, length) - connection.updateRecvWindow(length) - log.debug { "Data consumed, increased session recv window by $length for $connection" } - - var streamFrame: WindowUpdateFrame? = null - if (stream != null && stream is AsyncHttp2Stream) { - if (stream.isRemotelyClosed()) { - log.debug { "Data consumed, ignoring update stream recv window by $length for remotely closed $stream" } - } else { - streamFrame = WindowUpdateFrame(stream.id, length) - stream.updateRecvWindow(length) - log.debug { "Data consumed, increased stream recv window by $length for $stream" } - } - } - if (streamFrame != null) { - connection.sendControlFrame(stream, sessionFrame, streamFrame) - } else { - connection.sendControlFrame(stream, sessionFrame) - } - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AbstractHttpServerResponse.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AbstractHttpServerResponse.kt deleted file mode 100644 index e8e95fd72..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AbstractHttpServerResponse.kt +++ /dev/null @@ -1,192 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.io.useAwait -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.codec.CookieGenerator -import com.fireflysource.net.http.common.exception.HttpServerResponseNotCommitException -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.http.server.HttpServerContentProvider -import com.fireflysource.net.http.server.HttpServerOutputChannel -import com.fireflysource.net.http.server.HttpServerResponse -import kotlinx.coroutines.future.asCompletableFuture -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean -import java.util.function.Supplier - -abstract class AbstractHttpServerResponse(private val httpServerConnection: HttpServerConnection) : HttpServerResponse { - - companion object { - const val contentProviderBufferSize = 8 * 1024 - } - - val response: MetaData.Response = MetaData.Response(HttpVersion.HTTP_1_1, HttpStatus.OK_200, HttpFields()) - private var contentProvider: HttpServerContentProvider? = null - private var cookieList: List? = null - private val committed = AtomicBoolean(false) - private val callCommit = AtomicBoolean(false) - private var serverOutputChannel: HttpServerOutputChannel? = null - private val mutex = Mutex() - - override fun getStatus(): Int = response.status - - override fun setStatus(status: Int) { - response.status = status - } - - override fun getReason(): String = response.reason - - override fun setReason(reason: String) { - response.reason = reason - } - - override fun getHttpVersion(): HttpVersion = response.httpVersion - - override fun setHttpVersion(httpVersion: HttpVersion) { - response.httpVersion = httpVersion - } - - override fun getHttpFields(): HttpFields = response.fields - - override fun setHttpFields(httpFields: HttpFields) { - response.fields.clear() - response.fields.addAll(httpFields) - } - - override fun getCookies(): List = cookieList ?: listOf() - - override fun setCookies(cookies: List) { - cookieList = cookies - } - - override fun getContentProvider(): HttpServerContentProvider? = contentProvider - - override fun setContentProvider(contentProvider: HttpServerContentProvider) { - Assert.state(!isCommitted, "Set content provider must before commit response.") - this.contentProvider = contentProvider - } - - override fun getTrailerSupplier(): Supplier = response.trailerSupplier - - override fun setTrailerSupplier(supplier: Supplier) { - response.trailerSupplier = supplier - } - - override fun isCommitted(): Boolean = callCommit.get() - - override fun commit(): CompletableFuture { - callCommit.set(true) - return httpServerConnection.coroutineScope - .launch { commitAwait() } - .asCompletableFuture() - .thenCompose { Result.DONE } - } - - private suspend fun commitAwait() { - if (committed.get()) return - - mutex.withLock { - if (committed.get()) return@commitAwait - - createOutputChannelAndCommit() - committed.set(true) - } - } - - private suspend fun createOutputChannelAndCommit() { - if (response.fields[HttpHeader.CONNECTION] == null && httpVersion == HttpVersion.HTTP_1_1) { - response.fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE) - } - - cookies.map { CookieGenerator.generateSetCookie(it) } - .forEach { response.fields.add(HttpHeader.SET_COOKIE, it) } - - val provider = contentProvider - if (provider != null && provider.length() >= 0) { - response.fields.put(HttpHeader.CONTENT_LENGTH, provider.length().toString()) - } - - val contentEncoding = Optional - .ofNullable(response.fields[HttpHeader.CONTENT_ENCODING]) - .flatMap { ContentEncoding.from(it) } - - val output = if (contentEncoding.isPresent) { - val out = createHttpServerOutputChannel(response) - CompressedServerOutputChannel(out, contentEncoding.get()) - } else createHttpServerOutputChannel(response) - - if (provider != null) { - output.useAwait { - it.commit().await() - writeContent(provider, it) - } - } else { - output.commit().await() - } - this.serverOutputChannel = output - } - - /** - * Create the HTTP server output channel. It outputs the HTTP response. - * - * @return The HTTP server output channel. - */ - abstract fun createHttpServerOutputChannel(response: MetaData.Response): HttpServerOutputChannel - - private suspend fun writeContent(provider: HttpServerContentProvider, outputChannel: HttpServerOutputChannel) { - val size = provider.getContentProviderBufferSize() - writeLoop@ while (true) { - val buffer = BufferUtils.allocate(size) - val position = buffer.flipToFill() - val length = provider.read(buffer).await() - buffer.flipToFlush(position) - when { - length > 0 -> outputChannel.write(buffer).await() - length < 0 -> break@writeLoop - } - } - provider.closeAsync().await() - } - - private fun HttpServerContentProvider.getContentProviderBufferSize(): Int { - return if (this.length() > 0) this.length().coerceAtMost(contentProviderBufferSize.toLong()).toInt() - else contentProviderBufferSize - } - - override fun getOutputChannel(): HttpServerOutputChannel { - val outputChannel = serverOutputChannel - if (outputChannel == null) { - throw HttpServerResponseNotCommitException("The response not commit") - } else { - Assert.state( - contentProvider == null, - "The content provider is not null. The server has used content provider to output content." - ) - return outputChannel - } - } - - override fun closeAsync(): CompletableFuture { - val provider = contentProvider - return if (provider == null) { - httpServerConnection.coroutineScope.launch { - commit().await() - outputChannel.closeAsync().await() - }.asVoidFuture() - } else Result.DONE - } - - override fun close() { - closeAsync() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpProxy.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpProxy.kt deleted file mode 100644 index 187f37fd4..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpProxy.kt +++ /dev/null @@ -1,249 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.deleteIfExistsAsync -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.* -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpHeaderValue -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.server.* -import com.fireflysource.net.http.server.impl.content.handler.ByteBufferContentHandler -import com.fireflysource.net.http.server.impl.content.handler.FileContentHandler -import com.fireflysource.net.http.server.impl.router.asyncHandler -import com.fireflysource.net.tcp.TcpClientFactory -import com.fireflysource.net.tcp.TcpConnection -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.future.asCompletableFuture -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.net.InetSocketAddress -import java.net.SocketAddress -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.StandardOpenOption -import java.util.* -import java.util.concurrent.CompletableFuture - -class AsyncHttpProxy(httpConfig: HttpConfig = HttpConfig()) : AbstractLifeCycle(), HttpProxy { - - companion object { - private val log = SystemLogger.create(AsyncHttpProxy::class.java) - private val tempPath = System.getProperty("java.io.tmpdir") - private const val httpBodyDir = "com.fireflysource.http.proxy" - private const val httpProxyServerContentPathKey = "httpProxyServerContentPath" - private const val httpProxyClientContentPathKey = "httpProxyClientContentPath" - } - - private val server = HttpServerFactory.create(httpConfig) - private val tcpClient = TcpClientFactory.create() - private val httpClient = HttpClientFactory.create(httpConfig) - private val httpProxyBodySizeThreshold = httpConfig.httpProxyBodySizeThreshold - - init { - server - .onAcceptHttpTunnel { request -> - log.info("Accept http tunnel handshake. uri: ${request.uri}") - CompletableFuture.completedFuture(true) - } - .onHttpTunnelHandshakeComplete { connection, targetAddress -> - log.info("HTTP tunnel handshake success. target: $targetAddress") - connection.coroutineScope.launch { buildHttpTunnel(this, connection, targetAddress) }.asVoidFuture() - } - .onHeaderComplete { ctx -> - if (ctx.expect100Continue()) { - ctx.response100Continue() - } else if (ctx.method == HttpMethod.CONNECT.value) { - Result.DONE - } else { - setServerContentHandler(ctx) - Result.DONE - } - } - .router().path("*").asyncHandler { buildNonSecureHttpHandler(it) } - val tempDir = Paths.get(tempPath, httpBodyDir) - if (!Files.exists(tempDir)) { - Files.createDirectory(tempDir) - } - log.info("HTTP proxy content temp file path: $tempDir") - start() - } - - private suspend fun buildNonSecureHttpHandler(ctx: RoutingContext) { - try { - val response = httpClient.request(ctx.method, ctx.uri) - .addAll(ctx.httpFields) - .contentProvider(createClientContentProvider(ctx.request.contentHandler)) - .onHeaderComplete { request, response -> setClientContentHandler(request, response, ctx) } - .submit().await() - - ctx.setStatus(response.status) - .addAll(response.httpFields) - .contentProvider(createServerContentProvider(response.contentHandler)) - .end().await() - } finally { - deleteContentTempFile(ctx) - } - } - - private fun setClientContentHandler( - request: HttpClientRequest, - response: HttpClientResponse, - ctx: RoutingContext - ) { - val clientBodyContentHandler = createClientContentHandler(response, ctx) - request.contentHandler = clientBodyContentHandler - response.contentHandler = clientBodyContentHandler - } - - private fun createClientContentHandler( - response: HttpClientResponse, - ctx: RoutingContext - ): HttpClientContentHandler { - fun createFileHandler(): HttpClientContentHandler { - val path = - Paths.get(tempPath, httpBodyDir, "client-body-${UUID.randomUUID()}") - ctx.setAttribute(httpProxyClientContentPathKey, path) - return HttpClientContentHandlerFactory.fileHandler( - path, - StandardOpenOption.CREATE_NEW, - StandardOpenOption.READ, - StandardOpenOption.WRITE - ) - } - return if (response.contentLength > httpProxyBodySizeThreshold) { - createFileHandler() - } else if (response.httpFields.contains(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED.value)) { - createFileHandler() - } else { - HttpClientContentHandlerFactory.bytesHandler(httpProxyBodySizeThreshold) - } - } - - private fun createClientContentProvider(serverContentHandler: HttpServerContentHandler) = - when (serverContentHandler) { - is FileContentHandler -> HttpClientContentProviderFactory.fileBody( - serverContentHandler.path, - StandardOpenOption.READ - ) - is ByteBufferContentHandler -> HttpClientContentProviderFactory.bytesBody( - BufferUtils.merge(serverContentHandler.getByteBuffers()) - ) - else -> throw IllegalStateException("The HTTP proxy content handler type error.") - } - - private fun createServerContentProvider(clientContentHandler: HttpClientContentHandler) = - when (clientContentHandler) { - is com.fireflysource.net.http.client.impl.content.handler.FileContentHandler -> HttpServerContentProviderFactory.fileBody( - clientContentHandler.path, - StandardOpenOption.READ - ) - is com.fireflysource.net.http.client.impl.content.handler.ByteBufferContentHandler -> HttpServerContentProviderFactory.bytesBody( - BufferUtils.merge(clientContentHandler.getByteBuffers()) - ) - else -> throw IllegalStateException("The HTTP client response content handler type error.") - } - - - private fun setServerContentHandler(ctx: RoutingContext) { - fun setFileHandler() { - val path = - Paths.get(tempPath, httpBodyDir, "server-body-${UUID.randomUUID()}") - val handler = HttpServerContentHandlerFactory.fileHandler( - path, - StandardOpenOption.CREATE_NEW, - StandardOpenOption.READ, - StandardOpenOption.WRITE - ) - ctx.setAttribute(httpProxyServerContentPathKey, path) - ctx.contentHandler(handler) - } - - fun setBytesHandler() { - ctx.contentHandler(HttpServerContentHandlerFactory.bytesHandler(httpProxyBodySizeThreshold)) - } - - if (ctx.contentLength > httpProxyBodySizeThreshold) { - setFileHandler() - } else if (ctx.httpFields.contains(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED.value)) { - setFileHandler() - } else { - setBytesHandler() - } - } - - private fun deleteContentTempFile(ctx: RoutingContext) { - ctx.getAttribute(httpProxyServerContentPathKey)?.let { - deleteIfExistsAsync(it as Path).asCompletableFuture() - .thenAccept { success -> log.debug("delete server body temp file. $success") } - .exceptionallyAccept { e -> log.error(e) { "delete server body temp file exception." } } - } - ctx.getAttribute(httpProxyClientContentPathKey)?.let { - deleteIfExistsAsync(it as Path).asCompletableFuture() - .thenAccept { success -> log.debug("delete client body temp file. $success") } - .exceptionallyAccept { e -> log.error(e) { "delete client body temp file exception." } } - } - } - - private suspend fun buildHttpTunnel( - coroutineScope: CoroutineScope, - connection: TcpConnection, - targetAddress: InetSocketAddress - ) { - val targetConnection = tcpClient.connect(targetAddress).await() - val readFromClientJob = coroutineScope.launch { - while (true) { - val r = this.runCatching { - val data = connection.read().await() - val size = targetConnection.write(data).await() - log.debug("write to target: $size") - } - if (r.isFailure) { - log.error("read from client job failure", r.exceptionOrNull()) - break - } - } - } - val writeToClientJob = coroutineScope.launch { - while (true) { - val r = this.runCatching { - val data = targetConnection.read().await() - val size = connection.write(data).await() - connection.flush().await() - log.debug("write to client: $size") - } - if (r.isFailure) { - log.error("write to client job failure", r.exceptionOrNull()) - break - } - } - } - coroutineScope.runCatching { - readFromClientJob.join() - log.info("HTTP tunnel read job exit.") - writeToClientJob.join() - log.info("HTTP tunnel write job exit.") - } - targetConnection.closeAsync().await() - connection.closeAsync().await() - } - - override fun listen(address: SocketAddress) { - server.listen(address) - } - - override fun init() { - } - - override fun destroy() { - server.stop() - tcpClient.stop() - httpClient.stop() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServer.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServer.kt deleted file mode 100644 index c19d13999..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServer.kt +++ /dev/null @@ -1,282 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.ProjectVersion -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpHeaderValue -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.* -import com.fireflysource.net.http.server.impl.content.provider.DefaultContentProvider -import com.fireflysource.net.http.server.impl.exception.ProxyAuthException -import com.fireflysource.net.http.server.impl.exception.RouterNotCommitException -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpServer -import com.fireflysource.net.tcp.aio.AioTcpServer -import com.fireflysource.net.tcp.aio.ApplicationProtocol.HTTP1 -import com.fireflysource.net.tcp.aio.ApplicationProtocol.HTTP2 -import com.fireflysource.net.tcp.secure.SecureEngineFactory -import com.fireflysource.net.websocket.server.WebSocketManager -import com.fireflysource.net.websocket.server.WebSocketServerConnectionBuilder -import com.fireflysource.net.websocket.server.impl.AsyncWebSocketManager -import com.fireflysource.net.websocket.server.impl.AsyncWebSocketServerConnectionBuilder -import java.net.InetSocketAddress -import java.net.SocketAddress -import java.util.concurrent.CompletableFuture -import java.util.function.BiFunction -import java.util.function.Function -import kotlin.system.measureTimeMillis - -class AsyncHttpServer(val config: HttpConfig = HttpConfig()) : HttpServer, AbstractLifeCycle() { - - companion object { - private val log = SystemLogger.create(AsyncHttpServer::class.java) - } - - private var routerManager: RouterManager = AsyncRouterManager(this) - private var webSocketManager: WebSocketManager = AsyncWebSocketManager() - private var tcpServer: TcpServer = AioTcpServer() - private var address: SocketAddress? = null - private var onHeaderComplete: Function> = Function { ctx -> - if (ctx.expect100Continue()) ctx.response100Continue() else Result.DONE - } - private var onException: BiFunction> = BiFunction { ctx, e -> - if (ctx != null && !ctx.response.isCommitted) { - if (e is BadMessageException) { - ctx.setStatus(e.code) - .put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE) - .contentProvider(DefaultContentProvider(e.code, e, ctx)) - .end() - .thenCompose { ctx.connection.closeAsync() } - } else { - ctx.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500) - .setReason(HttpStatus.Code.INTERNAL_SERVER_ERROR.message) - .contentProvider(DefaultContentProvider(HttpStatus.INTERNAL_SERVER_ERROR_500, e, ctx)) - .end() - } - } else Result.DONE - } - private var onRouterNotFound: Function> = Function { ctx -> - ctx.setStatus(HttpStatus.NOT_FOUND_404) - .setReason(HttpStatus.Code.NOT_FOUND.message) - .contentProvider(DefaultContentProvider(HttpStatus.NOT_FOUND_404, null, ctx)) - .end() - } - private var onRouterComplete: Function> = Function { ctx -> - if (ctx.response.isCommitted) Result.DONE - else ctx.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500) - .setReason(HttpStatus.Code.INTERNAL_SERVER_ERROR.message) - .contentProvider( - DefaultContentProvider( - HttpStatus.INTERNAL_SERVER_ERROR_500, - RouterNotCommitException("The response does not commit"), - ctx - ) - ) - .end() - } - private var onAcceptHttpTunnel: Function> = Function { - CompletableFuture.completedFuture(false) - } - private var onHttpTunnelHandshakeComplete: BiFunction> = BiFunction { connection, _ -> - connection.closeAsync() - } - private var onAcceptHttpTunnelHandshakeResponse: Function> = - Function { ctx -> ctx.response200ConnectionEstablished() } - private var onRefuseHttpTunnelHandshakeResponse: Function> = - Function { ctx -> - ctx.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407) - .setReason(HttpStatus.Code.PROXY_AUTHENTICATION_REQUIRED.message) - .put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE) - .contentProvider( - DefaultContentProvider( - HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407, - ProxyAuthException("The proxy authentication must be required"), - ctx - ) - ) - .end() - .thenCompose { ctx.connection.closeAsync() } - } - - - override fun router(): Router = routerManager.register() - - override fun router(id: Int): Router = routerManager.register(id) - - override fun websocket(): WebSocketServerConnectionBuilder { - return AsyncWebSocketServerConnectionBuilder(this, webSocketManager) - } - - override fun websocket(path: String): WebSocketServerConnectionBuilder { - return AsyncWebSocketServerConnectionBuilder(this, webSocketManager).url(path) - } - - override fun onHeaderComplete(function: Function>): HttpServer { - this.onHeaderComplete = function - return this - } - - override fun onException(biFunction: BiFunction>): HttpServer { - this.onException = biFunction - return this - } - - override fun onRouterComplete(function: Function>): HttpServer { - this.onRouterComplete = function - return this - } - - override fun onRouterNotFound(function: Function>): HttpServer { - this.onRouterNotFound = function - return this - } - - override fun onAcceptHttpTunnel(function: Function>): HttpServer { - this.onAcceptHttpTunnel = function - return this - } - - override fun onAcceptHttpTunnelHandshakeResponse(function: Function>): HttpServer { - this.onAcceptHttpTunnelHandshakeResponse = function - return this - } - - override fun onRefuseHttpTunnelHandshakeResponse(function: Function>): HttpServer { - this.onRefuseHttpTunnelHandshakeResponse = function - return this - } - - override fun onHttpTunnelHandshakeComplete(function: BiFunction>): HttpServer { - this.onHttpTunnelHandshakeComplete = function - return this - } - - override fun timeout(timeout: Long): HttpServer { - tcpServer.timeout(timeout) - return this - } - - override fun secureEngineFactory(secureEngineFactory: SecureEngineFactory): HttpServer { - tcpServer.secureEngineFactory(secureEngineFactory) - return this - } - - override fun peerHost(peerHost: String): HttpServer { - tcpServer.peerHost(peerHost) - return this - } - - override fun peerPort(peerPort: Int): HttpServer { - tcpServer.peerPort(peerPort) - return this - } - - override fun supportedProtocols(supportedProtocols: MutableList): HttpServer { - tcpServer.supportedProtocols(supportedProtocols) - return this - } - - override fun enableSecureConnection(): HttpServer { - tcpServer.enableSecureConnection() - return this - } - - override fun listen(address: SocketAddress) { - this.address = address - start() - } - - override fun init() { - val time = measureTimeMillis { startupHttpServer() } - log.info(ProjectVersion.logo()) - log.info("Started Firefly HTTP server in {}ms. Address: {}", time, this.address) - } - - private fun startupHttpServer() { - require(config.maxRequestBodySize >= config.maxUploadFileSize) { "The max request size must be greater than the max file size." } - require(config.maxUploadFileSize >= config.uploadFileSizeThreshold) { "The max file size must be greater than the file size threshold." } - - val address = this.address - requireNotNull(address) - - if (config.tcpChannelGroup != null) { - tcpServer.tcpChannelGroup(config.tcpChannelGroup) - } - - tcpServer.stopTcpChannelGroup(config.isStopTcpChannelGroup) - - if (config.secureEngineFactory != null) { - tcpServer.secureEngineFactory(config.secureEngineFactory) - } - - val listener = AsyncHttpServerConnectionListener( - routerManager, - onHeaderComplete, - onException, - onRouterNotFound, - onRouterComplete, - webSocketManager, - onAcceptHttpTunnel, - onAcceptHttpTunnelHandshakeResponse, - onRefuseHttpTunnelHandshakeResponse, - onHttpTunnelHandshakeComplete - ) - - tcpServer.onAccept { connection -> - if (connection.isSecureConnection) { - connection.beginHandshake().thenAccept { protocol -> - when (protocol) { - HTTP2.value -> createHttp2Connection(connection, listener) - HTTP1.value -> createHttp1Connection(connection, listener) - else -> createHttp1Connection(connection, listener) - } - }.exceptionallyAccept { e -> - log.error(e) { "TLS handshake exception. id: ${connection.id}" } - connection.close() - } - } else createHttp1Connection(connection, listener) - } - - tcpServer.enableOutputBuffer().listen(address) - } - - private fun createHttp2Connection(connection: TcpConnection, listener: HttpServerConnection.Listener.Adapter) { - val http2Connection = Http2ServerConnection(config, connection) - http2Connection.setListener(listener).begin() - } - - private fun createHttp1Connection(connection: TcpConnection, listener: HttpServerConnection.Listener.Adapter) { - val http1Connection = Http1ServerConnection(config, connection) - http1Connection.setListener(listener).begin() - } - - override fun destroy() { - tcpServer.stop() - } - - override fun clone(): HttpServer { - val server = AsyncHttpServer(this.config.clone()) - server.routerManager = (this.routerManager as AsyncRouterManager).copy(server) - server.webSocketManager = (this.webSocketManager as AsyncWebSocketManager).clone() - server.tcpServer = (this.tcpServer as AioTcpServer).clone() - server.onHeaderComplete = this.onHeaderComplete - server.onException = this.onException - server.onRouterNotFound = this.onRouterNotFound - server.onRouterComplete = this.onRouterComplete - server.onAcceptHttpTunnel = this.onAcceptHttpTunnel - server.onHttpTunnelHandshakeComplete = this.onHttpTunnelHandshakeComplete - server.onAcceptHttpTunnelHandshakeResponse = this.onAcceptHttpTunnelHandshakeResponse - server.onRefuseHttpTunnelHandshakeResponse = this.onRefuseHttpTunnelHandshakeResponse - return server - } - - override fun copy(): HttpServer { - return this.clone() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServerConnectionListener.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServerConnectionListener.kt deleted file mode 100644 index 01dc467e0..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServerConnectionListener.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.annotation.NoArg -import com.fireflysource.common.concurrent.exceptionallyCompose -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.codec.URIUtils -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.http.server.HttpServerRequest -import com.fireflysource.net.http.server.RouterManager -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRoutingContext -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.websocket.server.WebSocketManager -import com.fireflysource.net.websocket.server.WebSocketServerConnectionHandler -import java.net.InetSocketAddress -import java.util.concurrent.CompletableFuture -import java.util.function.BiFunction -import java.util.function.Function - -/** - * @author Pengtao Qiu - */ -@NoArg -class AsyncHttpServerConnectionListener( - private val routerManager: RouterManager, - private val onHeaderComplete: Function>, - private val onException: BiFunction>, - private val onRouterNotFound: Function>, - private val onRouterComplete: Function>, - private val webSocketManager: WebSocketManager, - private val onAcceptHttpTunnel: Function>, - private val onAcceptHttpTunnelHandshakeResponse: Function>, - private val onRefuseHttpTunnelHandshakeResponse: Function>, - private val onHttpTunnelHandshakeComplete: BiFunction> -) : HttpServerConnection.Listener.Adapter() { - - override fun onHeaderComplete(ctx: RoutingContext): CompletableFuture { - return onHeaderComplete.apply(ctx) - } - - override fun onHttpRequestComplete(ctx: RoutingContext): CompletableFuture { - if (ctx.response.isCommitted) return Result.DONE - - val results = routerManager.findRouters(ctx) - val asyncCtx = ctx as AsyncRoutingContext - val iterator = results.iterator() - return if (iterator.hasNext()) { - val result = iterator.next() - asyncCtx.routerMatchResult = result - asyncCtx.routerIterator = iterator - (result.router as AsyncRouter).getHandler() - .apply(ctx) - .thenCompose { handleRouterComplete(ctx) } - .exceptionallyCompose { handleRouterException(ctx, it) } - } else handleRouterNotFound(ctx) - } - - override fun onException(ctx: RoutingContext?, e: Throwable): CompletableFuture { - return onException.apply(ctx, e) - } - - override fun onWebSocketHandshake(ctx: RoutingContext): CompletableFuture { - val path = URIUtils.canonicalPath(ctx.uri.decodedPath) - val handler = webSocketManager.findWebSocketHandler(path) - val future = CompletableFuture() - if (handler != null) { - future.complete(handler) - } else { - future.completeExceptionally( - BadMessageException( - HttpStatus.BAD_REQUEST_400, - "The websocket handler is not register" - ) - ) - } - return future - } - - override fun onAcceptHttpTunnel(request: HttpServerRequest): CompletableFuture { - return onAcceptHttpTunnel.apply(request) - } - - override fun onAcceptHttpTunnelHandshakeResponse(context: RoutingContext): CompletableFuture { - return onAcceptHttpTunnelHandshakeResponse.apply(context) - } - - override fun onRefuseHttpTunnelHandshakeResponse(context: RoutingContext): CompletableFuture { - return onRefuseHttpTunnelHandshakeResponse.apply(context) - } - - override fun onHttpTunnelHandshakeComplete(connection: TcpConnection, address: InetSocketAddress): CompletableFuture { - return onHttpTunnelHandshakeComplete.apply(connection, address) - } - - private fun handleRouterNotFound(ctx: RoutingContext): CompletableFuture { - return onRouterNotFound.apply(ctx) - } - - private fun handleRouterException(ctx: RoutingContext, e: Throwable): CompletableFuture { - return onException.apply(ctx, e) - } - - private fun handleRouterComplete(ctx: RoutingContext): CompletableFuture { - return onRouterComplete.apply(ctx) - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServerRequest.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServerRequest.kt deleted file mode 100644 index 2e469672e..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/AsyncHttpServerRequest.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.codec.CookieParser -import com.fireflysource.net.http.common.codec.UrlEncoded -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.server.HttpServerContentHandler -import com.fireflysource.net.http.server.HttpServerRequest -import com.fireflysource.net.http.server.MultiPart -import com.fireflysource.net.http.server.impl.content.handler.ByteBufferContentHandler -import com.fireflysource.net.http.server.impl.content.handler.FormInputsContentHandler -import com.fireflysource.net.http.server.impl.content.handler.MultiPartContentHandler -import com.fireflysource.net.http.server.impl.content.handler.StringContentHandler -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.concurrent.atomic.AtomicBoolean -import java.util.function.Supplier - -class AsyncHttpServerRequest( - val request: MetaData.Request, - config: HttpConfig, - scope: CoroutineScope = CoroutineScope(CoroutineName("Firefly-HTTP-server-request")) -) : HttpServerRequest { - - private var cookieList: List? = null - private var queryStringMap: UrlEncoded? = null - private val requestComplete = AtomicBoolean(false) - private var contentHandler: HttpServerContentHandler - - init { - val query: String? = request.uri.query - if (!query.isNullOrBlank()) { - queryStringMap = UrlEncoded(query) - } - val contentType = request.fields[HttpHeader.CONTENT_TYPE] - contentHandler = if (contentType != null) { - when { - contentType.contains("x-www-form-urlencoded", true) -> - FormInputsContentHandler(config.maxRequestBodySize) - contentType.contains("multipart/form-data", true) -> - MultiPartContentHandler( - config.maxUploadFileSize, - config.maxRequestBodySize, - config.uploadFileSizeThreshold, - scope - ) - else -> StringContentHandler(config.maxRequestBodySize) - } - } else StringContentHandler(config.maxRequestBodySize) - - } - - override fun getMethod(): String = request.method - - override fun getURI(): HttpURI = request.uri - - override fun getHttpVersion(): HttpVersion = request.httpVersion - - override fun getQueryString(name: String): String = queryStringMap?.getString(name) ?: "" - - override fun getQueryStrings(name: String): List = queryStringMap?.get(name) ?: listOf() - - override fun getQueryStrings(): Map> = queryStringMap ?: mapOf() - - override fun getHttpFields(): HttpFields = request.fields - - override fun getContentLength(): Long = request.contentLength - - override fun getCookies(): List { - val cookies = cookieList - return if (cookies == null) { - val list = Optional.ofNullable(httpFields[HttpHeader.COOKIE]) - .filter { it.isNotBlank() } - .map { CookieParser.parseCookie(it) } - .orElse(listOf()) - cookieList = list - list - } else { - cookies - } - } - - override fun getContentHandler(): HttpServerContentHandler = this.contentHandler - - override fun setContentHandler(contentHandler: HttpServerContentHandler) { - this.contentHandler = contentHandler - } - - override fun isRequestComplete(): Boolean = requestComplete.get() - - override fun setRequestComplete(requestComplete: Boolean) { - this.requestComplete.set(requestComplete) - } - - override fun getStringBody(): String = getStringBody(StandardCharsets.UTF_8) - - override fun getStringBody(charset: Charset): String = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is StringContentHandler } - .map { it as StringContentHandler } - .map { it.toString(charset, getContentEncoding()) } - .orElse("") - - override fun getBody(): List = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is ByteBufferContentHandler } - .map { it as ByteBufferContentHandler } - .map { it.getByteBuffers(getContentEncoding()) } - .orElse(listOf()) - - override fun getFormInput(name: String): String = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is FormInputsContentHandler } - .map { it as FormInputsContentHandler } - .map { it.getFormInput(name, getContentEncoding()) } - .orElse("") - - override fun getFormInputs(name: String): List = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is FormInputsContentHandler } - .map { it as FormInputsContentHandler } - .map { it.getFormInputs(name, getContentEncoding()) } - .orElse(listOf()) - - override fun getFormInputs(): Map> = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is FormInputsContentHandler } - .map { it as FormInputsContentHandler } - .map { it.getFormInputs(getContentEncoding()) } - .orElse(mapOf()) - - override fun getPart(name: String): MultiPart? = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is MultiPartContentHandler } - .map { it as MultiPartContentHandler } - .map { it.getPart(name) } - .orElse(null) - - override fun getParts(): List = Optional - .ofNullable(contentHandler) - .filter { isRequestComplete } - .filter { it is MultiPartContentHandler } - .map { it as MultiPartContentHandler } - .map { it.getParts() } - .orElse(listOf()) - - override fun getTrailerSupplier(): Supplier = request.trailerSupplier - - private fun getContentEncoding(): Optional { - return Optional.ofNullable(this.httpFields[HttpHeader.CONTENT_ENCODING]) - .map { it.trim() } - .map { it.lowercase(Locale.getDefault()) } - .flatMap { ContentEncoding.from(it) } - } - - override fun toString(): String { - return """ - |request: ----------------- - |$method $uri $httpVersion - |$httpFields - |$stringBody - |end request -------------- - """.trimMargin() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/CompressedServerOutputChannel.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/CompressedServerOutputChannel.kt deleted file mode 100644 index 297a13c84..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/CompressedServerOutputChannel.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.net.http.common.codec.ContentEncoded -import com.fireflysource.net.http.common.model.ContentEncoding -import com.fireflysource.net.http.server.HttpServerOutputChannel -import java.io.OutputStream -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletableFuture - -class CompressedServerOutputChannel( - private val outputChannel: HttpServerOutputChannel, - private val contentEncoding: ContentEncoding, - private val bufferSize: Int = 512 -) : HttpServerOutputChannel { - - private var compressedOutputStream: OutputStream? = null - private var outputStreamAdapter: ServerOutputStreamAdapter? = null - - private inner class ServerOutputStreamAdapter : OutputStream() { - - override fun write(b: Int) { - val buffer = BufferUtils.allocate(1) - val pos = buffer.flipToFill() - buffer.put(b.toByte()) - buffer.flipToFlush(pos) - outputChannel.write(buffer) - } - - override fun write(b: ByteArray) { - outputChannel.write(ByteBuffer.wrap(b)) - } - - override fun write(b: ByteArray, off: Int, len: Int) { - outputChannel.write(ByteBuffer.wrap(b, off, len)) - } - - } - - override fun isCommitted(): Boolean = outputChannel.isCommitted - - override fun commit(): CompletableFuture { - return outputChannel.commit().thenAccept { - val adapter = ServerOutputStreamAdapter() - compressedOutputStream = ContentEncoded.createEncodingOutputStream(adapter, contentEncoding, bufferSize) - } - } - - override fun write(byteBuffer: ByteBuffer): CompletableFuture { - val bytes = BufferUtils.toArray(byteBuffer) - getOutput().write(bytes) - val future = CompletableFuture() - future.complete(bytes.size) - return future - } - - override fun write(byteBuffers: Array, offset: Int, length: Int): CompletableFuture { - val last = offset + length - 1 - val buffer = BufferUtils.merge((offset..last).map { byteBuffers[it].duplicate() }) - val bytes = BufferUtils.toArray(buffer) - getOutput().write(bytes) - val future = CompletableFuture() - future.complete(bytes.size.toLong()) - return future - } - - override fun write(byteBufferList: List, offset: Int, length: Int): CompletableFuture { - return write(byteBufferList.toTypedArray(), offset, length) - } - - override fun write(string: String): CompletableFuture { - return write(string, StandardCharsets.UTF_8) - } - - override fun write(string: String, charset: Charset): CompletableFuture { - val buffer = BufferUtils.toBuffer(string, charset) - return write(buffer) - } - - private fun getOutput(): OutputStream { - val output = compressedOutputStream - requireNotNull(output) { "The compressed output stream not create" } - return output - } - - override fun closeAsync(): CompletableFuture { - compressedOutputStream?.close() - outputStreamAdapter?.close() - return outputChannel.closeAsync() - } - - override fun close() { - closeAsync() - } - - override fun isOpen(): Boolean = outputChannel.isOpen -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerConnection.kt deleted file mode 100644 index 6d909501f..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerConnection.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.Connection -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.TcpBasedHttpConnection -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.exception.HttpServerConnectionListenerNotSetException -import com.fireflysource.net.http.common.model.HttpVersion -import com.fireflysource.net.http.common.v1.decoder.HttpParser -import com.fireflysource.net.http.common.v1.decoder.parseAll -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpCoroutineDispatcher -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.launch -import java.io.IOException -import java.util.concurrent.CancellationException -import java.util.concurrent.atomic.AtomicBoolean - -class Http1ServerConnection( - val config: HttpConfig, - private val tcpConnection: TcpConnection -) : Connection by tcpConnection, TcpCoroutineDispatcher by tcpConnection, TcpBasedHttpConnection, HttpServerConnection { - - companion object { - private val log = SystemLogger.create(Http1ServerConnection::class.java) - } - - private val requestHandler = Http1ServerRequestHandler(this) - private val parser = HttpParser(requestHandler) - private val responseHandler = Http1ServerResponseHandler(this) - private val beginning = AtomicBoolean(false) - private val channel: Channel = Channel(Channel.UNLIMITED) - private var parseRequestJob: Job? = null - private var generateResponseJob: Job? = null - - private fun parseRequestJob() = coroutineScope.launch { - parseLoop@ while (true) { - when (channel.receive()) { - is ParseNextHttpPacket -> parseNextHttpPacket() - is ExitHttpParser -> { - log.info { "Exit the HTTP server parser. id: $id" } - break@parseLoop - } - } - } - } - - private suspend fun parseNextHttpPacket() { - try { - parser.parseAll(tcpConnection) - } catch (e: BadMessageException) { - requestHandler.badMessage(e) - channel.trySend(ExitHttpParser) - } catch (e: IOException) { - log.info { "The TCP connection IO exception. message: ${e.message ?: e.javaClass.name}, id: $id" } - channel.trySend(ExitHttpParser) - } catch (e: CancellationException) { - log.info { "Cancel HTTP1 parsing. message: ${e.message} id: $id" } - channel.trySend(ExitHttpParser) - } catch (e: Exception) { - log.error(e) { "Parse HTTP1 request exception. id: $id" } - } finally { - resetParser() - } - } - - fun parseNextRequest() { - channel.trySend(ParseNextHttpPacket) - } - - suspend fun endHttpParser() { - channel.trySend(ExitHttpParser) - parseRequestJob?.join() - } - - fun resetParser() { - parser.reset() - } - - private fun generateResponseJob() = responseHandler.generateResponseJob() - - fun getHeaderBufferSize() = config.headerBufferSize - - fun sendResponseMessage(message: Http1ResponseMessage) = responseHandler.sendResponseMessage(message) - - suspend fun endResponseHandler() { - responseHandler.endResponseHandler() - generateResponseJob?.join() - } - - override fun begin() { - if (beginning.compareAndSet(false, true)) { - if (requestHandler.connectionListener === HttpServerConnection.EMPTY_LISTENER) { - throw HttpServerConnectionListenerNotSetException("Please set connection listener before begin parsing.") - } - parseRequestJob = parseRequestJob() - generateResponseJob = generateResponseJob() - parseNextRequest() - } - } - - override fun setListener(listener: HttpServerConnection.Listener): HttpServerConnection { - Assert.state( - !beginning.get(), - "The HTTP request parser has started. Please set listener before begin parsing." - ) - requestHandler.connectionListener = listener - return this - } - - override fun getHttpVersion(): HttpVersion = HttpVersion.HTTP_1_1 - - override fun isSecureConnection(): Boolean = tcpConnection.isSecureConnection - - override fun getTcpConnection(): TcpConnection = tcpConnection -} - -sealed interface ParseHttpPacketMessage -object ParseNextHttpPacket : ParseHttpPacketMessage -object ExitHttpParser : ParseHttpPacketMessage \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerOutputChannel.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerOutputChannel.kt deleted file mode 100644 index 7ca4e58ef..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerOutputChannel.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.server.HttpServerOutputChannel -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean - -class Http1ServerOutputChannel( - private val http1ServerConnection: Http1ServerConnection, - private val response: MetaData.Response, - private val closeConnection: Boolean -) : HttpServerOutputChannel { - - private val committed = AtomicBoolean(false) - private val closed = AtomicBoolean(false) - - override fun isCommitted(): Boolean = committed.get() - - override fun commit(): CompletableFuture { - return if (committed.compareAndSet(false, true)) { - val header = Header(response, CompletableFuture()) - http1ServerConnection.sendResponseMessage(header) - header.future - } else { - Result.DONE - } - } - - override fun write(byteBuffers: Array, offset: Int, length: Int): CompletableFuture { - val future = CompletableFuture() - val buffers = Http1OutputBuffers(byteBuffers, offset, length, Result.futureToConsumer(future)) - http1ServerConnection.sendResponseMessage(buffers) - return future - } - - override fun write(byteBufferList: List, offset: Int, length: Int): CompletableFuture { - val future = CompletableFuture() - val buffers = Http1OutputBufferList(byteBufferList, offset, length, Result.futureToConsumer(future)) - http1ServerConnection.sendResponseMessage(buffers) - return future - } - - override fun write(string: String): CompletableFuture { - return write(string, StandardCharsets.UTF_8) - } - - override fun write(string: String, charset: Charset): CompletableFuture { - val buffer = BufferUtils.toBuffer(string, charset) - return write(buffer) - } - - override fun write(byteBuffer: ByteBuffer): CompletableFuture { - val future = CompletableFuture() - val buffer = Http1OutputBuffer(byteBuffer, Result.futureToConsumer(future)) - http1ServerConnection.sendResponseMessage(buffer) - return future - } - - override fun closeAsync(): CompletableFuture { - return if (closed.compareAndSet(false, true)) { - val message = EndResponse(CompletableFuture(), closeConnection) - http1ServerConnection.sendResponseMessage(message) - message.future - } else { - Result.DONE - } - } - - override fun isOpen(): Boolean = !closed.get() - - override fun close() { - closeAsync() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerRequestHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerRequestHandler.kt deleted file mode 100644 index 468f3d030..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerRequestHandler.kt +++ /dev/null @@ -1,355 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.codec.base64.Base64Utils -import com.fireflysource.common.io.toBuffer -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.common.v1.decoder.HttpParser -import com.fireflysource.net.http.common.v2.decoder.SettingsBodyParser -import com.fireflysource.net.http.common.v2.frame.SettingsFrame -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.router.AsyncRoutingContext -import com.fireflysource.net.websocket.common.impl.AsyncWebSocketConnection -import com.fireflysource.net.websocket.common.model.AcceptHash -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.net.InetSocketAddress -import java.nio.ByteBuffer - -class Http1ServerRequestHandler(private val connection: Http1ServerConnection) : HttpParser.RequestHandler { - - companion object { - private val log = SystemLogger.create(Http1ServerRequestHandler::class.java) - } - - var connectionListener: HttpServerConnection.Listener = HttpServerConnection.EMPTY_LISTENER - private val parserChannel: Channel = Channel(Channel.UNLIMITED) - private var expectUpgradeHttp2 = false - private var settingsFrame: SettingsFrame? = null - private var expectUpgradeWebsocket = false - - init { - handleParserMessageJob() - } - - private fun handleParserMessageJob() = connection.coroutineScope.launch { - var request: MetaData.Request? = null - var context: AsyncRoutingContext? = null - parserLoop@ while (true) { - val message = parserChannel.receive() - try { - when (message) { - is StartRequest -> request = newRequest(message) - is ParsedHeader -> addHeader(request, message) - is HeaderComplete -> context = newContextAndNotifyHeaderComplete(request) - is Content -> acceptContent(context, message) - is ContentComplete -> closeContentHandler(context) - is MessageComplete -> notifyHttpRequestComplete(context) - is BadMessage -> notifyException(request, context, message.exception) - is EarlyEOF -> notifyException(request, context, IllegalStateException("Parser early EOF")) - is EndRequestHandler -> { - log.info { "Exit the server request handler. id: ${connection.id}" } - break@parserLoop - } - } - } catch (e: Exception) { - notifyException(request, context, e) - } - } - } - - private fun newRequest(message: StartRequest): MetaData.Request { - val httpURI = if (message.method == HttpMethod.CONNECT.value) { - if (!message.uri.contains("://")) HttpURI("https://${message.uri}") - else HttpURI(message.uri) - } else HttpURI(message.uri) - return MetaData.Request(message.method, httpURI, message.version, HttpFields()) - } - - private fun addHeader(request: MetaData.Request?, message: ParsedHeader) { - requireNotNull(request) - request.fields.add(message.field) - } - - private suspend fun newContextAndNotifyHeaderComplete(request: MetaData.Request?): AsyncRoutingContext { - requireNotNull(request) - val httpServerRequest = AsyncHttpServerRequest( - request, - connection.config, - CoroutineScope(CoroutineName("Firefly-HTTP-server-request") + connection.coroutineDispatcher) - ) - - this.expectUpgradeHttp2 = HttpProtocolNegotiator.expectUpgradeHttp2(httpServerRequest) - if (expectUpgradeHttp2) { - val settingsBody = Base64Utils.decodeFromUrlSafeString(request.fields[HttpHeader.HTTP2_SETTINGS]) - val settings = SettingsBodyParser.parseBody(ByteBuffer.wrap(settingsBody)) - this.settingsFrame = settings - this.expectUpgradeHttp2 = settings != null - } - this.expectUpgradeWebsocket = HttpProtocolNegotiator.expectUpgradeWebsocket(httpServerRequest) - - - val ctx = newContext(httpServerRequest) - notifyHeaderComplete(ctx) - return ctx - } - - private fun newContext(request: MetaData.Request?): AsyncRoutingContext? { - return if (request != null) { - val httpServerRequest = AsyncHttpServerRequest( - request, - connection.config, - CoroutineScope(CoroutineName("Firefly-HTTP-server-request") + connection.coroutineDispatcher) - ) - newContext(httpServerRequest) - } else null - } - - private fun newContext(request: AsyncHttpServerRequest): AsyncRoutingContext { - val expect100 = request.httpFields.expectServerAcceptsContent() - val closeConnection = request.httpFields.isCloseConnection(request.httpVersion) - return AsyncRoutingContext( - request, - Http1ServerResponse(connection, expect100, closeConnection), - connection - ) - } - - private suspend fun notifyHeaderComplete(context: RoutingContext) { - connectionListener.onHeaderComplete(context).await() - } - - private fun acceptContent(context: AsyncRoutingContext?, message: Content) { - requireNotNull(context) - context.request.contentHandler.accept(message.byteBuffer, context) - } - - private suspend fun closeContentHandler(context: AsyncRoutingContext?) { - requireNotNull(context) - context.request.contentHandler.closeAsync().await() - } - - private suspend fun notifyHttpRequestComplete(context: RoutingContext?) { - requireNotNull(context) - context.request.isRequestComplete = true - when { - isHttpTunnel(context) -> { - val accept = connectionListener.onAcceptHttpTunnel(context.request).await() - if (accept) { - endHttpParser() - switchHttpTunnel(context) - endResponseHandler() - log.info { "Establish HTTP tunnel success. id: ${connection.id}" } - } else { - refuseHttpTunnelRequest(context) - } - } - isUpgradeHttp2(context) -> { - endHttpParser() - endResponseHandler() - switchToHttp2(context) - log.info { "Upgrade to HTTP2 success. id: ${connection.id}" } - } - isUpgradeWebsocket(context) -> { - endHttpParser() - endResponseHandler() - switchToWebSocket(context) - log.info { "Upgrade to Websocket success. id: ${connection.id}" } - } - else -> { - connection.parseNextRequest() - connectionListener.onHttpRequestComplete(context).await() - } - } - log.debug { "HTTP1 server handles request success. id: ${connection.id}" } - } - - private suspend fun endHttpParser() { - parserChannel.trySend(EndRequestHandler) - connection.endHttpParser() - log.info { "Upgrade protocol success. Exit HTTP1 parser. id: ${connection.id}" } - } - - private suspend fun endResponseHandler() { - connection.endResponseHandler() - } - - private fun isHttpTunnel(ctx: RoutingContext): Boolean { - return HttpMethod.CONNECT.`is`(ctx.method) && !connection.isSecureConnection - } - - private suspend fun switchHttpTunnel(ctx: RoutingContext) { - connectionListener.onAcceptHttpTunnelHandshakeResponse(ctx).await() - val address = InetSocketAddress(ctx.request.uri.host, ctx.request.uri.port) - connectionListener.onHttpTunnelHandshakeComplete(connection.tcpConnection, address) - } - - private suspend fun refuseHttpTunnelRequest(ctx: RoutingContext) { - connectionListener.onRefuseHttpTunnelHandshakeResponse(ctx).await() - } - - private suspend fun switchToHttp2(ctx: RoutingContext) { - val settings = settingsFrame - requireNotNull(settings) - - writeHttp2UpgradeResponse() - val http2ServerConnection = createHttp2Connection() - val stream = http2ServerConnection.upgradeHttp2(settings) - val response = Http2ServerResponse(http2ServerConnection, stream) - val http2Context = AsyncRoutingContext( - ctx.request, - response, - http2ServerConnection - ) - connectionListener.onHttpRequestComplete(http2Context).await() - } - - private fun isUpgradeWebsocket(context: RoutingContext) = - expectUpgradeWebsocket && !context.response.isCommitted - - private fun isUpgradeHttp2(context: RoutingContext) = - expectUpgradeHttp2 && !context.response.isCommitted - - private suspend fun switchToWebSocket(ctx: RoutingContext) { - val handler = connectionListener.onWebSocketHandshake(ctx).await() - - val clientKey = ctx.httpFields[HttpHeader.SEC_WEBSOCKET_KEY] - val serverAccept = AcceptHash.hashKey(clientKey) - - val clientExtensions = ctx.httpFields.getValuesList(HttpHeader.SEC_WEBSOCKET_EXTENSIONS) - val serverExtensions = handler.extensionSelector.select(clientExtensions) - - val clientSubProtocols = ctx.httpFields.getValuesList(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL) - val serverSubProtocols = handler.subProtocolSelector.select(clientSubProtocols) - - val message = buildString { - append("HTTP/1.1 101 Switching Protocols\r\n") - append("Connection: Upgrade\r\n") - append("Upgrade: websocket\r\n") - append("${HttpHeader.SEC_WEBSOCKET_ACCEPT.value}: ${serverAccept}\r\n") - if (!serverExtensions.isNullOrEmpty()) { - append("${HttpHeader.SEC_WEBSOCKET_EXTENSIONS.value}: ${serverExtensions.joinToString(", ")}\r\n") - } - if (!serverSubProtocols.isNullOrEmpty()) { - append("${HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL.value}: ${serverSubProtocols.joinToString(", ")}\r\n") - } - append("\r\n") - }.toBuffer() - connection.tcpConnection.writeAndFlush(message).await() - log.info { "Server response 101 Switching Protocols. upgrade: websocket, id: ${connection.id}" } - - val webSocketConnection = AsyncWebSocketConnection( - connection.tcpConnection, - handler.policy, - handler.url, - serverExtensions ?: listOf(), - AsyncWebSocketConnection.defaultExtensionFactory, - serverSubProtocols ?: listOf() - ) - webSocketConnection.setWebSocketMessageHandler(handler.messageHandler) - webSocketConnection.begin() - handler.connectionListener.accept(webSocketConnection).await() - } - - private fun createHttp2Connection() = - Http2ServerConnection(connection.config, connection.tcpConnection) - .also { it.setListener(connectionListener).begin() } - - private suspend fun writeHttp2UpgradeResponse() { - val message = ("HTTP/1.1 101 Switching Protocols\r\n" + - "Connection: Upgrade\r\n" + - "Upgrade: h2c\r\n\r\n").toBuffer() - connection.tcpConnection.writeAndFlush(message).await() - log.info { "Server response 101 Switching Protocols. upgrade: h2c, id: ${connection.id}" } - } - - private suspend fun notifyException(request: MetaData.Request?, context: RoutingContext?, exception: Throwable) { - val ctx = context ?: newContext(request) - try { - log.error(exception) { "HTTP1 server parser exception. id: ${connection.id}" } - connectionListener.onException(ctx, exception).await() - } catch (e: Exception) { - log.error(e) { "HTTP1 server handles exception failure. id: ${connection.id}" } - } - when { - exception is BadMessageException -> closeConnection() - ctx == null -> closeConnection() - !ctx.request.isRequestComplete -> connection.parseNextRequest() - } - } - - private suspend fun closeConnection() { - endHttpParser() - endResponseHandler() - connection.closeAsync() - } - - override fun startRequest(method: String, uri: String, version: HttpVersion): Boolean { - parserChannel.trySend(StartRequest(method, uri, version)) - return false - } - - override fun getHeaderCacheSize(): Int = 4096 - - override fun parsedHeader(field: HttpField) { - parserChannel.trySend(ParsedHeader(field)) - } - - override fun headerComplete(): Boolean { - parserChannel.trySend(HeaderComplete) - return false - } - - override fun content(byteBuffer: ByteBuffer): Boolean { - parserChannel.trySend(Content(byteBuffer)) - return false - } - - override fun contentComplete(): Boolean { - parserChannel.trySend(ContentComplete) - return false - } - - override fun messageComplete(): Boolean { - parserChannel.trySend(MessageComplete) - return true - } - - override fun earlyEOF() { - parserChannel.trySend(EarlyEOF) - } - - override fun badMessage(failure: BadMessageException) { - parserChannel.trySend(BadMessage(failure)) - } - -} - -sealed interface ParserMessage - -data class StartRequest(val method: String, val uri: String, val version: HttpVersion) : ParserMessage - -@JvmInline -value class ParsedHeader(val field: HttpField) : ParserMessage - -object HeaderComplete : ParserMessage - -@JvmInline -value class Content(val byteBuffer: ByteBuffer) : ParserMessage - -object ContentComplete : ParserMessage - -object MessageComplete : ParserMessage - -object EarlyEOF : ParserMessage - -@JvmInline -value class BadMessage(val exception: Exception) : ParserMessage - -object EndRequestHandler : ParserMessage \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerResponse.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerResponse.kt deleted file mode 100644 index 9faa26d58..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerResponse.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.io.toBuffer -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.server.HttpServerOutputChannel -import java.nio.ByteBuffer -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean - -class Http1ServerResponse( - private val http1ServerConnection: Http1ServerConnection, - private val expect100Continue: Boolean, - private val closeConnection: Boolean -) : AbstractHttpServerResponse(http1ServerConnection) { - - private val write100Continue = AtomicBoolean(false) - private val write200ConnectionEstablished = AtomicBoolean(false) - - override fun createHttpServerOutputChannel(response: MetaData.Response): HttpServerOutputChannel { - if (expect100Continue && !write100Continue.get()) { - http1ServerConnection.resetParser() - } - return Http1ServerOutputChannel(http1ServerConnection, response, closeConnection) - } - - override fun response100Continue(): CompletableFuture { - return if (write100Continue.compareAndSet(false, true)) { - writeAndFlushHttpMessage("HTTP/1.1 100 Continue\r\n".toBuffer()) - } else Result.DONE - } - - override fun response200ConnectionEstablished(): CompletableFuture { - return if (write200ConnectionEstablished.compareAndSet(false, true)) { - writeAndFlushHttpMessage("HTTP/1.1 200 Connection Established\r\n\r\n".toBuffer()) - } else Result.DONE - } - - private fun writeAndFlushHttpMessage(message: ByteBuffer): CompletableFuture { - val connection = http1ServerConnection.tcpConnection - return connection.writeAndFlush(message).thenCompose { Result.DONE } - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerResponseHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerResponseHandler.kt deleted file mode 100644 index 4c9720657..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http1ServerResponseHandler.kt +++ /dev/null @@ -1,235 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.exception.Http1GeneratingResultException -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v1.encoder.HttpGenerator -import com.fireflysource.net.http.common.v1.encoder.assert -import com.fireflysource.net.tcp.buffer.DelegatedOutputBufferArray -import com.fireflysource.net.tcp.buffer.OutputBufferArray -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.ByteBuffer -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.function.Consumer - -class Http1ServerResponseHandler(private val http1ServerConnection: Http1ServerConnection) { - - companion object { - private val log = SystemLogger.create(Http1ServerResponseHandler::class.java) - } - - private val generator = HttpGenerator() - private val headerBuffer = BufferUtils.allocateDirect(http1ServerConnection.getHeaderBufferSize()) - private val chunkBuffer: ByteBuffer by lazy(LazyThreadSafetyMode.NONE) { BufferUtils.allocateDirect(HttpGenerator.CHUNK_SIZE) } - private val responseChannel: Channel = Channel(Channel.UNLIMITED) - - fun sendResponseMessage(message: Http1ResponseMessage) { - responseChannel.trySend(message) - } - - fun generateResponseJob() = http1ServerConnection.coroutineScope.launch { - responseLoop@ while (true) { - when (val message = responseChannel.receive()) { - is Header -> generateHeader(message) - is Http1OutputBuffer -> generateContent(message) - is Http1OutputBufferList -> generateContent(message) - is Http1OutputBuffers -> generateContent(message) - is EndResponse -> completeContent(message) - is EndResponseHandler -> { - log.info { "Exit the server response handler. id: ${http1ServerConnection.id}" } - break@responseLoop - } - } - } - } - - fun endResponseHandler() { - responseChannel.trySend(EndResponseHandler) - } - - private suspend fun generateHeader(header: Header) { - val (response, future) = header - try { - generator.generateResponse(response, false, headerBuffer, null, null, false) - .assertFlush() - flushHeaderBuffer() - Result.done(future) - } catch (e: Exception) { - future.completeExceptionally(e) - } - } - - private suspend fun generateContent(http1OutputBuffers: Http1OutputBuffers) { - try { - val content = LinkedList() - val offset = http1OutputBuffers.getCurrentOffset() - val lastIndex = http1OutputBuffers.getLastIndex() - (offset..lastIndex).forEach { - val buffer = http1OutputBuffers.buffers[it] - if (generator.isChunking) { - val chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE) - generator.generateResponse(null, false, null, chunk, buffer, false) - .assertFlush() - content.add(chunk) - content.add(buffer) - } else { - generator.generateResponse(null, false, null, null, buffer, false) - .assertFlush() - content.add(buffer) - } - } - val length = http1ServerConnection.tcpConnection.write(content, 0, content.size).await() - http1OutputBuffers.result.accept(Result(true, length, null)) - } catch (e: Exception) { - http1OutputBuffers.result.accept(Result(false, -1, e)) - } - } - - private suspend fun generateContent(http1OutputBuffer: Http1OutputBuffer) { - val (buffer, result) = http1OutputBuffer - try { - val length = if (generator.isChunking) { - generator.generateResponse(null, false, null, chunkBuffer, buffer, false) - .assertFlush() - flushChunkedContentBuffer(buffer).toInt() - } else { - generator.generateResponse(null, false, null, null, buffer, false) - .assertFlush() - flushContentBuffer(buffer) - } - result.accept(Result(true, length, null)) - } catch (e: Exception) { - result.accept(Result(false, -1, e)) - } - } - - private fun HttpGenerator.Result.assertFlush() { - this.assert(HttpGenerator.Result.FLUSH) - assert(HttpGenerator.State.COMMITTED) - } - - private suspend fun completeContent(endResponse: EndResponse) { - try { - completing() - if (generator.isChunking) { - when (val generateResult = generator.generateResponse(null, false, null, chunkBuffer, null, true)) { - HttpGenerator.Result.FLUSH -> { - assert(HttpGenerator.State.COMPLETING) - flushChunkBuffer() - } - HttpGenerator.Result.NEED_CHUNK_TRAILER -> generateTrailer() - else -> throw Http1GeneratingResultException("The HTTP server generator result error. $generateResult") - } - } - end(endResponse) - Result.done(endResponse.future) - } catch (e: Exception) { - endResponse.future.completeExceptionally(e) - } - } - - private fun completing() { - generator.generateResponse(null, false, null, null, null, true) - .assert(HttpGenerator.Result.CONTINUE) - assert(HttpGenerator.State.COMPLETING) - } - - private suspend fun end(endResponse: EndResponse) { - http1ServerConnection.tcpConnection.flush().await() - val result = generator.generateResponse(null, false, null, null, null, true) - if (result == HttpGenerator.Result.SHUTDOWN_OUT || endResponse.closeConnection) { - http1ServerConnection.closeAsync() - log.debug { "HTTP1 server connection is closing. id: ${http1ServerConnection.id}" } - } - - assert(HttpGenerator.State.END) - generator.reset() - } - - private suspend fun generateTrailer() { - generator.generateResponse(null, false, null, headerBuffer, null, true) - .assert(HttpGenerator.Result.FLUSH) - assert(HttpGenerator.State.COMPLETING) - flushHeaderBuffer() - } - - private fun assert(expectState: HttpGenerator.State) { - if (!generator.isState(expectState)) { - throw Http1GeneratingResultException("The HTTP generator state error. ${generator.state}") - } - } - - private suspend fun flushHeaderBuffer() { - if (headerBuffer.hasRemaining()) { - val size = http1ServerConnection.tcpConnection.write(headerBuffer).await() - log.debug { "flush header bytes: $size" } - } - BufferUtils.clear(headerBuffer) - } - - private suspend fun flushContentBuffer(contentBuffer: ByteBuffer): Int { - return if (contentBuffer.hasRemaining()) { - val size = http1ServerConnection.tcpConnection.write(contentBuffer).await() - log.debug { "flush content bytes: $size" } - size - } else 0 - } - - private suspend fun flushChunkedContentBuffer(contentBuffer: ByteBuffer): Long { - val bufArray = arrayOf(chunkBuffer, contentBuffer) - val remaining = bufArray.sumOf { it.remaining().toLong() } - val length = if (remaining > 0) { - val len = http1ServerConnection.tcpConnection.write(bufArray, 0, bufArray.size).await() - log.debug { "flush chunked content bytes: $len" } - len - } else 0 - BufferUtils.clear(chunkBuffer) - return length - } - - private suspend fun flushChunkBuffer() { - if (chunkBuffer.hasRemaining()) { - val size = http1ServerConnection.tcpConnection.write(chunkBuffer).await() - log.debug { "flush chunked bytes: $size" } - } - BufferUtils.clear(chunkBuffer) - } - -} - -sealed class Http1ResponseMessage - -data class Header( - val response: MetaData.Response, - val future: CompletableFuture -) : Http1ResponseMessage() - -data class Http1OutputBuffer( - val buffer: ByteBuffer, val result: Consumer> -) : Http1ResponseMessage() - -open class Http1OutputBuffers( - val buffers: Array, - val offset: Int, - val length: Int, - val result: Consumer>, - private val outputBufferArray: DelegatedOutputBufferArray = DelegatedOutputBufferArray( - buffers, offset, length, result - ) -) : OutputBufferArray by outputBufferArray, Http1ResponseMessage() - -class Http1OutputBufferList( - bufferList: List, - offset: Int, - length: Int, - result: Consumer> -) : Http1OutputBuffers(bufferList.toTypedArray(), offset, length, result) - -data class EndResponse(val future: CompletableFuture, val closeConnection: Boolean) : Http1ResponseMessage() - -object EndResponseHandler : Http1ResponseMessage() \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerConnection.kt deleted file mode 100644 index 00536a819..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerConnection.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.exception.HttpServerConnectionListenerNotSetException -import com.fireflysource.net.http.common.v2.decoder.ServerParser -import com.fireflysource.net.http.common.v2.frame.* -import com.fireflysource.net.http.common.v2.stream.* -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.tcp.TcpConnection -import java.util.function.UnaryOperator - -class Http2ServerConnection( - config: HttpConfig, - tcpConnection: TcpConnection, - flowControl: FlowControl = BufferedFlowControlStrategy(), - private val listener: Http2Connection.Listener = Http2ServerConnectionListener() -) : AsyncHttp2Connection(2, config, tcpConnection, flowControl, listener), ServerParser.Listener, HttpServerConnection { - - companion object { - private val log = SystemLogger.create(Http2ServerConnection::class.java) - } - - private val parser: ServerParser = ServerParser(this, config.maxDynamicTableSize, config.maxHeaderSize) - private var connectionListener: HttpServerConnection.Listener = HttpServerConnection.EMPTY_LISTENER - - init { - parser.init(UnaryOperator.identity()) - } - - fun upgradeHttp2(settingsFrame: SettingsFrame): Stream { - super.onSettings(settingsFrame) - val stream = createRemoteStream(1) - requireNotNull(stream) - return stream - } - - override fun begin() { - if (listener is Http2ServerConnectionListener) { - if (connectionListener === HttpServerConnection.EMPTY_LISTENER) { - throw HttpServerConnectionListenerNotSetException("Please set connection listener before begin parsing.") - } - listener.connectionListener = connectionListener - } - launchParserJob(parser) - } - - // preface frame - override fun onPreface() { - val settings = notifyPreface() - val settingsFrame = SettingsFrame(settings, false) - val windowDelta: Int = initialSessionRecvWindow - HttpConfig.DEFAULT_WINDOW_SIZE - - log.info { "HTTP2 server on preface. id: $id, window delta: $windowDelta, settings: $settingsFrame" } - if (windowDelta > 0) { - updateRecvWindow(windowDelta) - sendControlFrame(null, settingsFrame, WindowUpdateFrame(0, windowDelta)) - } else sendControlFrame(null, settingsFrame) - } - - // headers frame - override fun onHeaders(frame: HeadersFrame) { - log.debug { "Received $frame" } - - val streamId = frame.streamId - if (!isClientStream(streamId)) { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_stream_id") - return - } - - val stream = getStream(streamId) - val metaData = frame.metaData - when { - metaData.isRequest -> onHttpRequest(stream, streamId, frame) - else -> onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "invalid_request") - } - } - - private fun onHttpRequest(stream: Stream?, streamId: Int, frame: HeadersFrame) { - if (stream == null) { - if (isRemoteStreamClosed(streamId)) { - onConnectionFailure(ErrorCode.STREAM_CLOSED_ERROR.code, "unexpected_headers_frame") - } else { - val remoteStream = createRemoteStream(streamId) - if (remoteStream != null && remoteStream is AsyncHttp2Stream) { - onStreamOpened(remoteStream) - remoteStream.process(frame, discard()) - remoteStream.listener = notifyNewStream(remoteStream, frame) - } - } - } else { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "duplicate_stream") - } - } - - // promise frame - override fun onPushPromise(frame: PushPromiseFrame) { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "push_promise") - } - - override fun onResetForUnknownStream(frame: ResetFrame) { - val streamId = frame.streamId - val closed = if (isClientStream(streamId)) isRemoteStreamClosed(streamId) else isLocalStreamClosed(streamId) - if (closed) { - notifyReset(this, frame) - } else { - onConnectionFailure(ErrorCode.PROTOCOL_ERROR.code, "unexpected_rst_stream_frame") - } - } - - override fun setListener(listener: HttpServerConnection.Listener): HttpServerConnection { - this.connectionListener = listener - return this - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerConnectionListener.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerConnectionListener.kt deleted file mode 100644 index 0628e77e6..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerConnectionListener.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.concurrent.CompletableFutures -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v2.frame.* -import com.fireflysource.net.http.common.v2.stream.Http2Connection -import com.fireflysource.net.http.common.v2.stream.Stream -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.router.AsyncRoutingContext -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import java.util.concurrent.CompletableFuture -import java.util.function.Consumer - -class Http2ServerConnectionListener : Http2Connection.Listener.Adapter() { - - companion object { - private val log = SystemLogger.create(Http2ServerConnectionListener::class.java) - } - - var connectionListener: HttpServerConnection.Listener = HttpServerConnection.EMPTY_LISTENER - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - val http2Connection = stream.http2Connection as Http2ServerConnection - - val request = AsyncHttpServerRequest( - frame.metaData as MetaData.Request, - http2Connection.config, - CoroutineScope(CoroutineName("Firefly-HTTP-server-request") + http2Connection.coroutineDispatcher) - ) - val response = Http2ServerResponse(http2Connection, stream) - val context = AsyncRoutingContext( - request, - response, - http2Connection - ) - val trailer: HttpFields by lazy { HttpFields() } - var receivedData = false - var headerComplete: CompletableFuture? = null - - if (frame.isEndHeaders) { - headerComplete = notifyHeaderComplete(context) - } - if (frame.isEndStream) { - if (headerComplete != null) { - headerComplete.thenCompose { notifyRequestComplete(context) } - .exceptionallyAccept { notifyException(context, it) } - } else { - notifyException(context, IllegalStateException("The header complete future must not be null")) - } - } - - return object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - log.debug { "HTTP2 server received trailer frame. id: ${stream.id}" } - if (receivedData) { - trailer.addAll(frame.metaData.fields) - } else { - request.httpFields.addAll(frame.metaData.fields) - } - if (frame.isEndHeaders) { - headerComplete = notifyHeaderComplete(context) - } - if (frame.isEndStream) { - val future = headerComplete - if (future != null) { - future.thenCompose { notifyRequestComplete(context) } - .exceptionallyAccept { notifyException(context, it) } - } else { - notifyException(context, IllegalStateException("The header complete future must not be null")) - } - } - } - - override fun onData(stream: Stream, frame: DataFrame, result: Consumer>) { - receivedData = true - try { - context.request.contentHandler.accept(frame.data, context) - log.debug { "HTTP2 server accepts content success. id: ${stream.id}" } - - if (frame.isEndStream) { - val future = headerComplete - if (future != null) { - future.thenCompose { context.request.contentHandler.closeAsync() } - .thenCompose { notifyRequestComplete(context) } - .thenAccept { result.accept(Result.SUCCESS) } - .exceptionallyAccept { e -> - notifyException(context, e) - .thenAccept { result.accept(Result.createFailedResult(e)) } - .exceptionallyAccept { result.accept(Result.createFailedResult(e)) } - } - } else { - val e = IllegalStateException("The header complete future must not be null") - notifyException(context, e) - .thenAccept { result.accept(Result.createFailedResult(e)) } - .exceptionallyAccept { result.accept(Result.createFailedResult(e)) } - } - } else { - result.accept(Result.SUCCESS) - } - } catch (e: Exception) { - log.error { "HTTP2 server accepts content exception. id: ${stream.id} info: ${e.javaClass.name} ${e.message}" } - notifyException(context, e) - .thenAccept { result.accept(Result.createFailedResult(e)) } - .exceptionallyAccept { result.accept(Result.createFailedResult(e)) } - } - } - - override fun onClosed(stream: Stream) { - log.debug { "HTTP2 server stream closed. id: ${stream.id}" } - } - - override fun onReset(stream: Stream, frame: ResetFrame, result: Consumer>) { - val e = IllegalStateException(ErrorCode.toString(frame.error, "stream reset. id: ${stream.id}")) - notifyException(context, e) - .thenAccept { result.accept(Result.SUCCESS) } - .exceptionallyAccept { result.accept(Result.createFailedResult(it)) } - } - - override fun onFailure(stream: Stream, error: Int, reason: String, result: Consumer>) { - val defaultError = "stream failure. id: ${stream.id}, reason: $reason" - val e = IllegalStateException(ErrorCode.toString(error, defaultError)) - notifyException(context, e) - .thenAccept { result.accept(Result.SUCCESS) } - .exceptionallyAccept { result.accept(Result.createFailedResult(it)) } - } - - override fun onIdleTimeout(stream: Stream, e: Throwable): Boolean { - notifyException(context, e) - return true - } - - } - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - log.info { "HTTP2 server connection closed. id: ${http2Connection.id}, frame: $frame" } - } - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - notifyException(null, failure) - } - - override fun onReset(http2Connection: Http2Connection, frame: ResetFrame) { - val e = IllegalStateException(ErrorCode.toString(frame.error, "stream exception")) - notifyException(null, e) - } - - private fun notifyHeaderComplete(context: RoutingContext): CompletableFuture = try { - connectionListener.onHeaderComplete(context) - } catch (e: Exception) { - log.error(e) { "HTTP2 server handles header complete exception. id: ${context.connection.id}" } - notifyException(context, e) - } - - private fun notifyRequestComplete(context: RoutingContext): CompletableFuture = try { - context.request.isRequestComplete = true - connectionListener.onHttpRequestComplete(context) - } catch (e: Exception) { - log.error(e) { "HTTP2 server handles header complete exception. id: ${context.connection.id}" } - notifyException(context, e) - } - - private fun notifyException(context: RoutingContext?, e: Throwable): CompletableFuture = try { - connectionListener.onException(context, e) - } catch (t: Throwable) { - log.error(t) { "HTTP2 server handler exception. id: ${context?.connection?.id}" } - CompletableFutures.failedFuture(t) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerOutputChannel.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerOutputChannel.kt deleted file mode 100644 index 0f345fe44..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerOutputChannel.kt +++ /dev/null @@ -1,172 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v2.frame.DataFrame -import com.fireflysource.net.http.common.v2.frame.Frame -import com.fireflysource.net.http.common.v2.frame.HeadersFrame -import com.fireflysource.net.http.common.v2.stream.Stream -import com.fireflysource.net.http.server.HttpServerOutputChannel -import com.fireflysource.net.tcp.buffer.DelegatedOutputBufferArray -import com.fireflysource.net.tcp.buffer.OutputBufferArray -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean - -/** - * @author Pengtao Qiu - */ -class Http2ServerOutputChannel( - private val response: MetaData.Response, - private val stream: Stream -) : HttpServerOutputChannel { - - companion object { - private val log = SystemLogger.create(Http2ServerOutputChannel::class.java) - private const val defaultMaxFrameSize = Frame.DEFAULT_MAX_LENGTH.toLong() - } - - private val committed = AtomicBoolean(false) - private val closed = AtomicBoolean(false) - private val messages = LinkedList() - - override fun commit(): CompletableFuture { - if (committed.compareAndSet(false, true)) { - messages.offer(HeadersOutputMessage) - } - return Result.DONE - } - - override fun isCommitted(): Boolean = committed.get() - - override fun write(byteBuffers: Array, offset: Int, length: Int): CompletableFuture { - val message = BuffersOutputMessage(byteBuffers, offset, length) - messages.offer(message) - writeOutputMessage() - - val future = CompletableFuture() - future.complete(message.remaining()) - return future - } - - override fun write(byteBufferList: List, offset: Int, length: Int): CompletableFuture { - return write(byteBufferList.toTypedArray(), offset, length) - } - - override fun write(string: String): CompletableFuture { - return write(string, StandardCharsets.UTF_8) - } - - override fun write(string: String, charset: Charset): CompletableFuture { - val byteBuffer = BufferUtils.toBuffer(string, charset) - return write(byteBuffer) - } - - override fun write(byteBuffer: ByteBuffer): CompletableFuture { - val message = BufferOutputMessage(byteBuffer) - messages.offer(message) - writeOutputMessage() - - val future = CompletableFuture() - future.complete(byteBuffer.remaining()) - return future - } - - override fun isOpen(): Boolean = closed.get() - - override fun closeAsync(): CompletableFuture { - if (closed.compareAndSet(false, true)) { - writeOutputMessage() - val trailers = response.trailerSupplier?.get() - if (trailers != null) { - val trailerMetaData = MetaData.Response(trailers) - trailerMetaData.isOnlyTrailer = true - val headersFrameTrailer = HeadersFrame(stream.id, trailerMetaData, null, true) - stream.headers(headersFrameTrailer, discard()) - } - } - return Result.DONE - } - - override fun close() { - closeAsync() - } - - private fun writeOutputMessage() { - val message = messages.poll() - if (message != null) { - val last = messages.isEmpty() && response.trailerSupplier == null - when (message) { - is HeadersOutputMessage -> writeHeaders(last) - is BufferOutputMessage -> writeBuffer(message, last) - is BuffersOutputMessage -> writeBuffers(message, last) - } - } - } - - private fun writeHeaders(last: Boolean) { - val headersFrame = HeadersFrame(stream.id, response, null, last) - stream.headers(headersFrame, discard()) - } - - private fun writeBuffer(message: BufferOutputMessage, last: Boolean) { - val dataFrame = DataFrame(stream.id, message.byteBuffer, last) - stream.data(dataFrame, discard()) - } - - private fun writeBuffers(message: BuffersOutputMessage, last: Boolean) { - val length = message.getCurrentLength() - when { - length == 1 -> { - val byteBuffer = message.byteBuffers[message.getCurrentOffset()] - val dataFrame = DataFrame(stream.id, byteBuffer, last) - stream.data(dataFrame, discard()) - } - length > 1 -> { - while (message.hasRemaining()) { - val remaining = message.remaining() - val size = remaining.coerceAtMost(defaultMaxFrameSize).toInt() - val buffer = BufferUtils.allocate(size) - val pos = buffer.flipToFill() - - log.debug { "HTTP2 outputs buffer array. before remaining: $remaining, size: $size" } - while (buffer.hasRemaining()) { - val offset = message.getCurrentOffset() - val src = message.byteBuffers[offset] - BufferUtils.put(src, buffer) - log.debug { "HTTP2 outputs buffer array. put offset: $offset" } - } - - buffer.flipToFlush(pos) - val end = last && !message.hasRemaining() - val dataFrame = DataFrame(stream.id, buffer, end) - stream.data(dataFrame, discard()) - log.debug { "HTTP2 outputs buffer array. after remaining: ${message.remaining()}, end: $end" } - } - } - } - } - -} - -sealed interface Http2OutputMessage - -object HeadersOutputMessage : Http2OutputMessage - -@JvmInline -value class BufferOutputMessage(val byteBuffer: ByteBuffer) : Http2OutputMessage - -class BuffersOutputMessage( - val byteBuffers: Array, val offset: Int, val length: Int, - private val delegatedBufferArray: DelegatedOutputBufferArray = DelegatedOutputBufferArray( - byteBuffers, offset, length, discard() - ) -) : OutputBufferArray by delegatedBufferArray, Http2OutputMessage \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerResponse.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerResponse.kt deleted file mode 100644 index 51bc2dc42..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/Http2ServerResponse.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.HttpVersion -import com.fireflysource.net.http.common.model.MetaData -import com.fireflysource.net.http.common.v2.frame.HeadersFrame -import com.fireflysource.net.http.common.v2.stream.Stream -import com.fireflysource.net.http.server.HttpServerOutputChannel -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean - -/** - * @author Pengtao Qiu - */ -class Http2ServerResponse( - http2ServerConnection: Http2ServerConnection, - private val stream: Stream -) : AbstractHttpServerResponse(http2ServerConnection) { - - private val write100Continue = AtomicBoolean(false) - - override fun createHttpServerOutputChannel(response: MetaData.Response): HttpServerOutputChannel { - return Http2ServerOutputChannel(response, stream) - } - - override fun response100Continue(): CompletableFuture { - return if (write100Continue.compareAndSet(false, true)) { - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.CONTINUE_100, HttpFields()) - val headers = HeadersFrame(stream.id, response, null, false) - val future = CompletableFuture() - stream.headers(headers, Result.futureToConsumer(future)) - future - } else Result.DONE - } - - override fun response200ConnectionEstablished(): CompletableFuture = Result.DONE -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/AsyncMultiPart.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/AsyncMultiPart.kt deleted file mode 100644 index ec7605133..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/AsyncMultiPart.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.deleteIfExistsAsync -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.content.handler.AbstractByteBufferContentHandler -import com.fireflysource.net.http.common.content.handler.AbstractFileContentHandler -import com.fireflysource.net.http.common.content.provider.AbstractByteBufferContentProvider -import com.fireflysource.net.http.common.content.provider.AbstractFileContentProvider -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.MultiPart -import kotlinx.coroutines.future.asCompletableFuture -import kotlinx.coroutines.future.await -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.file.Path -import java.nio.file.StandardOpenOption -import java.util.concurrent.CompletableFuture - -class AsyncMultiPart( - private val maxFileSize: Long, - private val fileSizeThreshold: Int, - private val path: Path -) : MultiPart { - - companion object { - private val log = SystemLogger.create(AsyncMultiPart::class.java) - } - - private val byteBufferHandler = object : AbstractByteBufferContentHandler() {} - private var fileHandler: AbstractFileContentHandler? = null - private var fileHandlerFuture: CompletableFuture? = null - private val fileProvider: AbstractFileContentProvider by lazy { - object : AbstractFileContentProvider(path, StandardOpenOption.READ) {} - } - private val byteBufferProvider: AbstractByteBufferContentProvider by lazy { - val size = byteBufferHandler.getByteBuffers().sumOf { it.remaining() } - val buf = BufferUtils.allocate(size) - val pos = buf.flipToFill() - byteBufferHandler.getByteBuffers().forEach { BufferUtils.put(it, buf) } - buf.flipToFlush(pos) - object : AbstractByteBufferContentProvider(buf) {} - } - private val httpFields = HttpFields() - private var size: Long = 0 - private var name: String = "" - private var fileName: String = "" - private var exceededFileThreshold = false - - override fun getName(): String = name - - fun setName(name: String) { - this.name = name - } - - override fun getFileName(): String = fileName - - fun setFileName(fileName: String) { - this.fileName = fileName - } - - override fun getHttpFields(): HttpFields = httpFields - - override fun getSize(): Long = size - - fun accept(item: ByteBuffer, last: Boolean) { - size += item.remaining() - log.debug { "Multi part accepts data. name: $name size: ${item.remaining()}, last: $last" } - if (size > maxFileSize) { - throw BadMessageException(HttpStatus.PAYLOAD_TOO_LARGE_413) - } - - if (size <= fileSizeThreshold) { - if (item.hasRemaining()) byteBufferHandler.accept(item, null) - } else { - val fileHandler = this.fileHandler - if (fileHandler != null) { - if (item.hasRemaining()) fileHandler.accept(item, null) - } else { - exceededFileThreshold = true - val newFileHandler = object : AbstractFileContentHandler( - path, - StandardOpenOption.WRITE, - StandardOpenOption.CREATE_NEW - ) {} - this.fileHandler = newFileHandler - byteBufferHandler.getByteBuffers().forEach { newFileHandler.accept(it, null) } - if (item.hasRemaining()) newFileHandler.accept(item, null) - } - - if (last) { - fileHandlerFuture = this.fileHandler?.closeAsync() - } - } - } - - suspend fun closeFileHandler() { - fileHandler?.closeAsync()?.await() - } - - override fun getContentType(): String = httpFields[HttpHeader.CONTENT_TYPE] - - override fun read(byteBuffer: ByteBuffer): CompletableFuture { - return getProvider().read(byteBuffer) - } - - override fun getStringBody(charset: Charset): String { - return if (exceededFileThreshold) "" - else { - val buffer = byteBufferProvider.toByteBuffer() - BufferUtils.toString(buffer, charset) - } - } - - override fun isOpen(): Boolean { - return getProvider().isOpen - } - - override fun close() { - closeAsync() - } - - override fun closeAsync(): CompletableFuture { - return getProvider().closeAsync() - .thenCompose { deleteIfExistsAsync(path).asCompletableFuture() } - .thenCompose { Result.DONE } - } - - private fun getProvider() = if (exceededFileThreshold) fileProvider else byteBufferProvider - - override fun toString(): String { - return "AsyncMultiPart(maxFileSize=$maxFileSize, fileSizeThreshold=$fileSizeThreshold, path=$path, httpFields=${httpFields.size()}, size=$size, name='$name', fileName='$fileName', exceededFileThreshold=$exceededFileThreshold)" - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/ByteBufferContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/ByteBufferContentHandler.kt deleted file mode 100644 index 2ddc88250..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/ByteBufferContentHandler.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.net.http.common.content.handler.AbstractByteBufferContentHandler -import com.fireflysource.net.http.server.HttpServerContentHandler -import com.fireflysource.net.http.server.RoutingContext - -open class ByteBufferContentHandler(maxRequestBodySize: Long = 200 * 1024 * 1024) : - AbstractByteBufferContentHandler(maxRequestBodySize), HttpServerContentHandler \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/FileContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/FileContentHandler.kt deleted file mode 100644 index 4b5b07365..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/FileContentHandler.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.net.http.common.content.handler.AbstractFileContentHandler -import com.fireflysource.net.http.server.HttpServerContentHandler -import com.fireflysource.net.http.server.RoutingContext -import java.nio.file.OpenOption -import java.nio.file.Path - -class FileContentHandler(path: Path, vararg options: OpenOption) : - AbstractFileContentHandler(path, *options), HttpServerContentHandler \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/FormInputsContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/FormInputsContentHandler.kt deleted file mode 100644 index c2ecf3f9d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/FormInputsContentHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.net.http.common.codec.UrlEncoded -import com.fireflysource.net.http.common.model.ContentEncoding -import java.nio.charset.StandardCharsets -import java.util.* - -class FormInputsContentHandler(maxRequestBodySize: Long = 200 * 1024 * 1024) : - StringContentHandler(maxRequestBodySize) { - - private var urlEncoded: UrlEncoded? = null - - fun getFormInput(name: String, encoding: Optional = Optional.empty()): String { - return getUrlEncoded(encoding).getString(name) ?: "" - } - - fun getFormInputs(name: String, encoding: Optional = Optional.empty()): List { - return getUrlEncoded(encoding)[name] ?: listOf() - } - - fun getFormInputs(encoding: Optional = Optional.empty()): Map> = - getUrlEncoded(encoding) - - private fun getUrlEncoded(encoding: Optional): UrlEncoded { - val e = urlEncoded - return if (e == null) { - val encoded = UrlEncoded(this.toString(StandardCharsets.UTF_8, encoding)) - urlEncoded = encoded - encoded - } else e - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/MultiPartContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/MultiPartContentHandler.kt deleted file mode 100644 index 8b4dd0250..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/MultiPartContentHandler.kt +++ /dev/null @@ -1,315 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.common.concurrent.CompletableFutures -import com.fireflysource.common.coroutine.clear -import com.fireflysource.common.string.QuotedStringTokenizer -import com.fireflysource.common.string.QuotedStringTokenizer.unquote -import com.fireflysource.common.string.QuotedStringTokenizer.unquoteOnly -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.http.common.codec.MultiPartParser -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.HttpServerContentHandler -import com.fireflysource.net.http.server.MultiPart -import com.fireflysource.net.http.server.RoutingContext -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.asCompletableFuture -import java.nio.ByteBuffer -import java.nio.file.Path -import java.nio.file.Paths -import java.util.* -import java.util.concurrent.CompletableFuture - -class MultiPartContentHandler( - private val maxUploadFileSize: Long = 200 * 1024 * 1024, - private val maxRequestBodySize: Long = 200 * 1024 * 1024, - private val uploadFileSizeThreshold: Int = 4 * 1024 * 1024, - private val scope: CoroutineScope = CoroutineScope(CoroutineName("Firefly-file-content-provider")), - private val path: Path = tempPath -) : HttpServerContentHandler { - - companion object { - private val log = SystemLogger.create(MultiPartContentHandler::class.java) - private val tempPath = Paths.get(System.getProperty("java.io.tmpdir")) - } - - init { - require(maxRequestBodySize >= maxUploadFileSize) { "The max request size must be greater than the max file size." } - require(maxUploadFileSize >= uploadFileSizeThreshold) { "The max file size must be greater than the file size threshold." } - } - - private val multiParts: LinkedList = LinkedList() - private val multiPartChannel: Channel = Channel(Channel.UNLIMITED) - private var firstMessage = true - private var requestSize: Long = 0 - private var parser: MultiPartParser? = null - private var byteBuffers: LinkedList = LinkedList() - private val multiPartHandler = MultiPartHandler() - private var job: Job? = null - private var part: AsyncMultiPart? = null - private var parsingException: Throwable? = null - - private inner class MultiPartHandler : MultiPartParser.Handler { - - override fun startPart() { - multiPartChannel.trySend(StartPart) - } - - override fun parsedField(name: String, value: String) { - multiPartChannel.trySend(PartField(name, value)) - } - - override fun headerComplete(): Boolean { - multiPartChannel.trySend(PartHeaderComplete) - return false - } - - override fun content(item: ByteBuffer, last: Boolean): Boolean { - multiPartChannel.trySend(PartContent(item, last)) - return false - } - - override fun messageComplete(): Boolean { - multiPartChannel.trySend(PartMessageComplete) - return true - } - - override fun earlyEOF() { - multiPartChannel.trySend(PartEarlyEOF) - } - - } - - private fun parsingJob() = scope.launch { - parseLoop@ while (true) { - try { - when (val message = multiPartChannel.receive()) { - is ParseMultiPartBoundary -> parseBoundaryAndCreateMultiPartParser(message) - is ParseMultiPartContent -> parseContent(message) - is EndMultiPartHandler -> endMultiPartHandler() - is StartPart -> createMultiPart() - is PartField -> addMultiPartField(message) - is PartHeaderComplete -> handleHeaderComplete() - is PartContent -> acceptContent(message) - is PartMessageComplete -> { - handleMessageComplete() - break@parseLoop - } - is PartEarlyEOF -> handleEarlyEOF() - } - } catch (e: Throwable) { - this@MultiPartContentHandler.parsingException = e - break@parseLoop - } - } - - multiPartChannel.clear() - val result = runCatching { closeAllFileHandlers() } - log.debug { "close file handlers. result: ${result.isSuccess}" } - } - - - private fun createMultiPart() { - val part = - AsyncMultiPart( - maxUploadFileSize, - uploadFileSizeThreshold, - Paths.get(path.toString(), UUID.randomUUID().toString()) - ) - multiParts.add(part) - this.part = part - log.debug { "Create multi-part. $part" } - } - - private fun addMultiPartField(message: PartField) { - part?.httpFields?.add(message.name, message.value) - } - - private fun handleHeaderComplete() { - val contentDisposition = part?.httpFields?.get("Content-Disposition") - requireNotNull(contentDisposition) { "Missing Content-Disposition header" } - - val token = QuotedStringTokenizer(contentDisposition, ";", false, true) - var formData = false - var name: String? = null - var fileName: String? = null - while (token.hasMoreTokens()) { - val tokenValue = token.nextToken().trim() - val lowerCaseValue = tokenValue.lowercase(Locale.getDefault()) - when { - lowerCaseValue.startsWith("form-data") -> formData = true - lowerCaseValue.startsWith("name=") -> name = value(tokenValue) - lowerCaseValue.startsWith("filename=") -> fileName = fileNameValue(tokenValue) - } - } - - require(formData) { "Part not form-data" } - requireNotNull(name) { "No name in part" } - part?.name = name - part?.fileName = fileName ?: "" - log.debug { "Multi-part header complete. name: $name, fileName: $fileName, fields: ${part?.httpFields?.size()}" } - } - - private fun acceptContent(message: PartContent) { - log.debug { "Accept multi-part content. size: ${message.byteBuffer.remaining()}, last: ${message.last}" } - part?.accept(message.byteBuffer, message.last) - } - - private suspend fun handleMessageComplete() { - val result = runCatching { closeAllFileHandlers() } - log.debug { "Multi-part complete. part: $part. result: ${result.isSuccess}" } - } - - private suspend fun handleEarlyEOF() { - val result = runCatching { closeAllFileHandlers() } - log.debug { "Early EOF. result: ${result.isSuccess}" } - throw BadMessageException(HttpStatus.BAD_REQUEST_400) - } - - private suspend fun closeAllFileHandlers() { - var ex: Exception? = null - multiParts.forEach { - try { - it.closeFileHandler() - } catch (e: Exception) { - ex = e - } - } - val e = ex - if (e != null) { - throw e - } - } - - private fun parseBoundary(contentType: String?): String { - if (contentType == null) return "" - if (!contentType.startsWith("multipart/form-data")) return "" - - var contentTypeBoundary = "" - val start: Int = contentType.indexOf("boundary=") - if (start >= 0) { - var end: Int = contentType.indexOf(";", start) - end = if (end < 0) contentType.length else end - contentTypeBoundary = unquote(value(contentType.substring(start, end)).trim()) - } - return contentTypeBoundary - } - - private fun parseBoundaryAndCreateMultiPartParser(message: ParseMultiPartBoundary) { - val boundary = parseBoundary(message.contentType) - log.debug { "Parsed multi-part boundary: $boundary" } - if (boundary.isNotBlank()) { - parser = MultiPartParser(multiPartHandler, boundary) - } else { - throw BadMessageException(HttpStatus.BAD_REQUEST_400) - } - } - - private fun parseContent(message: ParseMultiPartContent) { - byteBuffers.offer(message.byteBuffer) - log.debug { "Parse multi part content. state: ${parser?.state}, size: ${message.byteBuffer.remaining()}" } - if (byteBuffers.size > 1) { - parser?.parse(byteBuffers.poll(), false) - } - } - - private fun endMultiPartHandler() { - val buffer = byteBuffers.poll() - requireNotNull(buffer) - log.debug { "End multi-part handler. buffers: ${byteBuffers.size}, size: ${buffer.remaining()}" } - parser?.parse(buffer, true) - } - - private fun value(headerLine: String): String { - val idx = headerLine.indexOf('=') - val value = headerLine.substring(idx + 1).trim() - return unquoteOnly(value) - } - - private fun fileNameValue(headerLine: String): String { - val idx = headerLine.indexOf('=') - var value = headerLine.substring(idx + 1).trim() - - return if (value.matches(".??[a-z,A-Z]:\\\\[^\\\\].*".toRegex())) { - // incorrectly escaped IE filenames that have the whole path - // we just strip any leading & trailing quotes and leave it as is - val first = value[0] - if (first == '"' || first == '\'') value = value.substring(1) - val last = value[value.length - 1] - if (last == '"' || last == '\'') value = value.substring(0, value.length - 1) - value - } else unquoteOnly(value, true) - // unquote the string, but allow any backslashes that don't - // form a valid escape sequence to remain as many browsers - // even on *nix systems will not escape a filename containing - // backslashes - } - - override fun accept(byteBuffer: ByteBuffer, ctx: RoutingContext) { - requestSize += byteBuffer.remaining() - if (requestSize > maxRequestBodySize) { - throw BadMessageException(HttpStatus.PAYLOAD_TOO_LARGE_413) - } - - if (firstMessage) { - job = parsingJob() - multiPartChannel.trySend(ParseMultiPartBoundary(ctx.httpFields[HttpHeader.CONTENT_TYPE])) - firstMessage = false - } - multiPartChannel.trySend(ParseMultiPartContent(byteBuffer)) - } - - override fun closeAsync(): CompletableFuture { - val parsingJob = job - return if (parsingJob != null) { - if (parsingJob.isCompleted) complete() - else scope.launch { closeAwait() }.asCompletableFuture().thenCompose { complete() } - .thenAccept { scope.cancel() } - } else Result.DONE - } - - private fun complete(): CompletableFuture { - val e = parsingException - return if (e != null) CompletableFutures.failedFuture(e) else Result.DONE - } - - private suspend fun closeAwait() { - multiPartChannel.trySend(EndMultiPartHandler) - job?.join() - } - - override fun close() { - closeAsync() - } - - fun getPart(name: String): MultiPart? { - return multiParts.find { it.name == name } - } - - fun getParts(): List = multiParts -} - -sealed interface MultiPartHandlerMessage - -@JvmInline -value class ParseMultiPartBoundary(val contentType: String?) : MultiPartHandlerMessage - -@JvmInline -value class ParseMultiPartContent(val byteBuffer: ByteBuffer) : MultiPartHandlerMessage - -object StartPart : MultiPartHandlerMessage - -data class PartField(val name: String, val value: String) : MultiPartHandlerMessage - -object PartHeaderComplete : MultiPartHandlerMessage - -class PartContent(val byteBuffer: ByteBuffer, val last: Boolean) : MultiPartHandlerMessage - -object PartMessageComplete : MultiPartHandlerMessage - -object PartEarlyEOF : MultiPartHandlerMessage - -object EndMultiPartHandler : MultiPartHandlerMessage \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/StringContentHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/StringContentHandler.kt deleted file mode 100644 index f2ef8fbce..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/handler/StringContentHandler.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -open class StringContentHandler(maxRequestBodySize: Long = 200 * 1024 * 1024) : - ByteBufferContentHandler(maxRequestBodySize) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/ByteBufferContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/ByteBufferContentProvider.kt deleted file mode 100644 index 96b43b6ef..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/ByteBufferContentProvider.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.provider - -import com.fireflysource.net.http.common.content.provider.AbstractByteBufferContentProvider -import com.fireflysource.net.http.server.HttpServerContentProvider -import java.nio.ByteBuffer - -class ByteBufferContentProvider(content: ByteBuffer) : AbstractByteBufferContentProvider(content), - HttpServerContentProvider \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/DefaultContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/DefaultContentProvider.kt deleted file mode 100644 index c51ef6b9f..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/DefaultContentProvider.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.ProjectVersion -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.HttpServerContentProvider -import com.fireflysource.net.http.server.RoutingContext -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets -import java.util.* -import java.util.concurrent.CompletableFuture - -class DefaultContentProvider( - private val status: Int, - private val exception: Throwable?, - private val ctx: RoutingContext -) : HttpServerContentProvider { - - private val html = """ - | - | - | - |${getTitle()} - | - | - |

    ${getTitle()}

    - |

    ${getContent()}

    - |
    - |
    powered by Firefly ${ProjectVersion.getValue()}
    - | - | - """.trimMargin() - private val contentByteBuffer = BufferUtils.toBuffer(html, StandardCharsets.UTF_8) - private val provider: ByteBufferContentProvider = ByteBufferContentProvider(contentByteBuffer) - - private fun getTitle(): String { - return "$status ${getCode().message}" - } - - private fun getCode(): HttpStatus.Code { - return Optional.ofNullable(HttpStatus.getCode(status)).orElse(HttpStatus.Code.INTERNAL_SERVER_ERROR) - } - - private fun getContent(): String { - return when (getCode()) { - HttpStatus.Code.NOT_FOUND -> "The resource ${ctx.uri.path} is not found" - HttpStatus.Code.INTERNAL_SERVER_ERROR -> "The server internal error.
    ${exception?.message}" - else -> "${getTitle()}
    ${exception?.message}" - } - } - - override fun length(): Long = provider.length() - - override fun isOpen(): Boolean = provider.isOpen - - override fun toByteBuffer(): ByteBuffer = provider.toByteBuffer() - - override fun closeAsync(): CompletableFuture = provider.closeAsync() - - override fun close() = provider.close() - - override fun read(byteBuffer: ByteBuffer): CompletableFuture = provider.read(byteBuffer) -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/FileContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/FileContentProvider.kt deleted file mode 100644 index e66399a1e..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/FileContentProvider.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.provider - -import com.fireflysource.net.http.common.content.provider.AbstractFileContentProvider -import com.fireflysource.net.http.server.HttpServerContentProvider -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import java.nio.file.Files -import java.nio.file.OpenOption -import java.nio.file.Path - -class FileContentProvider( - path: Path, - options: Set, - position: Long, - length: Long, - scope: CoroutineScope = CoroutineScope(CoroutineName("Firefly-file-content-provider")) -) : AbstractFileContentProvider(path, options, position, length, scope), HttpServerContentProvider { - - constructor(path: Path, vararg options: OpenOption) : this(path, options.toSet(), 0, Files.size(path)) - - constructor(path: Path, scope: CoroutineScope, vararg options: OpenOption) : this( - path, - options.toSet(), - 0, - Files.size(path), - scope - ) - - constructor( - path: Path, - options: Set, - position: Long, - length: Long - ) : this(path, options, position, length, CoroutineScope(CoroutineName("Firefly-file-content-provider"))) -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/StringContentProvider.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/StringContentProvider.kt deleted file mode 100644 index 5da9b4ac1..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/content/provider/StringContentProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.common.content.provider.AbstractByteBufferContentProvider -import com.fireflysource.net.http.server.HttpServerContentProvider -import java.nio.charset.Charset - -class StringContentProvider(val content: String, val charset: Charset) : - AbstractByteBufferContentProvider(BufferUtils.toBuffer(content, charset)), HttpServerContentProvider \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/ProxyAuthException.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/ProxyAuthException.kt deleted file mode 100644 index 2f27a6856..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/ProxyAuthException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fireflysource.net.http.server.impl.exception - -/** - * @author Pengtao Qiu - */ -class ProxyAuthException(message: String) : IllegalArgumentException(message) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/RouterNotCommitException.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/RouterNotCommitException.kt deleted file mode 100644 index 100734657..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/RouterNotCommitException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fireflysource.net.http.server.impl.exception - -/** - * @author Pengtao Qiu - */ -class RouterNotCommitException(message: String) : IllegalStateException(message) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/RouterUrlFormatException.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/RouterUrlFormatException.kt deleted file mode 100644 index 84a88249e..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/exception/RouterUrlFormatException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.fireflysource.net.http.server.impl.exception - -class RouterUrlFormatException(message: String) : IllegalArgumentException(message) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractMatcher.kt deleted file mode 100644 index 0b6432c69..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractMatcher.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Router -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import java.util.* - -abstract class AbstractMatcher(val routersMap: MutableMap> = HashMap()) { - - fun copyRouterMap(manager: AsyncRouterManager): MutableMap> { - val newRoutersMap: MutableMap> = HashMap() - routersMap.forEach { (key, routerSet) -> - val newRouterSet: SortedSet = TreeSet() - routerSet.forEach { router -> - if (router is AsyncRouter) { - val newRouter = router.copy(manager) - newRouterSet.add(newRouter) - } - } - newRoutersMap[key] = newRouterSet - } - return newRoutersMap - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractPatternMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractPatternMatcher.kt deleted file mode 100644 index 12fefc85f..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractPatternMatcher.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.common.string.Pattern -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Router -import java.util.* - - -abstract class AbstractPatternMatcher : AbstractMatcher(), Matcher { - - companion object { - const val paramName = "param" - } - - class PatternRule(val rule: String) { - - val pattern: Pattern = Pattern.compile(rule, "*") - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as PatternRule - return rule == other.rule - } - - override fun hashCode(): Int { - return rule.hashCode() - } - } - - override fun add(rule: String, router: Router) { - routersMap.computeIfAbsent(PatternRule(rule)) { TreeSet() }.add(router) - } - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val routers = TreeSet() - val parameters = HashMap>() - - routersMap.forEach { (rule, routerSet) -> - val strings: Array? = rule.pattern.match(value) - if (strings != null) { - routers.addAll(routerSet) - if (strings.isNotEmpty()) { - val param: MutableMap = HashMap() - for (i in strings.indices) { - param["$paramName$i"] = strings[i] - } - routerSet.forEach { router -> parameters[router] = param } - } - } - } - return if (routers.isEmpty()) null else Matcher.MatchResult(routers, parameters, matchType) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractPreciseMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractPreciseMatcher.kt deleted file mode 100644 index 0e199e7e4..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractPreciseMatcher.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Router -import java.util.* - -abstract class AbstractPreciseMatcher : AbstractMatcher(), Matcher { - - override fun add(rule: String, router: Router) { - routersMap.computeIfAbsent(rule) { TreeSet() }.add(router) - } - - override fun match(value: String): Matcher.MatchResult? { - val routers = routersMap[value] - return if (!routers.isNullOrEmpty()) { - Matcher.MatchResult(routers, emptyMap(), matchType) - } else null - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractRegexMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractRegexMatcher.kt deleted file mode 100644 index 4414e9041..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AbstractRegexMatcher.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Router -import java.util.* -import java.util.regex.Pattern - - -abstract class AbstractRegexMatcher :AbstractMatcher(), Matcher { - - companion object { - const val paramName = "group" - } - - class RegexRule(val rule: String) { - val pattern: Pattern = Pattern.compile(rule) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as RegexRule - return rule == other.rule - } - - override fun hashCode(): Int { - return rule.hashCode() - } - } - - override fun add(rule: String, router: Router) { - routersMap.computeIfAbsent(RegexRule(rule)) { TreeSet() }.add(router) - } - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val routers = TreeSet() - val parameters = HashMap>() - - routersMap.forEach { (rule, routerSet) -> - var matcher = rule.pattern.matcher(value) - if (matcher.matches()) { - routers.addAll(routerSet) - matcher = rule.pattern.matcher(value) - val param: MutableMap = HashMap() - while (matcher.find()) { - for (i in 1..matcher.groupCount()) { - param["$paramName$i"] = matcher.group(i) - } - } - if (param.isNotEmpty()) { - routerSet.forEach { router -> parameters[router] = param } - } - } - } - - return if (routers.isEmpty()) null else Matcher.MatchResult(routers, parameters, matchType) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AcceptHeaderMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AcceptHeaderMatcher.kt deleted file mode 100644 index 7235156bb..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/AcceptHeaderMatcher.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.common.string.StringUtils -import com.fireflysource.net.http.common.model.AcceptMIMEMatchType -import com.fireflysource.net.http.common.model.AcceptMIMEType -import com.fireflysource.net.http.common.model.MimeTypes.parseAcceptMIMETypes -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Matcher.MatchType -import com.fireflysource.net.http.server.Router -import java.util.* - - -class AcceptHeaderMatcher : AbstractPreciseMatcher() { - - override fun getMatchType(): MatchType { - return MatchType.ACCEPT - } - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val acceptMIMETypes = parseAcceptMIMETypes(value) - if (acceptMIMETypes.isNullOrEmpty()) return null - - return acceptMIMETypes - .map { findRouters(it) } - .filter { !it.isEmpty() } - .map { Matcher.MatchResult(it, emptyMap(), matchType) } - .firstOrNull() - } - - private fun findRouters(type: AcceptMIMEType) = - routersMap.entries.filter { matchMimeType(it, type) }.flatMap { it.value }.toSortedSet() - - private fun matchMimeType(e: MutableMap.MutableEntry>, type: AcceptMIMEType): Boolean { - val acceptType: Array = StringUtils.split(e.key, '/') - val parentType = acceptType[0].trim() - val childType = acceptType[1].trim() - return when (type.matchType) { - AcceptMIMEMatchType.EXACT -> parentType == type.parentType && childType == type.childType - AcceptMIMEMatchType.CHILD -> childType == type.childType - AcceptMIMEMatchType.PARENT -> parentType == type.parentType - AcceptMIMEMatchType.ALL -> true - else -> false - } - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/HttpMethodMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/HttpMethodMatcher.kt deleted file mode 100644 index f4862b230..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/HttpMethodMatcher.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Router -import java.util.* - -class HttpMethodMatcher : AbstractPreciseMatcher() { - - override fun add(rule: String, router: Router) { - super.add(rule.uppercase(Locale.getDefault()), router) - } - - override fun match(value: String): Matcher.MatchResult? { - return if (routersMap.isEmpty()) null else super.match(value.uppercase(Locale.getDefault())) - } - - override fun getMatchType(): Matcher.MatchType { - return Matcher.MatchType.METHOD - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/ParameterPathMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/ParameterPathMatcher.kt deleted file mode 100644 index 386bd5a9b..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/ParameterPathMatcher.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Router -import java.util.* - - -class ParameterPathMatcher : AbstractMatcher(), Matcher { - - companion object { - fun isParameterPath(path: String): Boolean { - val paths = split(path) - return paths.any { it[0] == ':' } - } - - fun split(path: String): List { - val paths: MutableList = LinkedList() - var start = 1 - val last = path.lastIndex - - for (i in 1..last) { - if (path[i] == '/') { - paths.add(path.substring(start, i).trim()) - start = i + 1 - } - } - - if (path[last] != '/') { - paths.add(path.substring(start).trim()) - } - return paths - } - } - - inner class ParameterRule(val rule: String) { - - val paths = split(rule) - - fun match(list: List): Map { - if (paths.size != list.size) return emptyMap() - - val param: MutableMap = HashMap() - for (i in list.indices) { - val path = paths[i] - val value = list[i] - if (path[0] != ':') { - if (path != value) { - return emptyMap() - } - } else { - param[path.substring(1)] = value - } - } - return param - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ParameterRule - return rule == other.rule - } - - override fun hashCode(): Int { - return rule.hashCode() - } - } - - override fun getMatchType(): Matcher.MatchType { - return Matcher.MatchType.PATH - } - - override fun add(rule: String, router: Router) { - val parameterRule = ParameterRule(rule) - routersMap.computeIfAbsent(parameterRule) { TreeSet() }.add(router) - } - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val routers = TreeSet() - val parameters = HashMap>() - val paths = split(value) - - routersMap.forEach { (rule, routerSet) -> - val param = rule.match(paths) - if (param.isNotEmpty()) { - routers.addAll(routerSet) - routerSet.forEach { router -> parameters[router] = param } - } - } - return if (routers.isEmpty()) null else Matcher.MatchResult(routers, parameters, matchType) - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PatternedContentTypeMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PatternedContentTypeMatcher.kt deleted file mode 100644 index 49edad919..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PatternedContentTypeMatcher.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.common.string.StringUtils -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Matcher.MatchType - - -class PatternedContentTypeMatcher : AbstractPatternMatcher() { - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val mimeType = MimeTypes.getContentTypeMIMEType(value) - return if (StringUtils.hasText(mimeType)) super.match(mimeType) else null - } - - override fun getMatchType(): MatchType { - return MatchType.CONTENT_TYPE - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PatternedPathMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PatternedPathMatcher.kt deleted file mode 100644 index 5f69f7675..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PatternedPathMatcher.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher - -class PatternedPathMatcher : AbstractPatternMatcher() { - - override fun getMatchType(): Matcher.MatchType { - return Matcher.MatchType.PATH - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PreciseContentTypeMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PreciseContentTypeMatcher.kt deleted file mode 100644 index be1bef2cd..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PreciseContentTypeMatcher.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.common.string.StringUtils -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.Matcher - -class PreciseContentTypeMatcher : AbstractPreciseMatcher() { - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val mimeType = MimeTypes.getContentTypeMIMEType(value) - return if (StringUtils.hasText(mimeType)) super.match(mimeType) else null - } - - override fun getMatchType(): Matcher.MatchType { - return Matcher.MatchType.CONTENT_TYPE - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PrecisePathMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PrecisePathMatcher.kt deleted file mode 100644 index c00112969..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/PrecisePathMatcher.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Matcher.MatchType -import com.fireflysource.net.http.server.Router - -class PrecisePathMatcher : AbstractPreciseMatcher() { - - override fun add(rule: String, router: Router) { - val path = toPath(rule) - super.add(path, router) - } - - override fun match(value: String): Matcher.MatchResult? { - if (routersMap.isEmpty()) return null - - val path = toPath(value) - return super.match(path) - } - - override fun getMatchType(): MatchType { - return MatchType.PATH - } - - private fun toPath(value: String): String = if (value.last() != '/') "$value/" else value -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/RegexPathMatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/RegexPathMatcher.kt deleted file mode 100644 index a9dd77e86..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/matcher/RegexPathMatcher.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher - -class RegexPathMatcher : AbstractRegexMatcher() { - - override fun getMatchType(): Matcher.MatchType { - return Matcher.MatchType.PATH - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRouter.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRouter.kt deleted file mode 100644 index 1ad804ebf..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRouter.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.fireflysource.net.http.server.impl.router - -import com.fireflysource.common.coroutine.CoroutineDispatchers -import com.fireflysource.common.coroutine.CoroutineLocalContext -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.Router -import com.fireflysource.net.http.server.Router.EMPTY_HANDLER -import com.fireflysource.net.http.server.RoutingContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -class AsyncRouter( - private val id: Int, - private val routerManager: AsyncRouterManager -) : Router { - - private val matchTypes = HashSet() - private var handler: Router.Handler = EMPTY_HANDLER - private var enabled = true - - override fun method(httpMethod: String): Router { - routerManager.method(httpMethod, this) - matchTypes.add(Matcher.MatchType.METHOD) - return this - } - - override fun method(httpMethod: HttpMethod): Router { - routerManager.method(httpMethod.value, this) - matchTypes.add(Matcher.MatchType.METHOD) - return this - } - - override fun path(url: String): Router { - routerManager.path(url, this) - matchTypes.add(Matcher.MatchType.PATH) - return this - } - - override fun paths(urlList: MutableList): Router { - routerManager.paths(urlList, this) - matchTypes.add(Matcher.MatchType.PATH) - return this - } - - override fun pathRegex(regex: String): Router { - routerManager.pathRegex(regex, this) - matchTypes.add(Matcher.MatchType.PATH) - return this - } - - override fun get(url: String): Router { - return method(HttpMethod.GET).path(url) - } - - override fun post(url: String): Router { - return method(HttpMethod.POST).path(url) - } - - override fun put(url: String): Router { - return method(HttpMethod.PUT).path(url) - } - - override fun delete(url: String): Router { - return method(HttpMethod.DELETE).path(url) - } - - override fun consumes(contentType: String): Router { - routerManager.consumes(contentType, this) - matchTypes.add(Matcher.MatchType.CONTENT_TYPE) - return this - } - - override fun produces(accept: String): Router { - routerManager.produces(accept, this) - matchTypes.add(Matcher.MatchType.ACCEPT) - return this - } - - override fun getId(): Int = id - - override fun compareTo(other: Router): Int = id.compareTo(other.id) - - override fun handler(handler: Router.Handler): HttpServer { - this.handler = handler - return routerManager.httpServer - } - - fun getHandler() = this.handler - - override fun getMatchTypes(): MutableSet = matchTypes - - override fun enable(): Router { - enabled = true - return this - } - - override fun isEnable(): Boolean = enabled - - override fun disable(): Router { - enabled = false - return this - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AsyncRouter - return id == other.id - } - - override fun hashCode(): Int { - return id - } - - fun copy(manager: AsyncRouterManager): AsyncRouter { - val router = AsyncRouter(this.id, manager) - router.matchTypes.addAll(this.matchTypes) - router.enabled = this.enabled - router.handler = this.handler - return router - } -} - -private const val serverHandlerCoroutineContextKey = "_serverHandlerCoroutineContextKey" - -fun getCurrentRoutingContext(): RoutingContext? = CoroutineLocalContext.getAttr(serverHandlerCoroutineContextKey) - -fun Router.asyncHandler(block: suspend CoroutineScope.(RoutingContext) -> Unit): HttpServer { - return this.handler { ctx -> - ctx.connection.coroutineScope - .launch(CoroutineLocalContext.asElement(mutableMapOf(serverHandlerCoroutineContextKey to ctx))) { block(ctx) } - .asVoidFuture() - } -} - -fun Router.asyncBlockingHandler(block: suspend CoroutineScope.(RoutingContext) -> Unit): HttpServer { - return this.handler { ctx -> - ctx.connection.coroutineScope - .launch(CoroutineLocalContext.asElement(mutableMapOf(serverHandlerCoroutineContextKey to ctx)) + CoroutineDispatchers.ioBlocking) { - block(ctx) - } - .asVoidFuture() - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRouterManager.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRouterManager.kt deleted file mode 100644 index 7e6aad468..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRouterManager.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.fireflysource.net.http.server.impl.router - -import com.fireflysource.net.http.common.codec.URIUtils.canonicalPath -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.server.* -import com.fireflysource.net.http.server.impl.matcher.* -import java.util.* -import java.util.concurrent.atomic.AtomicInteger - -class AsyncRouterManager(val httpServer: HttpServer) : RouterManager { - - private val routerId = AtomicInteger() - - private val httpMethodMatcher: HttpMethodMatcher = HttpMethodMatcher() - private val precisePathMatcher: PrecisePathMatcher = PrecisePathMatcher() - private val parameterPathMatcher: ParameterPathMatcher = ParameterPathMatcher() - private val patternedPathMatcher: PatternedPathMatcher = PatternedPathMatcher() - private val regexPathMatcher: RegexPathMatcher = RegexPathMatcher() - private val preciseContentTypeMatcher: PreciseContentTypeMatcher = PreciseContentTypeMatcher() - private val patternedContentTypeMatcher: PatternedContentTypeMatcher = PatternedContentTypeMatcher() - private val acceptHeaderMatcher: AcceptHeaderMatcher = AcceptHeaderMatcher() - - override fun register(): Router = this.register(routerId.getAndIncrement()) - - override fun register(id: Int): Router = - AsyncRouter(id, this) - - override fun findRouters(ctx: RoutingContext): SortedSet { - val routerMatchTypeMap = TreeMap>() - val routerParameterMap = TreeMap>() - - val methodResult = httpMethodMatcher.match(ctx.method) - collectRouterResult(methodResult, routerMatchTypeMap, routerParameterMap) - - val path = canonicalPath(ctx.uri.decodedPath) - val precisePathResult = precisePathMatcher.match(path) - collectRouterResult(precisePathResult, routerMatchTypeMap, routerParameterMap) - - val parameterPathResult = parameterPathMatcher.match(path) - collectRouterResult(parameterPathResult, routerMatchTypeMap, routerParameterMap) - - val patternedPathResult = patternedPathMatcher.match(path) - collectRouterResult(patternedPathResult, routerMatchTypeMap, routerParameterMap) - - val regexPathResult = regexPathMatcher.match(path) - collectRouterResult(regexPathResult, routerMatchTypeMap, routerParameterMap) - - val contentType = ctx.contentType ?: "" - val preciseContentTypeResult = preciseContentTypeMatcher.match(contentType) - collectRouterResult(preciseContentTypeResult, routerMatchTypeMap, routerParameterMap) - - val patternedContentTypeResult = patternedContentTypeMatcher.match(contentType) - collectRouterResult(patternedContentTypeResult, routerMatchTypeMap, routerParameterMap) - - val accept = ctx.httpFields[HttpHeader.ACCEPT] ?: "" - val acceptHeaderResult = acceptHeaderMatcher.match(accept) - collectRouterResult(acceptHeaderResult, routerMatchTypeMap, routerParameterMap) - - return routerMatchTypeMap - .filter { it.key.isEnable } - .filter { it.key.matchTypes == it.value } - .map { RouterManager.RouterMatchResult(it.key, routerParameterMap[it.key] ?: emptyMap(), it.value) } - .toSortedSet() - } - - private fun collectRouterResult( - result: Matcher.MatchResult?, - routerMatchTypeMap: SortedMap>, - routerParameterMap: SortedMap> - ) { - result?.routers?.forEach { - routerMatchTypeMap.computeIfAbsent(it) { HashSet() }.add(result.matchType) - val params = result.parameters[it] - if (!params.isNullOrEmpty()) { - routerParameterMap.computeIfAbsent(it) { HashMap() }.putAll(params) - } - } - } - - fun method(httpMethod: String, router: AsyncRouter) { - httpMethodMatcher.add(httpMethod, router) - } - - fun path(url: String, router: AsyncRouter) { - when { - url == "/" -> precisePathMatcher.add(url, router) - url.contains("*") -> patternedPathMatcher.add(url, router) - ParameterPathMatcher.isParameterPath(url) -> parameterPathMatcher.add(url, router) - else -> precisePathMatcher.add(url, router) - } - } - - fun paths(urlList: MutableList, router: AsyncRouter) { - urlList.forEach { path(it, router) } - } - - fun pathRegex(regex: String, router: AsyncRouter) { - regexPathMatcher.add(regex, router) - } - - fun consumes(contentType: String, router: AsyncRouter) { - if (contentType.contains("*")) patternedContentTypeMatcher.add(contentType, router) - else preciseContentTypeMatcher.add(contentType, router) - } - - fun produces(accept: String, router: AsyncRouter) { - acceptHeaderMatcher.add(accept, router) - } - - fun copy(httpServer: HttpServer): AsyncRouterManager { - val newManager = AsyncRouterManager(httpServer) - newManager.routerId.set(this.routerId.get()) - newManager.httpMethodMatcher.routersMap.putAll(this.httpMethodMatcher.copyRouterMap(newManager)) - newManager.precisePathMatcher.routersMap.putAll(this.precisePathMatcher.copyRouterMap(newManager)) - newManager.parameterPathMatcher.routersMap.putAll(this.parameterPathMatcher.copyRouterMap(newManager)) - newManager.patternedPathMatcher.routersMap.putAll(this.patternedPathMatcher.copyRouterMap(newManager)) - newManager.regexPathMatcher.routersMap.putAll(this.regexPathMatcher.copyRouterMap(newManager)) - newManager.preciseContentTypeMatcher.routersMap.putAll(this.preciseContentTypeMatcher.copyRouterMap(newManager)) - newManager.patternedContentTypeMatcher.routersMap.putAll( - this.patternedContentTypeMatcher.copyRouterMap( - newManager - ) - ) - newManager.acceptHeaderMatcher.routersMap.putAll(this.acceptHeaderMatcher.copyRouterMap(newManager)) - return newManager - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRoutingContext.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRoutingContext.kt deleted file mode 100644 index 8b4133b5d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/AsyncRoutingContext.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.fireflysource.net.http.server.impl.router - -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.expectServerAcceptsContent -import com.fireflysource.net.http.server.* -import com.fireflysource.net.http.server.impl.content.provider.DefaultContentProvider -import com.fireflysource.net.http.server.impl.matcher.AbstractPatternMatcher -import com.fireflysource.net.http.server.impl.matcher.AbstractRegexMatcher -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap - -class AsyncRoutingContext( - private val request: HttpServerRequest, - private val response: HttpServerResponse, - private val connection: HttpServerConnection -) : RoutingContext { - - private val attributes: ConcurrentMap by lazy { ConcurrentHashMap() } - var routerMatchResult: RouterManager.RouterMatchResult? = null - var routerIterator: Iterator? = null - - override fun getAttribute(key: String): Any? = attributes[key] - - override fun setAttribute(key: String, value: Any): Any? = attributes.put(key, value) - - override fun getAttributes(): MutableMap = attributes - - override fun removeAttribute(key: String): Any? = attributes.remove(key) - - override fun getRequest(): HttpServerRequest = request - - override fun getResponse(): HttpServerResponse = response - - override fun getPathParameter(name: String): String { - val result = routerMatchResult - return if (result == null) "" - else result.parameters[name] ?: "" - } - - override fun getPathParameter(index: Int): String { - val result = routerMatchResult - return if (result == null) "" - else result.parameters[AbstractPatternMatcher.paramName + index] ?: "" - } - - override fun getPathParameterByRegexGroup(index: Int): String { - val result = routerMatchResult - return if (result == null) "" - else result.parameters[AbstractRegexMatcher.paramName + index] ?: "" - } - - override fun expect100Continue(): Boolean { - return request.httpFields.expectServerAcceptsContent() - } - - override fun redirect(url: String): CompletableFuture { - val status = HttpStatus.FOUND_302 - return setStatus(status) - .put(HttpHeader.LOCATION, url) - .contentProvider(DefaultContentProvider(status, null, this)) - .end() - } - - override fun next(): CompletableFuture { - if (!hasNext()) return Result.DONE - - val result = routerIterator?.next() - return if (result != null) { - routerMatchResult = result - val asyncRouter = result.router as AsyncRouter - asyncRouter.getHandler().apply(this) - } else Result.DONE - } - - override fun hasNext(): Boolean { - return routerIterator?.hasNext() ?: false - } - - override fun getConnection(): HttpServerConnection = connection - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/handler/CorsHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/handler/CorsHandler.kt deleted file mode 100644 index fbb0d9661..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/handler/CorsHandler.kt +++ /dev/null @@ -1,198 +0,0 @@ -package com.fireflysource.net.http.server.impl.router.handler - -import com.fireflysource.common.annotation.NoArg -import com.fireflysource.common.string.Pattern -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.model.HttpHeader.* -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.Router -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.content.provider.DefaultContentProvider -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.function.Consumer - - -class CorsHandler(val config: CorsConfig) : Router.Handler { - - companion object { - private val simpleRequestMethods = setOf( - HttpMethod.GET.value, - HttpMethod.HEAD.value, - HttpMethod.POST.value - ) - private val simpleRequestContentTypes = setOf( - MimeTypes.Type.FORM_ENCODED.value, - MimeTypes.Type.MULTIPART_FORM_DATA.value, - MimeTypes.Type.TEXT_PLAIN.value - ) - } - - private val allowOriginPattern = Pattern.compile(config.allowOriginPattern, "*") - - override fun apply(ctx: RoutingContext): CompletableFuture { - when { - isSimpleRequest(ctx) -> handleSimpleRequest(ctx) - isPreflightRequest(ctx) -> handlePreflightRequest(ctx) - else -> { - val origin: String? = ctx.httpFields[ORIGIN] - if (!origin.isNullOrBlank()) { - if (allowOrigin(ctx)) { - setAccessControlHeaders(ctx, origin) - } - } - } - } - return if (ctx.response.isCommitted) Result.DONE else ctx.next() - } - - private fun allowOrigin(ctx: RoutingContext): Boolean { - val origin: String? = ctx.httpFields[ORIGIN] - return !origin.isNullOrBlank() && allowOriginPattern.match(origin) != null - } - - private fun isSimpleRequest(ctx: RoutingContext): Boolean { - return if (ctx.httpFields.contains(ORIGIN) && simpleRequestMethods.contains(ctx.method)) { - if (ctx.method == HttpMethod.POST.value) - simpleRequestContentTypes.any { ctx.contentType.contains(it) } - else true - } else false - } - - private fun isPreflightRequest(ctx: RoutingContext): Boolean { - return ctx.httpFields.contains(ORIGIN) - && ctx.method == HttpMethod.OPTIONS.value - && ctx.httpFields.contains(ACCESS_CONTROL_REQUEST_METHOD) - } - - private fun handleSimpleRequest(ctx: RoutingContext) { - if (allowOrigin(ctx)) { - val origin: String? = ctx.httpFields[ORIGIN] - requireNotNull(origin) - setAccessControlHeaders(ctx, origin) - } else handleNotAllowOrigin(ctx) - } - - private fun setAccessControlHeaders(ctx: RoutingContext, origin: String) { - ctx.put(ACCESS_CONTROL_ALLOW_ORIGIN, origin) - .put(ACCESS_CONTROL_ALLOW_CREDENTIALS, config.allowCredentials.toString()) - if (config.exposeHeaders.isNotEmpty()) { - ctx.addCSV(ACCESS_CONTROL_EXPOSE_HEADERS, *config.exposeHeaders.toTypedArray()) - } - } - - private fun handlePreflightRequest(ctx: RoutingContext) { - if (!allowOrigin(ctx)) { - handleNotAllowOrigin(ctx) - return - } - - if (!allowMethod(ctx)) { - handleNotAllowMethod(ctx) - return - } - - if (!allowHeaders(ctx)) { - handleNotAllowHeader(ctx) - return - } - - val origin: String? = ctx.httpFields[ORIGIN] - requireNotNull(origin) - - ctx.setStatus(HttpStatus.NO_CONTENT_204) - .put(ACCESS_CONTROL_ALLOW_ORIGIN, origin) - .put(ACCESS_CONTROL_ALLOW_CREDENTIALS, config.allowCredentials.toString()) - .put(ACCESS_CONTROL_MAX_AGE, config.preflightMaxAge.toString()) - .addCSV(ACCESS_CONTROL_ALLOW_METHODS, *config.allowMethods.toTypedArray()) - .addCSV(ACCESS_CONTROL_ALLOW_HEADERS, *config.allowHeaders.toTypedArray()) - .end() - } - - private fun allowMethod(ctx: RoutingContext): Boolean { - val accessControlRequestMethods = ctx.httpFields.getCSV(ACCESS_CONTROL_REQUEST_METHOD, false) - return config.allowMethods.containsAll(accessControlRequestMethods) - } - - private fun allowHeaders(ctx: RoutingContext): Boolean { - val accessControlRequestHeaders = ctx.httpFields.getCSV(ACCESS_CONTROL_REQUEST_HEADERS, false) - return if (accessControlRequestHeaders.isNullOrEmpty()) true - else config.allowHeaders.map { it.lowercase(Locale.getDefault()) } - .containsAll(accessControlRequestHeaders.map { it.lowercase(Locale.getDefault()) }) - } - - - private fun handleNotAllowOrigin(ctx: RoutingContext) { - config.handleNotAllowOrigin.accept(ctx) - } - - private fun handleNotAllowMethod(ctx: RoutingContext) { - config.handleNotAllowMethod.accept(ctx) - } - - private fun handleNotAllowHeader(ctx: RoutingContext) { - config.handleNotAllowHeader.accept(ctx) - } -} - -@NoArg -data class CorsConfig @JvmOverloads constructor( - var allowOriginPattern: String, - var exposeHeaders: Set = setOf(), - var allowHeaders: Set = setOf("Content-Type"), - var preflightMaxAge: Int = 86400, - var allowCredentials: Boolean = true, - var allowMethods: Set = setOf( - HttpMethod.GET.value, - HttpMethod.POST.value, - HttpMethod.PUT.value, - HttpMethod.DELETE.value, - HttpMethod.OPTIONS.value, - HttpMethod.HEAD.value, - HttpMethod.PATCH.value - ), - var handleNotAllowOrigin: Consumer = Consumer { ctx -> - val origin: String? = ctx.httpFields[ORIGIN] - ctx.setStatus(HttpStatus.FORBIDDEN_403) - .contentProvider( - DefaultContentProvider( - HttpStatus.FORBIDDEN_403, - NotAllowOriginException("Not allow origin: $origin"), - ctx - ) - ) - .end() - }, - var handleNotAllowMethod: Consumer = Consumer { ctx -> - val accessControlRequestMethods = ctx.httpFields.getCSV(ACCESS_CONTROL_REQUEST_METHOD, false) - ctx.setStatus(HttpStatus.METHOD_NOT_ALLOWED_405) - .contentProvider( - DefaultContentProvider( - HttpStatus.METHOD_NOT_ALLOWED_405, - NotAllowMethodException("Not allow methods: $accessControlRequestMethods"), - ctx - ) - ) - .end() - }, - var handleNotAllowHeader: Consumer = Consumer { ctx -> - val accessControlRequestHeaders = ctx.httpFields.getCSV(ACCESS_CONTROL_REQUEST_HEADERS, false) - ctx.setStatus(HttpStatus.BAD_REQUEST_400) - .contentProvider( - DefaultContentProvider( - HttpStatus.BAD_REQUEST_400, - NotAllowHeaderException("Not allow headers: $accessControlRequestHeaders"), - ctx - ) - ) - .end() - } -) - -class NotAllowOriginException(message: String) : IllegalArgumentException(message) - -class NotAllowMethodException(message: String) : IllegalArgumentException(message) - -class NotAllowHeaderException(message: String) : IllegalArgumentException(message) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/handler/FileHandler.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/handler/FileHandler.kt deleted file mode 100644 index 73461c3f8..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/http/server/impl/router/handler/FileHandler.kt +++ /dev/null @@ -1,156 +0,0 @@ -package com.fireflysource.net.http.server.impl.router.handler - -import com.fireflysource.common.annotation.NoArg -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.common.io.existsAsync -import com.fireflysource.common.io.readAttributesAsync -import com.fireflysource.net.http.common.codec.InclusiveByteRange -import com.fireflysource.net.http.common.codec.URIUtils -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.Router -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.content.provider.DefaultContentProvider -import com.fireflysource.net.http.server.impl.content.provider.FileContentProvider -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.StandardOpenOption -import java.util.* -import java.util.concurrent.CompletableFuture - -class FileHandler(val config: FileConfig) : Router.Handler { - - companion object { - fun createFileHandlerByResourcePath(path: String): FileHandler { - val resourcePath = getResourcePath(path) - val fileConfig = FileConfig(resourcePath) - return FileHandler(fileConfig) - } - - fun getResourcePath(path: String): String { - return Optional.ofNullable(FileHandler::class.java.classLoader.getResource(path)) - .map { it.toURI() } - .map { Paths.get(it) } - .map { it.toString() } - .orElse("") - } - } - - override fun apply(ctx: RoutingContext): CompletableFuture = - ctx.connection.coroutineScope.launch { handleFile(ctx) }.asVoidFuture() - - private suspend fun handleFile(ctx: RoutingContext) { - val path = URIUtils.canonicalPath(ctx.uri.decodedPath) - val filePath = Paths.get(config.rootPath, path) - if (!existsAsync(filePath).await()) { - responseFileNotFound(ctx) - return - } - - val fileAttributes = readAttributesAsync(filePath).await() - if (fileAttributes.isDirectory) { - responseFileNotFound(ctx) - return - } - - val ranges = ctx.httpFields.getValuesList(HttpHeader.RANGE) - if (ranges.isNullOrEmpty()) { - responseFile(ctx, filePath) - } else { - val fileLength = fileAttributes.size() - val satisfiableRanges = InclusiveByteRange.satisfiableRanges(ranges, fileLength) - if (satisfiableRanges.isNullOrEmpty()) { - responseRangeNotSatisfiable(ctx, fileLength) - } else { - if (satisfiableRanges.size == 1) { - val inclusiveByteRange = satisfiableRanges[0] - responsePartialFile(ctx, filePath, inclusiveByteRange, fileLength) - } else { - responseRangeNotSatisfiable(ctx, fileLength) - } - } - } - } - - private suspend fun responseFileNotFound(ctx: RoutingContext) { - ctx.setStatus(HttpStatus.NOT_FOUND_404) - .setReason(HttpStatus.Code.NOT_FOUND.message) - .contentProvider(DefaultContentProvider(HttpStatus.NOT_FOUND_404, null, ctx)) - .end() - .await() - } - - private suspend fun responseFile(ctx: RoutingContext, filePath: Path) { - setContentType(ctx, filePath) - - ctx.setStatus(HttpStatus.OK_200) - .contentProvider( - FileContentProvider( - filePath, - CoroutineScope(CoroutineName("Firefly-server-file-content-provider") + ctx.connection.coroutineDispatcher), - StandardOpenOption.READ - ) - ) - .end() - .await() - } - - private suspend fun responseRangeNotSatisfiable(ctx: RoutingContext, fileLength: Long) { - ctx.setStatus(HttpStatus.RANGE_NOT_SATISFIABLE_416) - .put(HttpHeader.CONTENT_RANGE, InclusiveByteRange.to416HeaderRangeString(fileLength)) - .contentProvider( - DefaultContentProvider( - HttpStatus.RANGE_NOT_SATISFIABLE_416, - RangeNotSatisfiable("The range not satisfiable"), - ctx - ) - ) - .end() - .await() - } - - private suspend fun responsePartialFile( - ctx: RoutingContext, - filePath: Path, - inclusiveByteRange: InclusiveByteRange, - fileLength: Long - ) { - setContentType(ctx, filePath) - - val position = inclusiveByteRange.first - val length = inclusiveByteRange.size - ctx.setStatus(HttpStatus.PARTIAL_CONTENT_206) - .put(HttpHeader.CONTENT_RANGE, inclusiveByteRange.toHeaderRangeString(fileLength)) - .contentProvider( - FileContentProvider( - filePath, - setOf(StandardOpenOption.READ), - position, - length, - CoroutineScope(CoroutineName("Firefly-server-file-content-provider") + ctx.connection.coroutineDispatcher) - ) - ) - .end() - .await() - } - - private fun setContentType(ctx: RoutingContext, filePath: Path) { - val fileName = filePath.fileName.toString() - val mimeType: String? = MimeTypes.getDefaultMimeByExtension(fileName) - if (!mimeType.isNullOrBlank()) { - ctx.put(HttpHeader.CONTENT_TYPE, mimeType) - } - } -} - -class RangeNotSatisfiable(message: String) : IllegalArgumentException(message) - -@NoArg -data class FileConfig( - var rootPath: String -) \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/AsyncTcpConnectionExtension.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/AsyncTcpConnectionExtension.kt deleted file mode 100644 index 6218ec088..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/AsyncTcpConnectionExtension.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.fireflysource.net.tcp - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout -import java.nio.ByteBuffer - -fun TcpServer.onAcceptAsync(block: suspend CoroutineScope.(connection: TcpConnection) -> Unit): TcpServer { - this.onAccept { connection -> connection.coroutineScope.launch { block.invoke(this, connection) } } - return this -} - -suspend fun TcpConnection.read(timeout: Long): ByteBuffer = withTimeout(timeout) { - read().await() -} - -suspend fun TcpConnection.close(timeout: Long): Unit = withTimeout(timeout) { - closeAsync().await() -} - -suspend fun TcpConnection.write(byteBuffer: ByteBuffer, timeout: Long): Int = withTimeout(timeout) { - write(byteBuffer).await() -} - -suspend fun TcpConnection.flush(timeout: Long): Unit = withTimeout(timeout) { - flush().await() -} diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AbstractAioTcpConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AbstractAioTcpConnection.kt deleted file mode 100644 index df8a01a25..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AbstractAioTcpConnection.kt +++ /dev/null @@ -1,447 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.exception.UnknownTypeException -import com.fireflysource.common.func.Callback -import com.fireflysource.common.io.* -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.createFailedResult -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.AbstractConnection -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpCoroutineDispatcher -import com.fireflysource.net.tcp.buffer.* -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED -import kotlinx.coroutines.channels.ChannelResult -import kotlinx.coroutines.launch -import java.io.IOException -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.channels.AsynchronousSocketChannel -import java.nio.channels.ClosedChannelException -import java.nio.channels.InterruptedByTimeoutException -import java.util.concurrent.CancellationException -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import java.util.function.Consumer - -/** - * @author Pengtao Qiu - */ -abstract class AbstractAioTcpConnection( - id: Int, - maxIdleTime: Long, - private val socketChannel: AsynchronousSocketChannel, - dispatcher: CoroutineDispatcher, - inputBufferSize: Int, - private val aioTcpCoroutineDispatcher: TcpCoroutineDispatcher = AioTcpCoroutineDispatcher(id, dispatcher) -) : AbstractConnection(id, System.currentTimeMillis(), maxIdleTime), TcpConnection, - TcpCoroutineDispatcher by aioTcpCoroutineDispatcher { - - companion object { - private val log = SystemLogger.create(AbstractAioTcpConnection::class.java) - private val timeUnit = TimeUnit.SECONDS - } - - private val isInputShutdown: AtomicBoolean = AtomicBoolean(false) - private val isOutputShutdown: AtomicBoolean = AtomicBoolean(false) - private val socketChannelClosed: AtomicBoolean = AtomicBoolean(false) - private val closeRequest: AtomicBoolean = AtomicBoolean(false) - private val closeCallbacks: MutableList = mutableListOf() - private val outputMessageHandler = OutputMessageHandler() - private val inputMessageHandler = InputMessageHandler(inputBufferSize) - private val closeResultChannel: Channel>> = Channel(UNLIMITED) - - private inner class OutputMessageHandler { - private val outputMessageChannel: Channel = Channel(UNLIMITED) - private var writeTimeout = maxIdleTime - - init { - writeJob() - } - - fun sendOutputMessage(output: OutputMessage) = - outputMessageChannel.trySend(output) - - private fun writeJob() = coroutineScope.launch { - while (true) { - handleOutputMessage(outputMessageChannel.receive()) - } - }.invokeOnCompletion { cause -> - val e = cause ?: ClosedChannelException() - outputMessageChannel.consumeAll { message -> - when (message) { - is OutputBuffer -> message.result.accept(createFailedResult(-1, e)) - is OutputBufferList -> message.result.accept(createFailedResult(-1, e)) - is OutputBuffers -> message.result.accept(createFailedResult(-1, e)) - is ShutdownOutput -> message.result.accept(createFailedResult(e)) - else -> { - } - } - } - closeResultChannel.consumeAll { it.accept(Result.SUCCESS) } - } - - private suspend fun handleOutputMessage(output: OutputMessage) { - when (output) { - is OutputDataMessage -> writeBuffers(output) - is ShutdownOutput -> shutdownOutputAndClose(output) - is SetWriteTimeout -> if (output.timeout > 0) { - writeTimeout = output.timeout - log.info("Set write timeout: $writeTimeout, id: $id") - } - else -> throw UnknownTypeException("Unknown output message. $output") - } - } - - private suspend fun write(output: OutputDataMessage): Long = when (output) { - is OutputBuffer -> socketChannel.writeAwait(output.buffer, writeTimeout, timeUnit).toLong() - is OutputBufferList -> socketChannel.writeAwait( - output.buffers, output.getCurrentOffset(), output.getCurrentLength(), - writeTimeout, timeUnit - ) - is OutputBuffers -> socketChannel.writeAwait( - output.buffers, output.getCurrentOffset(), output.getCurrentLength(), - writeTimeout, timeUnit - ) - } - - private suspend fun writeBuffers(output: OutputDataMessage): Boolean { - lastWrittenTime = System.currentTimeMillis() - var totalLength = 0L - var success = true - var exception: Exception? = null - while (output.hasRemaining()) { - try { - val writtenLength = write(output) - if (writtenLength < 0) { - success = false - exception = ClosedChannelException() - break - } else { - writtenBytes += writtenLength - totalLength += writtenLength - } - } catch (e: InterruptedByTimeoutException) { - log.warn { "The TCP connection writing timeout. id: $id" } - success = false - exception = e - break - } catch (e: Exception) { - log.warn { "The TCP connection writing exception. ${e.message} id: $id" } - success = false - exception = e - break - } - } - - fun complete() { - when (output) { - is OutputBuffer -> output.result.accept(Result(true, totalLength.toInt(), null)) - is OutputBufferList -> output.result.accept(Result(true, totalLength, null)) - is OutputBuffers -> output.result.accept(Result(true, totalLength, null)) - } - } - - if (success) { - log.debug { "TCP connection writes buffers total length: $totalLength" } - complete() - } else { - shutdownOutputAndClose() - failed(output, exception) - } - return success - } - - private fun failed(outputBuffers: OutputDataMessage, exception: Exception?) { - when (outputBuffers) { - is OutputBuffer -> outputBuffers.result.accept(Result(false, -1, exception)) - is OutputBufferList -> outputBuffers.result.accept(Result(false, -1, exception)) - is OutputBuffers -> outputBuffers.result.accept(Result(false, -1, exception)) - } - } - - private fun shutdownOutputAndClose(output: ShutdownOutput) { - shutdownOutputAndClose() - output.result.accept(Result.SUCCESS) - } - - private fun shutdown() { - if (isOutputShutdown.compareAndSet(false, true)) { - try { - socketChannel.shutdownOutput() - } catch (e: ClosedChannelException) { - log.warn { "The channel closed. $id" } - } catch (e: IOException) { - log.warn { "Shutdown output exception. $id" } - } - } - } - - private fun shutdownOutputAndClose() { - if (isClosed) return - - this@OutputMessageHandler.shutdown() - log.debug { "TCP connection shutdown output. id $id, out: $isOutputShutdown, in: $isInputShutdown, socket: ${!socketChannel.isOpen}" } - if (isShutdownInput) { - closeNow() - } - } - } - - private inner class InputMessageHandler(inputBufferSize: Int) { - - private val inputMessageChannel: Channel = Channel(UNLIMITED) - private val inputBuffer = BufferUtils.allocateDirect(inputBufferSize) - private var readTimeout = maxIdleTime - - init { - readJob() - } - - fun sendInputMessage(input: InputMessage): ChannelResult { - if (input is ShutdownInput) { - this@InputMessageHandler.shutdown() - } - return inputMessageChannel.trySend(input) - } - - private fun readJob() = coroutineScope.launch { - while (true) { - handleInputMessage(inputMessageChannel.receive()) - } - }.invokeOnCompletion { cause -> - val e = cause ?: ClosedChannelException() - inputMessageChannel.consumeAll { message -> - if (message is InputBuffer) { - message.bufferFuture.completeExceptionally(e) - } - } - closeResultChannel.consumeAll { it.accept(Result.SUCCESS) } - } - - private suspend fun handleInputMessage(input: InputMessage) { - when (input) { - is InputBuffer -> readBuffers(input) - is ShutdownInput -> shutdownInputAndClose() - is SetReadTimeout -> if (input.timeout > 0) { - readTimeout = input.timeout - log.info("Set read timeout: $readTimeout, id: $id") - } - } - } - - private suspend fun readBuffers(input: InputBuffer): Boolean { - lastReadTime = System.currentTimeMillis() - var success = true - var exception: Exception? = null - var length = 0 - try { - val pos = inputBuffer.flipToFill() - length = socketChannel.readAwait(inputBuffer, readTimeout, timeUnit) - inputBuffer.flipToFlush(pos) - if (length < 0) { - success = false - exception = ClosedChannelException() - } else { - readBytes += length - } - } catch (e: InterruptedByTimeoutException) { - log.warn { "The TCP connection reading timeout. id: $id" } - success = false - exception = e - } catch (e: Exception) { - log.warn { "The TCP connection reading exception. ${e.message} id: $id" } - success = false - exception = e - } - - if (success) { - log.debug { "TCP connection reads buffers total length: $length" } - inputBuffer.copy().also { input.bufferFuture.complete(it) } - } else { - shutdownInputAndClose() - failed(input, exception) - } - BufferUtils.clear(inputBuffer) - return success - } - - private fun failed(input: InputMessage, e: Exception?) { - if (input is InputBuffer) { - input.bufferFuture.completeExceptionally(e) - } - } - - private fun shutdown() { - if (isInputShutdown.compareAndSet(false, true)) { - try { - socketChannel.shutdownInput() - } catch (e: ClosedChannelException) { - log.warn { "The channel closed. $id" } - } catch (e: IOException) { - log.warn { "Shutdown input exception. $id" } - } - } - } - - private fun shutdownInputAndClose() { - if (isClosed) { - return - } - - this@InputMessageHandler.shutdown() - log.debug { "TCP connection shutdown input. id $id, out: $isOutputShutdown, in: $isInputShutdown, socket: ${!socketChannel.isOpen}" } - if (isShutdownOutput) { - closeNow() - } else { - this@AbstractAioTcpConnection.shutdownOutput() - } - } - } - - override fun read(): CompletableFuture { - val future = CompletableFuture() - if (inputMessageHandler.sendInputMessage(InputBuffer(future)).isFailure) { - future.completeExceptionally(ClosedChannelException()) - } - return future - } - - override fun setReadTimeout(timeout: Long) { - inputMessageHandler.sendInputMessage(SetReadTimeout(timeout)) - } - - override fun write(byteBuffer: ByteBuffer, result: Consumer>): TcpConnection { - if (outputMessageHandler.sendOutputMessage(OutputBuffer(byteBuffer, result)).isFailure) { - result.accept(createFailedResult(-1, ClosedChannelException())) - } - return this - } - - override fun write( - byteBuffers: Array, offset: Int, length: Int, - result: Consumer> - ): TcpConnection { - if (outputMessageHandler.sendOutputMessage(OutputBuffers(byteBuffers, offset, length, result)).isFailure) { - result.accept(createFailedResult(-1L, ClosedChannelException())) - } - return this - } - - override fun write( - byteBufferList: List, offset: Int, length: Int, - result: Consumer> - ): TcpConnection { - if (outputMessageHandler.sendOutputMessage( - OutputBufferList( - byteBufferList, - offset, - length, - result - ) - ).isFailure - ) { - result.accept(createFailedResult(-1L, ClosedChannelException())) - } - return this - } - - override fun setWriteTimeout(timeout: Long) { - outputMessageHandler.sendOutputMessage(SetWriteTimeout(timeout)) - } - - override fun flush(result: Consumer>): TcpConnection { - result.accept(Result.SUCCESS) - return this - } - - override fun getBufferSize(): Int = 0 - - - override fun onClose(callback: Callback): TcpConnection { - closeCallbacks.add(callback) - return this - } - - override fun close(result: Consumer>): TcpConnection { - if (closeRequest.compareAndSet(false, true)) { - if (isClosed) { - result.accept(Result.SUCCESS) - } else { - closeResultChannel.trySend(result) - outputMessageHandler.sendOutputMessage(ShutdownOutput { - if (inputMessageHandler.sendInputMessage(ShutdownInput).isFailure) { - result.accept(createFailedResult(ClosedChannelException())) - } - }) - } - } else { - result.accept(Result.SUCCESS) - } - return this - } - - override fun close() { - close(discard()) - } - - override fun shutdownInput(): TcpConnection { - inputMessageHandler.sendInputMessage(ShutdownInput) - return this - } - - override fun shutdownOutput(): TcpConnection { - outputMessageHandler.sendOutputMessage(ShutdownOutput(discard())) - return this - } - - override fun isShutdownInput(): Boolean = isInputShutdown.get() - - override fun isShutdownOutput(): Boolean = isOutputShutdown.get() - - override fun closeNow(): TcpConnection { - if (socketChannelClosed.compareAndSet(false, true)) { - closeTime = System.currentTimeMillis() - try { - socketChannel.close() - } catch (e: Exception) { - log.warn { "Close socket channel exception. ${e.message} id: $id" } - } - - closeCallbacks.forEach { - try { - it.call() - } catch (e: Exception) { - log.warn { "The TCP connection close callback exception. ${e.message} id: $id" } - } - } - - try { - coroutineScope.cancel(CancellationException("Cancel TCP coroutine exception. id: $id")) - } catch (e: Throwable) { - log.warn { "Cancel TCP coroutine exception. ${e.message} id: $id" } - } - - closeResultChannel.consumeAll { it.accept(Result.SUCCESS) } - - log.info { "The TCP connection close success. id: $id, out: $isOutputShutdown, in: $isInputShutdown, socket: ${!socketChannel.isOpen}" } - } - return this - } - - - override fun isClosed(): Boolean = socketChannelClosed.get() - - override fun isInvalid(): Boolean = - closeRequest.get() || isShutdownInput || isShutdownOutput || socketChannelClosed.get() - - override fun getLocalAddress(): InetSocketAddress = socketChannel.localAddress as InetSocketAddress - - override fun getRemoteAddress(): InetSocketAddress = socketChannel.remoteAddress as InetSocketAddress -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AdaptiveBufferSize.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AdaptiveBufferSize.kt deleted file mode 100644 index b9446360a..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AdaptiveBufferSize.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.fireflysource.net.tcp.aio - -/** - * @author Pengtao Qiu - */ -class AdaptiveBufferSize { - companion object { - private val sizeArray = arrayOf( - 128, - 256, - 512, - 1 * 1024, - 2 * 1024, - 4 * 1024, - 4 * 1024, - 8 * 1024, - 8 * 1024, - 8 * 1024, - 16 * 1024, - 32 * 1024, - 64 * 1024, - 128 * 1024, - 256 * 1024, - 512 * 1024 - ) - } - - private var index: Int = 0 - - fun getBufferSize() = sizeArray[index] - - fun update(size: Int) { - index = if (size >= getBufferSize()) { - (index + 1).coerceAtMost(sizeArray.lastIndex) - } else { - (index - 1).coerceAtLeast(0) - } - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioSecureTcpConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioSecureTcpConnection.kt deleted file mode 100644 index bebd81758..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioSecureTcpConnection.kt +++ /dev/null @@ -1,195 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.WrappedTcpConnection -import com.fireflysource.net.tcp.buffer.* -import com.fireflysource.net.tcp.secure.SecureEngine -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.ByteBuffer -import java.nio.channels.ClosedChannelException -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean -import java.util.function.Consumer - -/** - * @author Pengtao Qiu - */ -class AioSecureTcpConnection( - private val tcpConnection: TcpConnection, - private val secureEngine: SecureEngine -) : TcpConnection by tcpConnection, WrappedTcpConnection { - - companion object { - private val log = SystemLogger.create(AioSecureTcpConnection::class.java) - } - - private val encryptedOutChannel: Channel = Channel(Channel.UNLIMITED) - private val stashedBuffers = LinkedList() - private val beginHandshake = AtomicBoolean(false) - - init { - secureEngine - .onHandshakeWrite { tcpConnection.write(it) } - .onHandshakeRead { tcpConnection.read() } - tcpConnection.onClose { secureEngine.close() } - } - - override fun getRawTcpConnection(): TcpConnection = tcpConnection - - override fun read(): CompletableFuture { - if (!beginHandshake.get()) { - val future = CompletableFuture() - future.completeExceptionally(IllegalStateException("The TLS handshake has not begun")) - return future - } - - val stashedBuf: ByteBuffer? = stashedBuffers.poll() - return if (stashedBuf != null) { - val future = CompletableFuture() - future.complete(stashedBuf) - future - } else { - tcpConnection.read().thenApply(secureEngine::decrypt) - } - } - - private fun launchEncryptingAndFlushJob() = tcpConnection.coroutineScope.launch { - while (true) { - when (val message = encryptedOutChannel.receive()) { - is OutputBuffer -> encryptAndFlushBuffer(message) - is OutputBufferList -> encryptAndFlushBuffers(message) - is OutputBuffers -> encryptAndFlushBuffers(message) - is ShutdownOutput -> { - shutdownOutput(message) - break - } - else -> {} - } - } - }.invokeOnCompletion { cause -> - val e = cause ?: ClosedChannelException() - encryptedOutChannel.consumeAll { message -> - when (message) { - is OutputBuffer -> message.result.accept(Result.createFailedResult(-1, e)) - is OutputBufferList -> message.result.accept(Result.createFailedResult(-1, e)) - is OutputBuffers -> message.result.accept(Result.createFailedResult(-1, e)) - is ShutdownOutput -> message.result.accept(Result.createFailedResult(e)) - else -> { - } - } - } - } - - private suspend fun encryptAndFlushBuffers(outputMessage: OutputBuffers) { - val result = outputMessage.result - try { - val remaining = outputMessage.remaining() - val buffers = outputMessage.buffers - val offset = outputMessage.getCurrentOffset() - val length = outputMessage.getCurrentLength() - val encryptedBuffer = secureEngine.encrypt(buffers, offset, length) - val size = encryptedBuffer.remaining() - log.debug { "Encrypt and flush buffer. id: $id, src: $remaining, desc: $size, offset: $offset, length: $length" } - - if (remaining == 0L || size == 0) { - result.accept(Result(true, 0, null)) - } else { - tcpConnection.write(encryptedBuffer).await() - result.accept(Result(true, remaining, null)) - } - } catch (e: Exception) { - result.accept(Result(false, -1, e)) - } - } - - private suspend fun encryptAndFlushBuffer(outputMessage: OutputBuffer) { - val (buffer, result) = outputMessage - try { - val remaining = buffer.remaining() - val encryptedBuffer = secureEngine.encrypt(buffer) - val size = encryptedBuffer.remaining() - log.debug { "Encrypt and flush buffer. id: $id, src: $remaining, desc: $size" } - - if (remaining == 0 || size == 0) { - result.accept(Result(true, 0, null)) - } else { - tcpConnection.write(encryptedBuffer).await() - result.accept(Result(true, remaining, null)) - } - } catch (e: Exception) { - result.accept(Result(false, -1, e)) - } - } - - private fun shutdownOutput(message: ShutdownOutput) { - tcpConnection.close(message.result) - } - - override fun write(byteBuffer: ByteBuffer, result: Consumer>): TcpConnection { - encryptedOutChannel.trySend(OutputBuffer(byteBuffer, result)) - return this - } - - override fun write( - byteBuffers: Array, - offset: Int, - length: Int, - result: Consumer> - ): TcpConnection { - encryptedOutChannel.trySend(OutputBuffers(byteBuffers, offset, length, result)) - return this - } - - override fun write( - byteBufferList: List, - offset: Int, - length: Int, - result: Consumer> - ): TcpConnection { - encryptedOutChannel.trySend(OutputBufferList(byteBufferList, offset, length, result)) - return this - } - - override fun close(result: Consumer>): TcpConnection { - encryptedOutChannel.trySend(ShutdownOutput(result)) - return this - } - - override fun isSecureConnection(): Boolean = true - - override fun isClientMode(): Boolean { - return secureEngine.isClientMode - } - - override fun isHandshakeComplete(): Boolean = secureEngine.isHandshakeComplete - - override fun beginHandshake(result: Consumer>): TcpConnection { - if (beginHandshake.compareAndSet(false, true)) { - secureEngine.beginHandshake() - .thenAccept { - result.accept(Result(true, it.applicationProtocol, null)) - it.stashedAppBuffers.forEach { b -> stashedBuffers.add(b) } - launchEncryptingAndFlushJob() - } - .exceptionallyAccept { result.accept(Result(false, "", it)) } - } else { - result.accept(Result(false, "", IllegalStateException("The handshake has begun"))) - } - return this - } - - override fun getSupportedApplicationProtocols(): List { - return secureEngine.supportedApplicationProtocols - } - - override fun getApplicationProtocol(): String { - return secureEngine.applicationProtocol - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpChannelGroup.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpChannelGroup.kt deleted file mode 100644 index d701e4382..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpChannelGroup.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.coroutine.CoroutineDispatchers.awaitTerminationTimeout -import com.fireflysource.common.coroutine.CoroutineDispatchers.defaultPoolSize -import com.fireflysource.common.coroutine.CoroutineDispatchers.newSingleThreadDispatcher -import com.fireflysource.common.coroutine.CoroutineDispatchers.newSingleThreadExecutor -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.tcp.TcpChannelGroup -import kotlinx.coroutines.CoroutineDispatcher -import java.nio.channels.AsynchronousChannelGroup -import java.nio.channels.AsynchronousChannelGroup.withThreadPool -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger -import kotlin.math.abs - -class AioTcpChannelGroup(threadName: String) : AbstractLifeCycle(), TcpChannelGroup { - - companion object { - private val log = SystemLogger.create(AioTcpChannelGroup::class.java) - } - - private val id: AtomicInteger = AtomicInteger(0) - private val group: AsynchronousChannelGroup by lazy { - withThreadPool(newSingleThreadExecutor("firefly-aio-channel-group-thread")) - } - private val dispatchers: Array by lazy { - Array(defaultPoolSize) { i -> - newSingleThreadDispatcher("firefly-$threadName-$i") - } - } - - override fun getDispatcher(connectionId: Int): CoroutineDispatcher { - return dispatchers[abs(connectionId % defaultPoolSize)] - } - - override fun getAsynchronousChannelGroup(): AsynchronousChannelGroup = group - - override fun getNextId(): Int = id.getAndIncrement() - - override fun init() { - log.info { "Initialize TCP channel group. boss: 1, worker: $defaultPoolSize" } - } - - override fun destroy() { - try { - group.shutdown() - // Wait a while for existing tasks to terminate - if (!group.awaitTermination(awaitTerminationTimeout, TimeUnit.SECONDS)) { - group.shutdownNow() // Cancel currently executing tasks - // Wait a while for tasks to respond to being cancelled - if (!group.awaitTermination(awaitTerminationTimeout, TimeUnit.SECONDS)) { - log.info("The TCP channel group did not terminate") - } - } - } catch (ie: InterruptedException) { - // (Re-)Cancel if current thread also interrupted - group.shutdownNow() - // Preserve interrupt status - Thread.currentThread().interrupt() - } catch (e: Exception) { - log.info { "shutdown channel group exception. ${e.message}" } - } - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpClient.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpClient.kt deleted file mode 100644 index e5e29bf6a..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpClient.kt +++ /dev/null @@ -1,151 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.tcp.TcpChannelGroup -import com.fireflysource.net.tcp.TcpClient -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.secure.DefaultSecureEngineFactorySelector -import com.fireflysource.net.tcp.secure.SecureEngineFactory -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.net.SocketAddress -import java.net.StandardSocketOptions -import java.nio.channels.AsynchronousSocketChannel -import java.nio.channels.CompletionHandler -import java.util.concurrent.CompletableFuture - - -/** - * @author Pengtao Qiu - */ -class AioTcpClient(private val config: TcpConfig = TcpConfig()) : AbstractLifeCycle(), TcpClient { - - companion object { - private val log = SystemLogger.create(AioTcpClient::class.java) - } - - private var secureEngineFactory: SecureEngineFactory = - DefaultSecureEngineFactorySelector.createSecureEngineFactory(true) - private var group: TcpChannelGroup = AioTcpChannelGroup("aio-tcp-client") - private var stopGroup = true - - override fun init() { - group.start() - } - - override fun destroy() { - if (stopGroup) group.stop() - } - - override fun tcpChannelGroup(group: TcpChannelGroup): TcpClient { - this.group = group - return this - } - - override fun stopTcpChannelGroup(stop: Boolean): TcpClient { - this.stopGroup = stop - return this - } - - override fun secureEngineFactory(secureEngineFactory: SecureEngineFactory): TcpClient { - this.secureEngineFactory = secureEngineFactory - return this - } - - override fun enableSecureConnection(): TcpClient { - config.enableSecureConnection = true - return this - } - - override fun timeout(timeout: Long): TcpClient { - config.timeout = timeout - return this - } - - override fun bufferSize(bufferSize: Int): TcpClient { - config.outputBufferSize = bufferSize - return this - } - - override fun enableOutputBuffer(): TcpClient { - config.enableOutputBuffer = true - return this - } - - override fun connect(address: SocketAddress): CompletableFuture = - connect(address, defaultSupportedProtocols) - - override fun connect(address: SocketAddress, supportedProtocols: List): CompletableFuture = - connect(address, "", 0, supportedProtocols) - - override fun connect( - address: SocketAddress, - peerHost: String, - peerPort: Int, - supportedProtocols: List - ): CompletableFuture { - val future = CompletableFuture() - try { - connect(address, peerHost, peerPort, supportedProtocols, future) - } catch (e: Exception) { - log.warn(e) { "connecting exception. $address" } - future.completeExceptionally(e) - } - return future - } - - private fun connect( - address: SocketAddress, - peerHost: String, - peerPort: Int, - supportedProtocols: List, - future: CompletableFuture - ) { - start() - - try { - val socketChannel = AsynchronousSocketChannel.open(group.asynchronousChannelGroup) - socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, config.reuseAddr) - socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, config.keepAlive) - socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, config.tcpNoDelay) - socketChannel.connect(address, group.nextId, object : CompletionHandler { - - override fun completed(result: Void?, connectionId: Int) { - try { - future.complete( - createTcpConnection( - connectionId, - socketChannel, - group, - config, - peerHost, - peerPort, - true, - supportedProtocols, - secureEngineFactory - ) - ) - } catch (e: Exception) { - log.warn(e) { "connecting exception. id: ${connectionId}, address: $address" } - future.completeExceptionally(e) - } - } - - override fun failed(t: Throwable?, connectionId: Int) { - log.warn(t) { "connecting exception. id: ${connectionId}, address: $address" } - future.completeExceptionally(t) - } - }) - } catch (e: Exception) { - log.error(e) { "TCP client connect exception" } - future.completeExceptionally(e) - } - } - -} - -fun TcpClient.connectAsync(host: String, port: Int, block: suspend CoroutineScope.(TcpConnection) -> Unit): TcpClient { - connect(host, port).thenAccept { connection -> connection.coroutineScope.launch { block(connection) } } - return this -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpConnection.kt deleted file mode 100644 index e88fdf80d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpConnection.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.exception.UnsupportedOperationException -import com.fireflysource.common.sys.Result -import com.fireflysource.net.tcp.TcpConnection -import kotlinx.coroutines.CoroutineDispatcher -import java.nio.channels.AsynchronousSocketChannel -import java.util.function.Consumer - -/** - * @author Pengtao Qiu - */ -class AioTcpConnection( - id: Int, - maxIdleTime: Long, - socketChannel: AsynchronousSocketChannel, - dispatcher: CoroutineDispatcher, - inputBufferSize: Int -) : AbstractAioTcpConnection(id, maxIdleTime, socketChannel, dispatcher, inputBufferSize) { - - override fun isSecureConnection(): Boolean = false - - override fun isClientMode(): Boolean { - throw UnsupportedOperationException() - } - - override fun isHandshakeComplete(): Boolean = true - - override fun getSupportedApplicationProtocols(): List = listOf() - - override fun beginHandshake(result: Consumer>): TcpConnection { - result.accept(Result(true, "", null)) - return this - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpConnectionFactory.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpConnectionFactory.kt deleted file mode 100644 index 072cc3ff8..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpConnectionFactory.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.net.tcp.TcpChannelGroup -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.WrappedTcpConnection -import com.fireflysource.net.tcp.secure.SecureEngineFactory -import java.nio.channels.AsynchronousSocketChannel - -fun createSecureTcpConnection( - tcpConnection: TcpConnection, - peerHost: String, - peerPort: Int, - clientMode: Boolean, - supportedProtocols: List, - secureEngineFactory: SecureEngineFactory -): TcpConnection { - val rawTcpConnection = if (tcpConnection is WrappedTcpConnection) { - tcpConnection.rawTcpConnection - } else tcpConnection - val secureEngine = if (peerHost.isNotBlank() && peerPort != 0) { - secureEngineFactory.create(rawTcpConnection.coroutineScope, clientMode, peerHost, peerPort, supportedProtocols) - } else { - secureEngineFactory.create(rawTcpConnection.coroutineScope, clientMode, supportedProtocols) - } - return AioSecureTcpConnection(rawTcpConnection, secureEngine) -} - -fun createTcpConnection( - connectionId: Int, - socketChannel: AsynchronousSocketChannel, - group: TcpChannelGroup, - tcpConfig: TcpConfig, - peerHost: String, - peerPort: Int, - clientMode: Boolean, - supportedProtocols: List, - secureEngineFactory: SecureEngineFactory, -): TcpConnection { - val aioTcpConnection = AioTcpConnection( - connectionId, - tcpConfig.timeout, - socketChannel, - group.getDispatcher(connectionId), - tcpConfig.inputBufferSize - ) - - val tcpConnection = if (tcpConfig.enableSecureConnection) { - createSecureTcpConnection( - aioTcpConnection, - peerHost, - peerPort, - clientMode, - supportedProtocols, - secureEngineFactory - ) - } else aioTcpConnection - - return if (tcpConfig.enableOutputBuffer) { - BufferedOutputTcpConnection(tcpConnection, tcpConfig.outputBufferSize) - } else tcpConnection -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpCoroutineDispatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpCoroutineDispatcher.kt deleted file mode 100644 index 91d2a1256..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpCoroutineDispatcher.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.net.tcp.TcpCoroutineDispatcher -import kotlinx.coroutines.* - -class AioTcpCoroutineDispatcher( - id: Int, - private val dispatcher: CoroutineDispatcher, - private val supervisor: CompletableJob = SupervisorJob(), - private val scope: CoroutineScope = CoroutineScope(dispatcher + supervisor + CoroutineName("TcpConnection#$id")) -) : TcpCoroutineDispatcher { - - override fun getCoroutineDispatcher(): CoroutineDispatcher = dispatcher - - override fun getSupervisorJob(): CompletableJob = supervisor - - override fun getCoroutineScope(): CoroutineScope = scope - - override fun execute(runnable: Runnable) { - scope.launch { runnable.run() } - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpServer.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpServer.kt deleted file mode 100644 index 658900c39..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/AioTcpServer.kt +++ /dev/null @@ -1,203 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.tcp.TcpChannelGroup -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpServer -import com.fireflysource.net.tcp.secure.DefaultSecureEngineFactorySelector -import com.fireflysource.net.tcp.secure.SecureEngineFactory -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED -import kotlinx.coroutines.launch -import java.net.SocketAddress -import java.net.StandardSocketOptions -import java.nio.channels.* -import java.util.function.Consumer - -/** - * @author Pengtao Qiu - */ -class AioTcpServer(private val config: TcpConfig = TcpConfig()) : AbstractLifeCycle(), TcpServer { - - companion object { - private val log = SystemLogger.create(AioTcpServer::class.java) - } - - private var group: TcpChannelGroup = AioTcpChannelGroup("aio-tcp-server") - private var stopGroup = true - private val connectionChannel = Channel(UNLIMITED) - private var connectionConsumer: Consumer = Consumer { connectionChannel.trySend(it) } - private var secureEngineFactory: SecureEngineFactory = - DefaultSecureEngineFactorySelector.createSecureEngineFactory(false) - private var supportedProtocols: List = defaultSupportedProtocols - private var peerHost: String = "" - private var peerPort: Int = 0 - private var serverSocketChannel: AsynchronousServerSocketChannel? = null - private val acceptSocketConnectionCompletionHandler = - object : CompletionHandler { - override fun completed(socketChannel: AsynchronousSocketChannel, connectionId: Int) { - onAcceptCompleted(socketChannel, connectionId) - } - - override fun failed(e: Throwable, connectionId: Int) { - onAcceptFailed(e, connectionId) - } - } - - override fun init() { - group.start() - log.info("The Firefly HTTP server supported application protocols {}", supportedProtocols) - } - - override fun destroy() { - try { - serverSocketChannel?.close() - } catch (e: Exception) { - log.error(e) { "close server socket channel exception" } - } - if (stopGroup) group.stop() - } - - override fun tcpChannelGroup(group: TcpChannelGroup): TcpServer { - this.group = group - return this - } - - override fun stopTcpChannelGroup(stop: Boolean): TcpServer { - this.stopGroup = stop - return this - } - - override fun getTcpConnectionChannel(): Channel = connectionChannel - - override fun secureEngineFactory(secureEngineFactory: SecureEngineFactory): TcpServer { - this.secureEngineFactory = secureEngineFactory - return this - } - - override fun supportedProtocols(supportedProtocols: List): TcpServer { - this.supportedProtocols = supportedProtocols - return this - } - - override fun peerHost(peerHost: String): TcpServer { - this.peerHost = peerHost - return this - } - - override fun peerPort(peerPort: Int): TcpServer { - this.peerPort = peerPort - return this - } - - override fun enableSecureConnection(): TcpServer { - config.enableSecureConnection = true - return this - } - - override fun timeout(timeout: Long): TcpServer { - config.timeout = timeout - return this - } - - override fun bufferSize(bufferSize: Int): TcpServer { - config.outputBufferSize = bufferSize - return this - } - - override fun enableOutputBuffer(): TcpServer { - config.enableOutputBuffer = true - return this - } - - override fun onAccept(consumer: Consumer): TcpServer { - connectionConsumer = consumer - return this - } - - override fun listen(address: SocketAddress): TcpServer { - if (isStarted) { - return this - } - - start() - - try { - val socketChannel = AsynchronousServerSocketChannel.open(group.asynchronousChannelGroup) - socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, config.reuseAddr) - socketChannel.bind(address, config.backlog) - this.serverSocketChannel = socketChannel - accept() - } catch (e: Exception) { - log.error(e) { "bind server address exception" } - } - return this - } - - private fun accept() { - try { - serverSocketChannel?.accept(group.nextId, acceptSocketConnectionCompletionHandler) - } catch (e: ShutdownChannelGroupException) { - log.info { "the channel group is shutdown." } - } catch (e: Exception) { - log.error(e) { "accept socket channel exception." } - } - } - - private fun onAcceptCompleted(socketChannel: AsynchronousSocketChannel, connectionId: Int) { - try { - socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, config.reuseAddr) - socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, config.keepAlive) - socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, config.tcpNoDelay) - - connectionConsumer.accept( - createTcpConnection( - connectionId, - socketChannel, - group, - config, - peerHost, - peerPort, - false, - supportedProtocols, - secureEngineFactory - ) - ) - log.debug { "accept the client connection. $connectionId" } - } catch (e: Exception) { - log.warn(e) { "accept connection exception. $connectionId" } - } finally { - accept() - } - } - - private fun onAcceptFailed(e: Throwable, connectionId: Int) { - when (e) { - is ClosedChannelException -> { - log.info { "The server socket channel has been closed." } - } - is ShutdownChannelGroupException -> { - log.info { "the server is shutdown. stop to accept connection." } - } - else -> { - log.warn(e) { "accept connection failure. $connectionId" } - accept() - } - } - } - - public override fun clone(): AioTcpServer { - val config = this.config.copy() - val server = AioTcpServer(config) - server.group = this.group - server.stopGroup = this.stopGroup - return server - } -} - -fun TcpServer.onAcceptAsync(block: suspend CoroutineScope.(TcpConnection) -> Unit): TcpServer { - onAccept { connection -> connection.coroutineScope.launch { block(connection) } } - return this -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/BufferedOutputTcpConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/BufferedOutputTcpConnection.kt deleted file mode 100644 index 9d6864675..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/BufferedOutputTcpConnection.kt +++ /dev/null @@ -1,159 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.WrappedTcpConnection -import com.fireflysource.net.tcp.buffer.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.ByteBuffer -import java.nio.channels.ClosedChannelException -import java.util.function.Consumer - -class BufferedOutputTcpConnection( - private val tcpConnection: TcpConnection, - private val bufferSize: Int = 8192 -) : TcpConnection by tcpConnection, WrappedTcpConnection { - - companion object { - private val log = SystemLogger.create(BufferedOutputTcpConnection::class.java) - } - - private val buffer: ByteBuffer = BufferUtils.allocateDirect(bufferSize) - private var position = buffer.flipToFill() - private val outputMessageChannel: Channel = Channel(Channel.UNLIMITED) - - init { - flushJob() - } - - override fun getRawTcpConnection(): TcpConnection = tcpConnection - - private fun flushJob() = coroutineScope.launch { - while (true) { - when (val message = outputMessageChannel.receive()) { - is OutputBuffer -> appendOutputBuffer(message) - is OutputBufferList -> appendOutputBuffers(message) - is OutputBuffers -> appendOutputBuffers(message) - is FlushOutput -> flushBuffer(message) - is ShutdownOutput -> { - shutdownOutput(message) - break - } - else -> {} - } - } - }.invokeOnCompletion { cause -> - val e = cause ?: ClosedChannelException() - outputMessageChannel.consumeAll { message -> - when (message) { - is OutputBuffer -> message.result.accept(Result.createFailedResult(-1, e)) - is OutputBufferList -> message.result.accept(Result.createFailedResult(-1, e)) - is OutputBuffers -> message.result.accept(Result.createFailedResult(-1, e)) - is ShutdownOutput -> message.result.accept(Result.createFailedResult(e)) - is FlushOutput -> message.result.accept(Result.createFailedResult(e)) - else -> { - } - } - } - } - - private suspend fun appendOutputBuffer(message: OutputBuffer) { - try { - val remaining = message.buffer.remaining() - append(message.buffer) - message.result.accept(Result(true, remaining, null)) - } catch (e: Exception) { - message.result.accept(Result(false, -1, e)) - } - } - - private suspend fun appendOutputBuffers(message: OutputBuffers) { - try { - val remaining = message.remaining() - val offset = message.getCurrentOffset() - val lastIndex = message.getLastIndex() - (offset..lastIndex).map { message.buffers[it] }.forEach { append(it) } - message.result.accept(Result(true, remaining, null)) - } catch (e: Exception) { - message.result.accept(Result(false, -1, e)) - } - } - - private suspend fun append(src: ByteBuffer) { - while (src.hasRemaining()) { - val srcRemaining = src.remaining() - val consumed = BufferUtils.put(src, buffer) - log.debug { "Append buffer. id: $id, src: $srcRemaining, consumed: $consumed" } - if (!buffer.hasRemaining()) { - flushBuffer() - } - } - } - - private suspend fun flushBuffer(message: FlushOutput) { - try { - flushBuffer() - message.result.accept(Result.SUCCESS) - } catch (e: Exception) { - message.result.accept(Result.createFailedResult(e)) - } - } - - private suspend fun flushBuffer() { - buffer.flipToFlush(position) - val remaining = buffer.remaining() - val consumed = tcpConnection.write(buffer).await() - log.debug { "Flush buffer. id: $id, len: $remaining, consumed: $consumed" } - BufferUtils.clear(buffer) - position = buffer.flipToFill() - } - - private suspend fun shutdownOutput(message: ShutdownOutput) { - try { - flushBuffer() - tcpConnection.close(message.result) - } catch (e: Exception) { - message.result.accept(Result.createFailedResult(e)) - } - } - - override fun flush(result: Consumer>): TcpConnection { - outputMessageChannel.trySend(FlushOutput(result)) - return this - } - - override fun getBufferSize(): Int = bufferSize - - override fun write(byteBuffer: ByteBuffer, result: Consumer>): TcpConnection { - outputMessageChannel.trySend(OutputBuffer(byteBuffer, result)) - return this - } - - override fun write( - byteBuffers: Array, offset: Int, length: Int, result: Consumer> - ): TcpConnection { - val message = OutputBuffers(byteBuffers, offset, length, result) - outputMessageChannel.trySend(message) - return this - } - - override fun write( - byteBufferList: List, offset: Int, length: Int, result: Consumer> - ): TcpConnection { - val message = OutputBufferList(byteBufferList, offset, length, result) - outputMessageChannel.trySend(message) - return this - } - - override fun close(result: Consumer>): TcpConnection { - outputMessageChannel.trySend(ShutdownOutput(result)) - return this - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/TcpConfig.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/TcpConfig.kt deleted file mode 100644 index 4aa690385..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/aio/TcpConfig.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.annotation.NoArg -import com.fireflysource.net.tcp.exception.UnknownProtocolException - -/** - * @author Pengtao Qiu - */ -@NoArg -data class TcpConfig @JvmOverloads constructor( - var timeout: Long = 30, - var enableSecureConnection: Boolean = false, - var backlog: Int = 16 * 1024, - var reuseAddr: Boolean = true, - var keepAlive: Boolean = true, - var tcpNoDelay: Boolean = false, - var inputBufferSize: Int = 16 * 1024, - var outputBufferSize: Int = 16 * 1024, - var enableOutputBuffer: Boolean = false -) - -enum class ApplicationProtocol(val value: String) { - HTTP2("h2"), HTTP1("http/1.1") -} - -val defaultSupportedProtocols: List = ApplicationProtocol.values().map { it.value } - -val schemaDefaultPort = mapOf( - "http" to 80, - "https" to 443, - "ws" to 80, - "wss" to 443 -) - -fun isSecureProtocol(scheme: String): Boolean { - return when (scheme) { - "wss", "https" -> true - "ws", "http" -> false - else -> throw UnknownProtocolException("Unknown protocol $scheme") - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/buffer/InputOutputMessages.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/buffer/InputOutputMessages.kt deleted file mode 100644 index 3aefd2a0f..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/buffer/InputOutputMessages.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.fireflysource.net.tcp.buffer - -import com.fireflysource.common.sys.Result -import java.nio.ByteBuffer -import java.util.concurrent.CompletableFuture -import java.util.function.Consumer - -sealed interface InputMessage - -@JvmInline -value class InputBuffer(val bufferFuture: CompletableFuture) : InputMessage - -object ShutdownInput : InputMessage - -@JvmInline -value class SetReadTimeout(val timeout: Long) : InputMessage - -sealed interface OutputMessage - -sealed interface OutputDataMessage { - fun hasRemaining(): Boolean = false -} - -data class OutputBuffer(val buffer: ByteBuffer, val result: Consumer>) : OutputMessage, OutputDataMessage { - override fun hasRemaining(): Boolean = buffer.hasRemaining() -} - -open class OutputBuffers( - val buffers: Array, - val offset: Int, - val length: Int, - val result: Consumer>, - private val outputBufferArray: DelegatedOutputBufferArray = DelegatedOutputBufferArray( - buffers, offset, length, result - ) -) : OutputMessage, OutputBufferArray by outputBufferArray, OutputDataMessage { - override fun hasRemaining(): Boolean { - return outputBufferArray.hasRemaining() - } -} - -class OutputBufferList( - bufferList: List, - offset: Int, - length: Int, - result: Consumer> -) : OutputBuffers(bufferList.toTypedArray(), offset, length, result) - -@JvmInline -value class ShutdownOutput(val result: Consumer>) : OutputMessage - -@JvmInline -value class FlushOutput(val result: Consumer>) : OutputMessage - -@JvmInline -value class SetWriteTimeout(val timeout: Long) : OutputMessage - -interface OutputBufferArray { - fun getCurrentOffset(): Int - fun getCurrentLength(): Int - fun getLastIndex(): Int - fun remaining(): Long - fun hasRemaining(): Boolean -} - -class DelegatedOutputBufferArray( - val buffers: Array, - val offset: Int, - val length: Int, - val result: Consumer> -) : OutputBufferArray { - init { - require(offset >= 0) { "The offset must be greater than or equal the 0" } - require(length > 0) { "The length must be greater than 0" } - require(offset < buffers.size) { "The offset must be less than the buffer size" } - require((offset + length) <= buffers.size) { "The length must be less than or equal the buffer size" } - } - - private val maxSize = offset + length - private val lastIndex = maxSize - 1 - private var currentOffset = offset - - override fun getCurrentOffset(): Int { - for (i in currentOffset..lastIndex) { - if (buffers[i].hasRemaining()) { - currentOffset = i - return i - } - } - return maxSize - } - - override fun getCurrentLength(): Int { - return maxSize - getCurrentOffset() - } - - override fun getLastIndex(): Int = lastIndex - - override fun remaining(): Long { - val offset = getCurrentOffset() - return (offset..lastIndex).sumOf { buffers[it].remaining().toLong() } - } - - override fun hasRemaining(): Boolean { - return getCurrentOffset() < maxSize - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/secure/AbstractAsyncSecureEngine.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/secure/AbstractAsyncSecureEngine.kt deleted file mode 100644 index 4d78c795d..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/tcp/secure/AbstractAsyncSecureEngine.kt +++ /dev/null @@ -1,305 +0,0 @@ -package com.fireflysource.net.tcp.secure - -import com.fireflysource.common.coroutine.blocking -import com.fireflysource.common.exception.UnknownTypeException -import com.fireflysource.common.io.* -import com.fireflysource.common.io.BufferUtils.EMPTY_BUFFER -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.tcp.buffer.OutputBuffer -import com.fireflysource.net.tcp.buffer.OutputBufferList -import com.fireflysource.net.tcp.buffer.OutputBuffers -import com.fireflysource.net.tcp.buffer.OutputDataMessage -import com.fireflysource.net.tcp.secure.exception.SecureNetException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.ByteBuffer -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicBoolean -import java.util.function.Consumer -import java.util.function.Function -import java.util.function.Supplier -import javax.net.ssl.SSLEngine -import javax.net.ssl.SSLEngineResult -import javax.net.ssl.SSLEngineResult.HandshakeStatus.* -import javax.net.ssl.SSLEngineResult.Status.* - -abstract class AbstractAsyncSecureEngine( - private val coroutineScope: CoroutineScope, - private val sslEngine: SSLEngine, - private val applicationProtocolSelector: ApplicationProtocolSelector -) : SecureEngine { - - companion object { - private val log = SystemLogger.create(AbstractAsyncSecureEngine::class.java) - } - - private var readSupplier: Supplier>? = null - private var writeFunction: Function>? = null - - private var inPacketBuffer = EMPTY_BUFFER - private val inAppBuffer = BufferUtils.allocateDirect(sslEngine.session.applicationBufferSize) - private val outPacketBuffer = BufferUtils.allocateDirect(sslEngine.session.packetBufferSize) - - private val closed = AtomicBoolean(false) - private var handshakeStatus = sslEngine.handshakeStatus - private val handshakeFinished = AtomicBoolean(false) - private val beginHandshake = AtomicBoolean(false) - private var unwrapResultStatus: SSLEngineResult.Status = OK - - override fun onHandshakeRead(supplier: Supplier>): SecureEngine { - this.readSupplier = supplier - return this - } - - override fun onHandshakeWrite(function: Function>): SecureEngine { - this.writeFunction = function - return this - } - - override fun beginHandshake(result: Consumer>) { - if (beginHandshake.compareAndSet(false, true)) { - launchHandshakeJob(result) - } else { - result.accept( - Result( - false, null, - SecureNetException("The handshake has begun, do not invoke it method repeatedly.") - ) - ) - } - } - - private fun launchHandshakeJob(result: Consumer>) = coroutineScope.launch { - try { - val stashedAppBuffers = LinkedList() - doHandshake(stashedAppBuffers) - result.accept(Result(true, HandshakeData(stashedAppBuffers, applicationProtocol), null)) - } catch (e: Exception) { - result.accept(Result(false, null, e)) - } - } - - private suspend fun doHandshake(stashedAppBuffers: MutableList) { - begin() - handshakeLoop@ while (true) { - when (handshakeStatus) { - NEED_WRAP -> doHandshakeWrap() - NEED_UNWRAP -> doHandshakeUnwrap(stashedAppBuffers) - NEED_TASK -> runDelegatedTasks() - NOT_HANDSHAKING, FINISHED -> { - handshakeComplete() - break@handshakeLoop - } - else -> throw UnknownTypeException("Unknown handshake status. $handshakeStatus") - } - } - } - - private fun begin() { - sslEngine.beginHandshake() - handshakeStatus = sslEngine.handshakeStatus - val packetBufferSize = sslEngine.session.packetBufferSize - val applicationBufferSize = sslEngine.session.applicationBufferSize - val status = handshakeStatus - log.info { - "Begin TLS handshake. mode: ${getMode()}, status: ${status}, " + - "packetSize: ${packetBufferSize}, appSize: $applicationBufferSize" - } - } - - private suspend fun doHandshakeWrap() { - val bufferList = encrypt(EMPTY_BUFFER) - if (bufferList.hasRemaining()) { - val length = writeFunction?.apply(bufferList)?.await() - log.debug { "Wrap TLS handshake data. status: $handshakeStatus, mode: ${getMode()}, length: $length" } - } - } - - private suspend fun doHandshakeUnwrap(stashedAppBuffers: MutableList) { - val receivedBuffer = when { - unwrapResultStatus == BUFFER_UNDERFLOW -> readSupplier?.get()?.await() - inPacketBuffer.hasRemaining() -> EMPTY_BUFFER - else -> readSupplier?.get()?.await() - } - if (receivedBuffer != null) { - val length = inPacketBuffer.remaining() + receivedBuffer.remaining() - val inAppBuffer = decrypt(receivedBuffer) - val remaining = inAppBuffer.remaining() - if (remaining > 0) { - stashedAppBuffers.add(inAppBuffer) - } - log.debug { "Unwrap TLS handshake data. status: $handshakeStatus, mode: ${getMode()}, length: ${length}, stashedBuffer: $remaining" } - } - } - - private suspend fun runDelegatedTasks() { - while (true) { - val runnable: Runnable? = sslEngine.delegatedTask - if (runnable != null) { - log.debug { "Run TLS handshake delegated tasks. status: ${sslEngine.handshakeStatus}" } - blocking { runnable.run() }.join() - } else break - } - handshakeStatus = sslEngine.handshakeStatus - log.debug { "After run TLS handshake delegated tasks. no tasks: ${sslEngine.delegatedTask == null}, status: $handshakeStatus" } - } - - private fun handshakeComplete() { - if (handshakeFinished.compareAndSet(false, true)) { - val tlsProtocol = sslEngine.session.protocol - val cipherSuite = sslEngine.session.cipherSuite - val inPacketBufferRemaining = inPacketBuffer.remaining() - log.info( - "TLS handshake success. mode: ${getMode()}, protocol: {} {}, cipher: {}, status: {}, inPacketRemaining: {}", - applicationProtocol, tlsProtocol, cipherSuite, handshakeStatus, inPacketBufferRemaining - ) - } - } - - override fun encrypt(outAppBuffer: ByteBuffer): ByteBuffer = - encryptBuffers(OutputBuffer(outAppBuffer, discard())) - - override fun encrypt(byteBuffers: Array, offset: Int, length: Int): ByteBuffer = - encryptBuffers(OutputBuffers(byteBuffers, offset, length, discard())) - - - override fun encrypt(byteBuffers: MutableList, offset: Int, length: Int): ByteBuffer = - encryptBuffers(OutputBufferList(byteBuffers, offset, length, discard())) - - private fun encryptBuffers(outAppBuffer: OutputDataMessage): ByteBuffer { - var packetBuffer = this.outPacketBuffer - val pos = packetBuffer.flipToFill() - - fun wrap() = when (outAppBuffer) { - is OutputBuffer -> sslEngine.wrap(outAppBuffer.buffer, packetBuffer) - is OutputBufferList -> sslEngine.wrap(outAppBuffer.buffers, packetBuffer) - is OutputBuffers -> sslEngine.wrap(outAppBuffer.buffers, packetBuffer) - } - - wrap@ while (true) { - val result = wrap() - handshakeStatus = result.handshakeStatus - - when (result.status) { - BUFFER_OVERFLOW -> { - packetBuffer = packetBuffer.addCapacity(sslEngine.session.packetBufferSize) - val capacity = packetBuffer.capacity() - val remaining = packetBuffer.remaining() - log.debug { "Resize out packet buffer. capacity: $capacity, remaining: $remaining" } - } - OK -> { - if (handshakeStatus != NEED_WRAP && result.bytesProduced() == 0 && result.bytesConsumed() == 0) { - break@wrap - } - if (!outAppBuffer.hasRemaining()) { - break@wrap - } - } - CLOSED -> { - sslEngine.closeOutbound() - break@wrap - } - else -> throw SecureNetException("Wrap data result status error. ${result.status}") - } - - } - - return packetBuffer.flipToFlush(pos).copy().also { BufferUtils.clear(this.outPacketBuffer) } - } - - override fun decrypt(receivedBuffer: ByteBuffer): ByteBuffer { - merge(receivedBuffer) - - if (!inPacketBuffer.hasRemaining()) { - return EMPTY_BUFFER - } - - var appBuffer = this.inAppBuffer - val pos = appBuffer.flipToFill() - - unwrap@ while (true) { - val result = sslEngine.unwrap(inPacketBuffer, appBuffer) - handshakeStatus = result.handshakeStatus - unwrapResultStatus = result.status - - when (result.status) { - BUFFER_UNDERFLOW -> { - if (inPacketBuffer.remaining() < sslEngine.session.packetBufferSize) { - break@unwrap - } - } - BUFFER_OVERFLOW -> { - appBuffer = appBuffer.addCapacity(sslEngine.session.applicationBufferSize) - val capacity = appBuffer.capacity() - val remaining = appBuffer.remaining() - log.debug { "Resize in app buffer. capacity: $capacity, remaining: $remaining" } - } - OK -> { - if (handshakeStatus != NEED_UNWRAP && result.bytesProduced() == 0 && result.bytesConsumed() == 0) { - break@unwrap - } - if (!inPacketBuffer.hasRemaining()) { - inPacketBuffer = EMPTY_BUFFER - break@unwrap - } - } - CLOSED -> { - sslEngine.closeInbound() - break@unwrap - } - else -> throw SecureNetException("Unwrap packets state exception. ${result.status}") - } - } - - return appBuffer.flipToFlush(pos).copy().also { BufferUtils.clear(this.inAppBuffer) } - } - - private fun merge(receivedBuffer: ByteBuffer) { - if (!receivedBuffer.hasRemaining()) { - return - } - - inPacketBuffer = if (inPacketBuffer.hasRemaining()) { - log.debug { - "Merge received packet buffer. mode: ${getMode()}, " + - "in packet buffer: ${inPacketBuffer.remaining()}, " + - "received buffer: ${receivedBuffer.remaining()}" - } - - val capacity = inPacketBuffer.remaining() + receivedBuffer.remaining() - BufferUtils.allocate(capacity).append(inPacketBuffer).append(receivedBuffer) - } else { - receivedBuffer - } - } - - private fun getMode(): String = if (isClientMode) "Client" else "Server" - - override fun isClientMode(): Boolean = sslEngine.useClientMode - - override fun getSupportedApplicationProtocols(): MutableList = - applicationProtocolSelector.supportedApplicationProtocols - - override fun getApplicationProtocol(): String = applicationProtocolSelector.applicationProtocol - - override fun close() { - if (closed.compareAndSet(false, true)) { - sslEngine.closeOutbound() - } - } - - override fun isHandshakeComplete(): Boolean = handshakeFinished.get() - - private class HandshakeData( - private val stashedAppBuffers: MutableList, - private val applicationProtocol: String - ) : HandshakeResult { - override fun getApplicationProtocol(): String = applicationProtocol - override fun getStashedAppBuffers(): MutableList = stashedAppBuffers - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/buffer/InputOutputMessages.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/udp/buffer/InputOutputMessages.kt deleted file mode 100644 index e65acf046..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/buffer/InputOutputMessages.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.udp.buffer - -import com.fireflysource.net.udp.UdpConnection -import java.nio.ByteBuffer -import java.nio.channels.SelectionKey -import java.util.concurrent.CompletableFuture - -sealed interface InputMessage - -@JvmInline -value class InputBuffer(val bufferFuture: CompletableFuture) : InputMessage - -object CancelSelectionKey : InputMessage - -object InvalidSelectionKey : InputMessage - -object UnregisterRead : InputMessage -@JvmInline -value class ReadComplete(val buffer: ByteBuffer): InputMessage - -sealed interface NioWorkerMessage - -data class RegisterRead( - val udpConnection: UdpConnection, - val future: CompletableFuture -) : NioWorkerMessage \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/AbstractNioUdpConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/AbstractNioUdpConnection.kt deleted file mode 100644 index f1d98a133..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/AbstractNioUdpConnection.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.fireflysource.net.udp.nio - -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.AbstractConnection -import com.fireflysource.net.udp.UdpConnection -import com.fireflysource.net.udp.UdpCoroutineDispatcher -import com.fireflysource.net.udp.buffer.* -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.ByteBuffer -import java.nio.channels.ClosedChannelException -import java.nio.channels.DatagramChannel -import java.nio.channels.SelectionKey -import java.util.* -import java.util.concurrent.TimeUnit -import java.util.function.Consumer - -abstract class AbstractNioUdpConnection( - id: Int, - maxIdleTime: Long, - dispatcher: CoroutineDispatcher, - inputBufferSize: Int, - private val nioUdpCoroutineDispatcher: UdpCoroutineDispatcher = NioUdpCoroutineDispatcher(id, dispatcher), - val datagramChannel: DatagramChannel, - private val nioUdpWorker: NioUdpWorker, -) : AbstractConnection(id, System.currentTimeMillis(), maxIdleTime), UdpConnection, - UdpCoroutineDispatcher by nioUdpCoroutineDispatcher { - - companion object { - private val log = SystemLogger.create(AbstractNioUdpConnection::class.java) - private val timeUnit = TimeUnit.SECONDS - } - - private val closeResultChannel: Channel>> = Channel(Channel.UNLIMITED) - private val inputMessageHandler = InputMessageHandler(inputBufferSize) - - private inner class InputMessageHandler(inputBufferSize: Int) { - private val inputMessageChannel: Channel = Channel(Channel.UNLIMITED) - private val readRequestQueue = LinkedList() - private val readCompleteQueue = LinkedList() - private var readTimeout = maxIdleTime - private var readWaterline = 0 - private var selectionKey: SelectionKey? = null - - init { - readJob() - } - - - private fun readJob() = coroutineScope.launch { - selectionKey = nioUdpWorker.registerRead(this@AbstractNioUdpConnection).await() - while (true) { - when (val msg = inputMessageChannel.receive()) { - is InputBuffer -> offerReadRequest(msg) - is ReadComplete -> offerReadComplete(msg.buffer) - is CancelSelectionKey -> { - selectionKey = null - } - is InvalidSelectionKey -> { - selectionKey = null - } - is UnregisterRead -> { - - } - } - } - }.invokeOnCompletion { cause -> - val e = cause ?: ClosedChannelException() - inputMessageChannel.consumeAll { message -> - if (message is InputBuffer) { - message.bufferFuture.completeExceptionally(e) - } - } - closeResultChannel.consumeAll { it.accept(Result.SUCCESS) } - } - - private fun offerReadComplete(buffer: ByteBuffer) { - if (readCompleteQueue.offer(buffer)) { - readWaterline += buffer.remaining() - updateReadQueue() - } - } - - private fun pollReadComplete(): ByteBuffer? { - val buffer = readCompleteQueue.poll() - if (buffer != null) { - readWaterline -= buffer.remaining() - } - return buffer - } - - private fun offerReadRequest(request: InputBuffer) { - if (readRequestQueue.offer(request)) { - updateReadQueue() - } - } - - private fun updateReadQueue() { - while (!readRequestQueue.isEmpty() && !readCompleteQueue.isEmpty()) { - val readRequest = readRequestQueue.poll()!! - val buffer: ByteBuffer = pollReadComplete()!! - readRequest.bufferFuture.complete(buffer) - } - } - - fun sendMessage(message: InputMessage) { - if (inputMessageChannel.trySend(message).isFailure) { - log.error("send input message failure. $message") - } - } - - } - - fun sendInvalidSelectionKeyMessage() { - inputMessageHandler.sendMessage(InvalidSelectionKey) - } - - fun sendCancelSelectionKeyMessage() { - inputMessageHandler.sendMessage(CancelSelectionKey) - } - - fun sendUnregisterReadMessage() { - inputMessageHandler.sendMessage(UnregisterRead) - } - - fun sendReadCompleteMessage(buffer: ByteBuffer) { - inputMessageHandler.sendMessage(ReadComplete(buffer)) - } - - -} - -enum class ReadResult { - REMOTE_CLOSE, SUSPEND_READ, CONTINUE_READ, READ_EXCEPTION -} - -enum class WriteResult { - REMOTE_CLOSE, SUSPEND_WRITE, CONTINUE_WRITE, WRITE_EXCEPTION -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/NioUdpCoroutineDispatcher.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/NioUdpCoroutineDispatcher.kt deleted file mode 100644 index 3d830476f..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/NioUdpCoroutineDispatcher.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.net.udp.nio - -import com.fireflysource.net.udp.UdpCoroutineDispatcher -import kotlinx.coroutines.* - -class NioUdpCoroutineDispatcher( - id: Int, - private val dispatcher: CoroutineDispatcher, - private val supervisor: CompletableJob = SupervisorJob(), - private val scope: CoroutineScope = CoroutineScope(dispatcher + supervisor + CoroutineName("UdpConnection#$id")) -) : UdpCoroutineDispatcher { - override fun execute(runnable: Runnable) { - scope.launch { runnable.run() } - } - - override fun getCoroutineDispatcher(): CoroutineDispatcher = dispatcher - - override fun getCoroutineScope(): CoroutineScope = scope - - override fun getSupervisorJob(): CompletableJob = supervisor -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/NioUdpWorker.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/NioUdpWorker.kt deleted file mode 100644 index 4a6211ad2..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/udp/nio/NioUdpWorker.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.fireflysource.net.udp.nio - -import com.fireflysource.common.concurrent.ExecutorServiceUtils.shutdownAndAwaitTermination -import com.fireflysource.common.coroutine.CoroutineDispatchers.newSingleThreadExecutor -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.copy -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.udp.UdpConnection -import com.fireflysource.net.udp.buffer.NioWorkerMessage -import com.fireflysource.net.udp.buffer.RegisterRead -import com.fireflysource.net.udp.exception.UdpAttachmentTypeException -import org.jctools.queues.MpscLinkedQueue -import java.nio.channels.SelectionKey -import java.nio.channels.Selector -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit - -class NioUdpWorker( - id: Int -) : AbstractLifeCycle(), Runnable { - - companion object { - private val log = SystemLogger.create(NioUdpWorker::class.java) - private val seconds = TimeUnit.SECONDS - } - - private val executor = newSingleThreadExecutor("firefly-nio-udp-worker-thread-$id") - private val selector = Selector.open() - private val workerMessageQueue = MpscLinkedQueue() - private val inputBuffer = BufferUtils.allocateDirect(8 * 1024) - - override fun init() { - executor.execute(this) - } - - override fun destroy() { - val closeResult = runCatching { selector.close() } - log.info { "Nio UDP worker selector close result: $closeResult" } - shutdownAndAwaitTermination(executor, 5, seconds) - } - - override fun run() { - while (true) { - val count = selectKeys() - handleNioUdpWorkerMessages() - if (count == 0) - continue - - val iterator = selector.selectedKeys().iterator() - while (iterator.hasNext()) { - val selectedKey = iterator.next() - iterator.remove() - - val result = runCatching { - val udpConnection = selectedKey.attachment() - if (udpConnection !is AbstractNioUdpConnection) { - throw UdpAttachmentTypeException("attachment type exception. ${udpConnection::class.java.name}") - } - val datagramChannel = udpConnection.datagramChannel - - fun readComplete(): ReadResult { - val pos = inputBuffer.flipToFill() - val result = runCatching { datagramChannel.read(inputBuffer) } - inputBuffer.flipToFlush(pos) - - return if (result.isSuccess) { - val length = result.getOrDefault(0) - when { - length > 0 -> { - udpConnection.sendReadCompleteMessage(inputBuffer.copy()) - ReadResult.CONTINUE_READ - } - - length == 0 -> { - ReadResult.CONTINUE_READ - } - - else -> { - ReadResult.REMOTE_CLOSE - } - } - } else { - ReadResult.REMOTE_CLOSE - } - } - - fun writeComplete(): WriteResult { - return WriteResult.CONTINUE_WRITE - } - - fun cancelSelectedKey() { - selectedKey.cancel() - selector.selectNow() - udpConnection.sendCancelSelectionKeyMessage() - } - if (selectedKey.isValid) { - if (selectedKey.isReadable) { - when (readComplete()) { - ReadResult.CONTINUE_READ -> TODO() - ReadResult.REMOTE_CLOSE -> { - val unregisterReadResult = runCatching { - selectedKey.interestOps(selectedKey.interestOps() and SelectionKey.OP_READ.inv()) - } - if (unregisterReadResult.isSuccess) { - udpConnection.sendUnregisterReadMessage() - } else { - val e = unregisterReadResult.exceptionOrNull() - if (e != null && e is IllegalArgumentException) { - cancelSelectedKey() - } - } - } - - ReadResult.SUSPEND_READ -> TODO() - ReadResult.READ_EXCEPTION -> TODO() - } - } - if (selectedKey.isWritable) { - when (writeComplete()) { - WriteResult.REMOTE_CLOSE -> { - } - - WriteResult.SUSPEND_WRITE -> TODO() - WriteResult.CONTINUE_WRITE -> TODO() - WriteResult.WRITE_EXCEPTION -> TODO() - } - } - } else { - udpConnection.sendInvalidSelectionKeyMessage() - } - } - if (result.isFailure) { - log.error { "handle nio selected key failure. $result" } - } - } - } - } - - fun registerRead(udpConnection: UdpConnection): CompletableFuture { - val future = CompletableFuture() - sendMessage(RegisterRead(udpConnection, future)) - return future - } - - private fun selectKeys(): Int { - return selector.select() - } - - private fun sendMessage(message: NioWorkerMessage) { - if (workerMessageQueue.offer(message)) { - selector.wakeup() - } - } - - private fun handleNioUdpWorkerMessages() { - while (true) { - val message = workerMessageQueue.poll() ?: break - when (message) { - is RegisterRead -> { - if (message.udpConnection is AbstractNioUdpConnection) { - val udpConnection = message.udpConnection - val datagramChannel = message.udpConnection.datagramChannel - val key = datagramChannel.register( - selector, - SelectionKey.OP_READ, - udpConnection - ) - message.future.complete(key) - } - } - } - } - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/client/impl/AsyncWebSocketClientConnectionBuilder.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/client/impl/AsyncWebSocketClientConnectionBuilder.kt deleted file mode 100644 index 6f9167fe3..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/client/impl/AsyncWebSocketClientConnectionBuilder.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.fireflysource.net.websocket.client.impl - -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.net.websocket.client.WebSocketClientConnectionBuilder -import com.fireflysource.net.websocket.client.WebSocketClientConnectionManager -import com.fireflysource.net.websocket.client.WebSocketClientRequest -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.WebSocketMessageHandler -import com.fireflysource.net.websocket.common.frame.Frame -import com.fireflysource.net.websocket.common.model.WebSocketBehavior -import com.fireflysource.net.websocket.common.model.WebSocketPolicy -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.util.concurrent.CompletableFuture - -/** - * @author Pengtao Qiu - */ -class AsyncWebSocketClientConnectionBuilder( - private val connectionManager: WebSocketClientConnectionManager -) : WebSocketClientConnectionBuilder { - - private val request = WebSocketClientRequest() - - override fun url(url: String): WebSocketClientConnectionBuilder { - request.url = url - return this - } - - override fun policy(policy: WebSocketPolicy): WebSocketClientConnectionBuilder { - request.policy = policy - return this - } - - override fun extensions(extensions: List): WebSocketClientConnectionBuilder { - request.extensions = extensions - return this - } - - override fun subProtocols(subProtocols: List): WebSocketClientConnectionBuilder { - request.subProtocols = subProtocols - return this - } - - override fun onMessage(handler: WebSocketMessageHandler): WebSocketClientConnectionBuilder { - request.handler = handler - return this - } - - override fun connect(): CompletableFuture { - if (request.policy == null) { - request.policy = WebSocketPolicy(WebSocketBehavior.CLIENT) - } - if (request.subProtocols == null) { - request.subProtocols = listOf() - } - if (request.extensions == null) { - request.extensions = listOf() - } - return connectionManager.connect(request) - } - -} - -fun WebSocketClientConnectionBuilder.connectAsync(block: suspend CoroutineScope.(WebSocketConnection) -> Unit) { - this.connect().thenAccept { connection -> connection.coroutineScope.launch { block(connection) } } -} - -fun WebSocketClientConnectionBuilder.onClientMessageAsync(block: suspend CoroutineScope.(Frame, WebSocketConnection) -> Unit): WebSocketClientConnectionBuilder { - this.onMessage { frame, connection -> connection.coroutineScope.launch { block(frame, connection) }.asVoidFuture() } - return this -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/client/impl/AsyncWebSocketClientConnectionManager.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/client/impl/AsyncWebSocketClientConnectionManager.kt deleted file mode 100644 index f1761e8a5..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/client/impl/AsyncWebSocketClientConnectionManager.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.fireflysource.net.websocket.client.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.common.lifecycle.AbstractLifeCycle -import com.fireflysource.net.http.client.impl.Http1ClientConnection -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.tcp.TcpClientConnectionFactory -import com.fireflysource.net.tcp.aio.ApplicationProtocol.HTTP1 -import com.fireflysource.net.websocket.client.WebSocketClientConnectionManager -import com.fireflysource.net.websocket.client.WebSocketClientRequest -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.exception.WebSocketException -import com.fireflysource.net.websocket.common.model.WebSocketBehavior -import java.net.InetSocketAddress -import java.util.concurrent.CompletableFuture - -/** - * @author Pengtao Qiu - */ -class AsyncWebSocketClientConnectionManager( - private val config: HttpConfig, - private val connectionFactory: TcpClientConnectionFactory -) : WebSocketClientConnectionManager, AbstractLifeCycle() { - - init { - start() - } - - override fun connect(request: WebSocketClientRequest): CompletableFuture { - Assert.hasText(request.url, "The websocket url must be not blank") - Assert.notNull(request.policy, "The websocket policy must be not null") - Assert.notNull(request.handler, "The websocket message handler must be not null") - Assert.isTrue(request.policy.behavior == WebSocketBehavior.CLIENT, "The websocket behavior must be client") - - val uri = HttpURI(request.url) - val inetSocketAddress = InetSocketAddress(uri.host, uri.port) - val tcpConnection = when (uri.scheme) { - "ws" -> connectionFactory.connect(inetSocketAddress, false) - "wss" -> connectionFactory.connect(inetSocketAddress, true, listOf(HTTP1.value)) - else -> throw WebSocketException("The websocket scheme error. scheme: ${uri.scheme}") - } - - return tcpConnection - .thenCompose { connection -> connection.beginHandshake().thenApply { connection } } - .thenApply { Http1ClientConnection(config, it) } - .thenCompose { it.upgradeWebSocket(request) } - } - - override fun init() { - connectionFactory.start() - } - - override fun destroy() { - connectionFactory.stop() - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/common/impl/AsyncWebSocketConnection.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/common/impl/AsyncWebSocketConnection.kt deleted file mode 100644 index 92b9c122a..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/common/impl/AsyncWebSocketConnection.kt +++ /dev/null @@ -1,255 +0,0 @@ -package com.fireflysource.net.websocket.common.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.coroutine.consumeAll -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.common.sys.Result.futureToConsumer -import com.fireflysource.common.sys.SystemLogger -import com.fireflysource.net.Connection -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpCoroutineDispatcher -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.WebSocketConnectionState -import com.fireflysource.net.websocket.common.WebSocketMessageHandler -import com.fireflysource.net.websocket.common.decoder.Parser -import com.fireflysource.net.websocket.common.encoder.Generator -import com.fireflysource.net.websocket.common.exception.NextIncomingFramesNotSetException -import com.fireflysource.net.websocket.common.extension.ExtensionFactory -import com.fireflysource.net.websocket.common.extension.WebSocketExtensionFactory -import com.fireflysource.net.websocket.common.frame.* -import com.fireflysource.net.websocket.common.model.* -import com.fireflysource.net.websocket.common.stream.ConnectionState -import com.fireflysource.net.websocket.common.stream.ExtensionNegotiator -import com.fireflysource.net.websocket.common.stream.IOState -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import java.nio.ByteBuffer -import java.nio.channels.ClosedChannelException -import java.util.concurrent.CancellationException -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ThreadLocalRandom -import java.util.function.Consumer - - -/** - * @author Pengtao Qiu - */ -class AsyncWebSocketConnection( - private val tcpConnection: TcpConnection, - private val webSocketPolicy: WebSocketPolicy, - private val url: String, - private val extensions: List = listOf(), - private val extensionFactory: ExtensionFactory = defaultExtensionFactory, - private val subProtocols: List = listOf(), - private val ioState: IOState = IOState(), - private val remainingData: ByteBuffer? = null -) : Connection by tcpConnection, TcpCoroutineDispatcher by tcpConnection, - WebSocketConnectionState by ioState, WebSocketConnection, - IncomingFrames, OutgoingFrames { - - companion object { - private val log = SystemLogger.create(AsyncWebSocketConnection::class.java) - val defaultExtensionFactory = WebSocketExtensionFactory() - } - - private val extensionNegotiator = ExtensionNegotiator(extensionFactory) - private val parser = Parser(webSocketPolicy) - private val generator = Generator(webSocketPolicy) - private val messageChannel = Channel(Channel.UNLIMITED) - private var messageHandler: WebSocketMessageHandler? = null - - override fun getUrl(): String = url - - override fun getExtensions(): List = extensions - - override fun getSubProtocols(): List = subProtocols - - override fun getPolicy(): WebSocketPolicy = webSocketPolicy - - override fun getExtensionFactory(): ExtensionFactory = extensionFactory - - override fun generateMask(): ByteArray { - val mask = ByteArray(4) - ThreadLocalRandom.current().nextBytes(mask) - return mask - } - - override fun sendData(data: ByteBuffer): CompletableFuture { - val binaryFrame = BinaryFrame() - binaryFrame.payload = data - return sendFrame(binaryFrame) - } - - override fun sendText(text: String): CompletableFuture { - val textFrame = TextFrame() - textFrame.setPayload(text) - return sendFrame(textFrame) - } - - override fun sendFrame(frame: Frame): CompletableFuture { - val future = CompletableFuture() - outgoingFrame(frame, futureToConsumer(future)) - return future - } - - override fun incomingFrame(frame: Frame) { - when (frame.type) { - Frame.Type.PING -> { - val pong = PongFrame() - outgoingFrame(pong, discard()) - } - Frame.Type.PONG -> log.info { "The websocket connection received pong frame. id: ${this.id}" } - Frame.Type.CLOSE -> { - val closeFrame = frame as CloseFrame - val closeInfo = CloseInfo(closeFrame.payload, false) - ioState.onCloseRemote(closeInfo) - } - else -> { - } - } - extensionNegotiator.incomingFrames.incomingFrame(frame) - } - - override fun setWebSocketMessageHandler(handler: WebSocketMessageHandler) { - extensionNegotiator.setNextIncomingFrames { messageChannel.trySend(it) } - messageHandler = handler - } - - override fun outgoingFrame(frame: Frame, result: Consumer>) { - extensionNegotiator.outgoingFrames.outgoingFrame(frame, result) - } - - private fun close(code: Int, reason: String?): CompletableFuture { - val closeInfo = CloseInfo(code, reason) - return close(closeInfo) - } - - private fun close(closeInfo: CloseInfo): CompletableFuture { - val closeFrame = closeInfo.asFrame() - return sendFrame(closeFrame) - } - - override fun closeAsync(): CompletableFuture { - return close(StatusCode.NORMAL, null) - } - - override fun close() { - closeAsync() - } - - override fun begin() { - if (extensionNegotiator.nextIncomingFrames == null) { - throw NextIncomingFramesNotSetException("Please set the next incoming frames listener before start websocket connection.") - } - - parser.incomingFramesHandler = this - setNextOutgoingFrames() - configureExtensions() - ioState.onConnected() - - receiveMessageJob() - parseFrameJob() - ioState.addListener { state -> - when (state) { - ConnectionState.CLOSED -> tcpConnection.closeAsync() - ConnectionState.CLOSING -> { - if (ioState.isOutputAvailable && ioState.isRemoteCloseInitiated) { - close(StatusCode.NORMAL, null) - } - } - else -> { - } - } - } - ioState.onOpen() - } - - private fun parseFrameJob() { - tcpConnection.coroutineScope.launch { - try { - if (remainingData != null && remainingData.hasRemaining()) { - parser.parse(remainingData) - } - } catch (e: Exception) { - log.error(e) { "Parse websocket frame error. id: ${this@AsyncWebSocketConnection.id}" } - ioState.onReadFailure(e) - } - - while (true) { - try { - val buffer = tcpConnection.read().await() - parser.parse(buffer) - } catch (e: CancellationException) { - log.info { "The websocket parsing job canceled. id: ${this@AsyncWebSocketConnection.id}" } - break - } catch (e: ClosedChannelException) { - log.warn("The remote endpoint closed connection. message: ${e.message} id: ${this@AsyncWebSocketConnection.id}") - } catch (e: Exception) { - log.error(e) { "Parse websocket frame error. id: ${this@AsyncWebSocketConnection.id}" } - ioState.onReadFailure(e) - break - } - } - - } - } - - private fun receiveMessageJob() { - tcpConnection.coroutineScope.launch { - while (true) { - val frame = messageChannel.receive() - try { - messageHandler?.handle(frame, this@AsyncWebSocketConnection)?.await() - } catch (e: CancellationException) { - log.info { "The websocket receiving message job canceled. id: ${this@AsyncWebSocketConnection.id}" } - } catch (e: Exception) { - log.error(e) { "Handle websocket frame exception. id: ${this@AsyncWebSocketConnection.id}" } - } - } - }.invokeOnCompletion { cause -> - log.info { "The websocket connection closed, handle the remaining message. id: ${this@AsyncWebSocketConnection.id}, cause: ${cause?.message}" } - messageChannel.consumeAll { frame -> - try { - messageHandler?.handle(frame, this@AsyncWebSocketConnection) - } catch (e: Exception) { - log.error(e) { "Handle websocket frame exception. id: ${this@AsyncWebSocketConnection.id}" } - } - } - } - } - - private fun configureExtensions() { - extensionNegotiator.configureExtensions(extensions, parser, generator, policy) - } - - private fun setNextOutgoingFrames() { - extensionNegotiator.setNextOutgoingFrames { frame, result -> - if (policy.behavior == WebSocketBehavior.CLIENT && frame is WebSocketFrame && !frame.isMasked) { - frame.mask = generateMask() - } - - val buf = BufferUtils.allocate(Generator.MAX_HEADER_LENGTH + frame.payloadLength) - val pos = buf.flipToFill() - generator.generateWholeFrame(frame, buf) - buf.flipToFlush(pos) - tcpConnection.writeAndFlush(buf) - .thenAccept { - if (frame.type == Frame.Type.CLOSE && frame is CloseFrame) { - val closeInfo = CloseInfo(frame.getPayload(), false) - ioState.onCloseLocal(closeInfo) - } - result.accept(Result.SUCCESS) - } - .exceptionallyAccept { - result.accept(Result.createFailedResult(it)) - ioState.onWriteFailure(it) - } - } - } - -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/server/impl/AsyncWebSocketManager.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/server/impl/AsyncWebSocketManager.kt deleted file mode 100644 index 0279f1bcc..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/server/impl/AsyncWebSocketManager.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.fireflysource.net.websocket.server.impl - -import com.fireflysource.common.`object`.Assert -import com.fireflysource.net.websocket.common.model.WebSocketBehavior -import com.fireflysource.net.websocket.server.WebSocketManager -import com.fireflysource.net.websocket.server.WebSocketServerConnectionHandler - -/** - * @author Pengtao Qiu - */ -class AsyncWebSocketManager : WebSocketManager { - - private val webSocketHandlers: MutableMap = HashMap() - - override fun register(connectionHandler: WebSocketServerConnectionHandler) { - Assert.notNull(connectionHandler.url, "The websocket url must be not null") - Assert.notNull(connectionHandler.extensionSelector, "The websocket extension selector must be not null") - Assert.notNull(connectionHandler.subProtocolSelector, "The websocket sub protocol selector must be not null") - Assert.notNull(connectionHandler.policy, "The websocket policy must be not null") - Assert.notNull(connectionHandler.connectionListener, "The websocket connection listener must be not null") - Assert.notNull(connectionHandler.messageHandler, "The websocket message handler must be not null") - Assert.isTrue( - connectionHandler.policy.behavior == WebSocketBehavior.SERVER, - "The websocket behavior must be server" - ) - - webSocketHandlers[connectionHandler.url] = connectionHandler - } - - override fun findWebSocketHandler(path: String): WebSocketServerConnectionHandler? { - return webSocketHandlers[path] - } - - public override fun clone(): AsyncWebSocketManager { - val newWebSocketManager = AsyncWebSocketManager() - newWebSocketManager.webSocketHandlers.putAll(this.webSocketHandlers) - return newWebSocketManager - } -} \ No newline at end of file diff --git a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/server/impl/AsyncWebSocketServerConnectionBuilder.kt b/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/server/impl/AsyncWebSocketServerConnectionBuilder.kt deleted file mode 100644 index 826606b69..000000000 --- a/firefly-net/src/main/kotlin/com/fireflysource/net/websocket/server/impl/AsyncWebSocketServerConnectionBuilder.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.fireflysource.net.websocket.server.impl - -import com.fireflysource.common.coroutine.asVoidFuture -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.WebSocketMessageHandler -import com.fireflysource.net.websocket.common.frame.Frame -import com.fireflysource.net.websocket.common.impl.AsyncWebSocketConnection.Companion.defaultExtensionFactory -import com.fireflysource.net.websocket.common.model.ExtensionConfig -import com.fireflysource.net.websocket.common.model.WebSocketBehavior -import com.fireflysource.net.websocket.common.model.WebSocketPolicy -import com.fireflysource.net.websocket.server.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -/** - * @author Pengtao Qiu - */ -class AsyncWebSocketServerConnectionBuilder( - private val httpServer: HttpServer, - private val webSocketManager: WebSocketManager -) : WebSocketServerConnectionBuilder { - - private val connectionHandler = WebSocketServerConnectionHandler() - - override fun url(url: String): WebSocketServerConnectionBuilder { - connectionHandler.url = url - return this - } - - override fun policy(policy: WebSocketPolicy): WebSocketServerConnectionBuilder { - connectionHandler.policy = policy - return this - } - - override fun onExtensionSelect(selector: ExtensionSelector): WebSocketServerConnectionBuilder { - connectionHandler.extensionSelector = selector - return this - } - - override fun onSubProtocolSelect(selector: SubProtocolSelector): WebSocketServerConnectionBuilder { - connectionHandler.subProtocolSelector = selector - return this - } - - override fun onMessage(handler: WebSocketMessageHandler): WebSocketServerConnectionBuilder { - connectionHandler.messageHandler = handler - return this - } - - override fun onAccept(listener: WebSocketServerConnectionListener): HttpServer { - connectionHandler.connectionListener = listener - if (connectionHandler.policy == null) { - connectionHandler.policy = WebSocketPolicy(WebSocketBehavior.SERVER) - } - if (connectionHandler.subProtocolSelector == null) { - connectionHandler.setSubProtocolSelector { listOf() } - } - if (connectionHandler.extensionSelector == null) { - connectionHandler.setExtensionSelector { clientExtensions -> - if (clientExtensions.isNullOrEmpty()) { - listOf() - } else { - ExtensionConfig - .parseList(clientExtensions) - .filter { c -> defaultExtensionFactory.isAvailable(c.name) } - .map { c -> c.name } - } - } - } - webSocketManager.register(connectionHandler) - return httpServer - } -} - -fun WebSocketServerConnectionBuilder.onAcceptAsync(block: suspend CoroutineScope.(WebSocketConnection) -> Unit): HttpServer { - return this.onAccept { connection -> - connection.coroutineScope.launch { block(connection) } - Result.DONE - } -} - -fun WebSocketServerConnectionBuilder.onServerMessageAsync(block: suspend CoroutineScope.(Frame, WebSocketConnection) -> Unit): WebSocketServerConnectionBuilder { - this.onMessage { frame, connection -> connection.coroutineScope.launch { block(frame, connection) }.asVoidFuture() } - return this -} \ No newline at end of file diff --git a/firefly-net/src/main/resources/com/fireflysource/net/http/common/model/encoding.properties b/firefly-net/src/main/resources/com/fireflysource/net/http/common/model/encoding.properties deleted file mode 100644 index 327ef71ae..000000000 --- a/firefly-net/src/main/resources/com/fireflysource/net/http/common/model/encoding.properties +++ /dev/null @@ -1,6 +0,0 @@ -text/html=utf-8 -text/plain=iso-8859-1 -text/xml=utf-8 -application/xhtml+xml=utf-8 -text/json=-utf-8 -application/vnd.api+json=-utf-8 \ No newline at end of file diff --git a/firefly-net/src/main/resources/com/fireflysource/net/http/common/model/mime.properties b/firefly-net/src/main/resources/com/fireflysource/net/http/common/model/mime.properties deleted file mode 100644 index ed4e4540e..000000000 --- a/firefly-net/src/main/resources/com/fireflysource/net/http/common/model/mime.properties +++ /dev/null @@ -1,191 +0,0 @@ -ai=application/postscript -aif=audio/x-aiff -aifc=audio/x-aiff -aiff=audio/x-aiff -apk=application/vnd.android.package-archive -asc=text/plain -asf=video/x.ms.asf -asx=video/x.ms.asx -au=audio/basic -avi=video/x-msvideo -bcpio=application/x-bcpio -bin=application/octet-stream -bmp=image/bmp -br=application/brotli -cab=application/x-cabinet -cdf=application/x-netcdf -chm=application/vnd.ms-htmlhelp -class=application/java-vm -cpio=application/x-cpio -cpt=application/mac-compactpro -crt=application/x-x509-ca-cert -csh=application/x-csh -css=text/css -csv=text/csv -dcr=application/x-director -dir=application/x-director -dll=application/x-msdownload -dms=application/octet-stream -doc=application/msword -dtd=application/xml-dtd -dvi=application/x-dvi -dxr=application/x-director -eps=application/postscript -etx=text/x-setext -exe=application/octet-stream -ez=application/andrew-inset -gif=image/gif -gtar=application/x-gtar -gz=application/gzip -gzip=application/gzip -hdf=application/x-hdf -hqx=application/mac-binhex40 -htc=text/x-component -htm=text/html -html=text/html -ice=x-conference/x-cooltalk -ico=image/x-icon -ief=image/ief -iges=model/iges -igs=model/iges -jad=text/vnd.sun.j2me.app-descriptor -jar=application/java-archive -java=text/plain -jnlp=application/x-java-jnlp-file -jpe=image/jpeg -jp2=image/jpeg2000 -jpeg=image/jpeg -jpg=image/jpeg -js=application/javascript -json=application/json -jsp=text/html -kar=audio/midi -latex=application/x-latex -lha=application/octet-stream -lzh=application/octet-stream -man=application/x-troff-man -mathml=application/mathml+xml -me=application/x-troff-me -mesh=model/mesh -mid=audio/midi -midi=audio/midi -mif=application/vnd.mif -mol=chemical/x-mdl-molfile -mov=video/quicktime -movie=video/x-sgi-movie -mp2=audio/mpeg -mp3=audio/mpeg -mpe=video/mpeg -mpeg=video/mpeg -mpg=video/mpeg -mpga=audio/mpeg -ms=application/x-troff-ms -msh=model/mesh -msi=application/octet-stream -nc=application/x-netcdf -oda=application/oda -odb=application/vnd.oasis.opendocument.database -odc=application/vnd.oasis.opendocument.chart -odf=application/vnd.oasis.opendocument.formula -odg=application/vnd.oasis.opendocument.graphics -odi=application/vnd.oasis.opendocument.image -odm=application/vnd.oasis.opendocument.text-master -odp=application/vnd.oasis.opendocument.presentation -ods=application/vnd.oasis.opendocument.spreadsheet -odt=application/vnd.oasis.opendocument.text -ogg=application/ogg -otc=application/vnd.oasis.opendocument.chart-template -otf=application/vnd.oasis.opendocument.formula-template -otg=application/vnd.oasis.opendocument.graphics-template -oth=application/vnd.oasis.opendocument.text-web -oti=application/vnd.oasis.opendocument.image-template -otp=application/vnd.oasis.opendocument.presentation-template -ots=application/vnd.oasis.opendocument.spreadsheet-template -ott=application/vnd.oasis.opendocument.text-template -pbm=image/x-portable-bitmap -pdb=chemical/x-pdb -pdf=application/pdf -pgm=image/x-portable-graymap -pgn=application/x-chess-pgn -png=image/png -pnm=image/x-portable-anymap -ppm=image/x-portable-pixmap -pps=application/vnd.ms-powerpoint -ppt=application/vnd.ms-powerpoint -ps=application/postscript -qml=text/x-qml -qt=video/quicktime -ra=audio/x-pn-realaudio -rar=application/x-rar-compressed -ram=audio/x-pn-realaudio -ras=image/x-cmu-raster -rdf=application/rdf+xml -rgb=image/x-rgb -rm=audio/x-pn-realaudio -roff=application/x-troff -rpm=application/x-rpm -rtf=application/rtf -rtx=text/richtext -rv=video/vnd.rn-realvideo -ser=application/java-serialized-object -sgm=text/sgml -sgml=text/sgml -sh=application/x-sh -shar=application/x-shar -silo=model/mesh -sit=application/x-stuffit -skd=application/x-koan -skm=application/x-koan -skp=application/x-koan -skt=application/x-koan -smi=application/smil -smil=application/smil -snd=audio/basic -spl=application/x-futuresplash -src=application/x-wais-source -sv4cpio=application/x-sv4cpio -sv4crc=application/x-sv4crc -svg=image/svg+xml -svgz=image/svg+xml -swf=application/x-shockwave-flash -t=application/x-troff -tar=application/x-tar -tar.gz=application/x-gtar -tcl=application/x-tcl -tex=application/x-tex -texi=application/x-texinfo -texinfo=application/x-texinfo -tgz=application/x-gtar -tif=image/tiff -tiff=image/tiff -tr=application/x-troff -tsv=text/tab-separated-values -txt=text/plain -ustar=application/x-ustar -vcd=application/x-cdlink -vrml=model/vrml -vxml=application/voicexml+xml -wav=audio/x-wav -wbmp=image/vnd.wap.wbmp -wml=text/vnd.wap.wml -wmlc=application/vnd.wap.wmlc -wmls=text/vnd.wap.wmlscript -wmlsc=application/vnd.wap.wmlscriptc -wrl=model/vrml -wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate -xbm=image/x-xbitmap -xcf=image/xcf -xht=application/xhtml+xml -xhtml=application/xhtml+xml -xls=application/vnd.ms-excel -xml=application/xml -xpm=image/x-xpixmap -xsd=application/xml -xsl=application/xml -xslt=application/xslt+xml -xul=application/vnd.mozilla.xul+xml -xwd=image/x-xwindowdump -xyz=chemical/x-xyz -xz=application/x-xz -z=application/compress -zip=application/zip diff --git a/firefly-net/src/main/resources/fireflyKeystore.jks b/firefly-net/src/main/resources/fireflyKeystore.jks deleted file mode 100644 index 9694f10c8..000000000 Binary files a/firefly-net/src/main/resources/fireflyKeystore.jks and /dev/null differ diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/ContentEncodedTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/ContentEncodedTest.java deleted file mode 100644 index 9d4c2e59f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/ContentEncodedTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.model.ContentEncoding; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -public class ContentEncodedTest { - - static Stream testParametersProvider() { - return Stream.of( - arguments(ContentEncoding.GZIP), - arguments(ContentEncoding.DEFLATE) - ); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should encode and decode content successfully.") - void test(ContentEncoding encoding) throws Exception { - ByteBuffer buffer = BufferUtils.toBuffer("测试hello", StandardCharsets.UTF_8); - byte[] encodedBytes = ContentEncoded.encode(BufferUtils.toArray(buffer), encoding); - byte[] decodedBytes = ContentEncoded.decode(encodedBytes, encoding); - assertEquals("测试hello", new String(decodedBytes, StandardCharsets.UTF_8)); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/CookieTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/CookieTest.java deleted file mode 100644 index a65bc923d..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/CookieTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.fireflysource.net.http.common.codec; - - -import com.fireflysource.net.http.common.model.Cookie; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class CookieTest { - - @Test - void setCookieTest() { - Cookie cookie = new Cookie("test31", "hello"); - cookie.setDomain("www.fireflysource.com"); - cookie.setPath("/test/hello"); - cookie.setMaxAge(10); - cookie.setSecure(true); - cookie.setComment("commenttest"); - cookie.setVersion(20); - - String setCookieString = CookieGenerator.generateSetCookie(cookie); - - Cookie setCookie = CookieParser.parseSetCookie(setCookieString); - assertEquals("test31", setCookie.getName()); - assertEquals("hello", setCookie.getValue()); - assertEquals("www.fireflysource.com", setCookie.getDomain()); - assertEquals("/test/hello", setCookie.getPath()); - assertTrue(setCookie.getSecure()); - assertEquals("commenttest", setCookie.getComment()); - assertEquals(20, setCookie.getVersion()); - } - - @Test - void cookieTest() { - Cookie cookie = new Cookie("test21", "hello"); - String cookieString = CookieGenerator.generateCookie(cookie); - - List list = CookieParser.parseCookie(cookieString); - assertEquals(1, list.size()); - assertEquals("test21", list.get(0).getName()); - assertEquals("hello", list.get(0).getValue()); - } - - @Test - void cookieListTest() { - List list = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - list.add(new Cookie("test" + i, "hello" + i)); - } - String cookieString = CookieGenerator.generateCookies(list); - System.out.println(cookieString); - - List ret = CookieParser.parseCookie(cookieString); - assertEquals(10, ret.size()); - for (int i = 0; i < 10; i++) { - assertEquals("test" + i, ret.get(i).getName()); - assertEquals("hello" + i, ret.get(i).getValue()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/HttpURIParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/HttpURIParseTest.java deleted file mode 100644 index 1bd0b2c67..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/HttpURIParseTest.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.net.http.common.model.HttpURI; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -class HttpURIParseTest { - - public static Stream testParametersProvider() { - return data().stream().map(arr -> arguments(Arrays.asList(arr).toArray())); - } - - public static List data() { - String[][] tests = { - // Nothing but path - {"path", null, null, "-1", "path", null, null, null}, - {"path/path", null, null, "-1", "path/path", null, null, null}, - {"%65ncoded/path", null, null, "-1", "%65ncoded/path", null, null, null}, - - // Basic path reference - {"/path/to/context", null, null, "-1", "/path/to/context", null, null, null}, - - // Basic with encoded query - {"http://example.com/path/to/context;param?query=%22value%22#fragment", "http", "example.com", "-1", - "/path/to/context;param", "param", "query=%22value%22", "fragment"}, - {"http://[::1]/path/to/context;param?query=%22value%22#fragment", "http", "[::1]", "-1", - "/path/to/context;param", "param", "query=%22value%22", "fragment"}, - - // Basic with parameters and query - {"http://example.com:8080/path/to/context;param?query=%22value%22#fragment", "http", "example.com", - "8080", "/path/to/context;param", "param", "query=%22value%22", "fragment"}, - {"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment", "http", "[::1]", "8080", - "/path/to/context;param", "param", "query=%22value%22", "fragment"}, - - // Path References - {"/path/info", null, null, null, "/path/info", null, null, null}, - {"/path/info#fragment", null, null, null, "/path/info", null, null, "fragment"}, - {"/path/info?query", null, null, null, "/path/info", null, "query", null}, - {"/path/info?query#fragment", null, null, null, "/path/info", null, "query", "fragment"}, - {"/path/info;param", null, null, null, "/path/info;param", "param", null, null}, - {"/path/info;param#fragment", null, null, null, "/path/info;param", "param", null, "fragment"}, - {"/path/info;param?query", null, null, null, "/path/info;param", "param", "query", null}, - {"/path/info;param?query#fragment", null, null, null, "/path/info;param", "param", "query", - "fragment"}, - - // Protocol Less (aka scheme-less) URIs - {"//host/path/info", null, "host", null, "/path/info", null, null, null}, - {"//user@host/path/info", null, "host", null, "/path/info", null, null, null}, - {"//user@host:8080/path/info", null, "host", "8080", "/path/info", null, null, null}, - {"//host:8080/path/info", null, "host", "8080", "/path/info", null, null, null}, - - // Host Less - {"http:/path/info", "http", null, null, "/path/info", null, null, null}, - {"http:/path/info#fragment", "http", null, null, "/path/info", null, null, "fragment"}, - {"http:/path/info?query", "http", null, null, "/path/info", null, "query", null}, - {"http:/path/info?query#fragment", "http", null, null, "/path/info", null, "query", "fragment"}, - {"http:/path/info;param", "http", null, null, "/path/info;param", "param", null, null}, - {"http:/path/info;param#fragment", "http", null, null, "/path/info;param", "param", null, "fragment"}, - {"http:/path/info;param?query", "http", null, null, "/path/info;param", "param", "query", null}, - {"http:/path/info;param?query#fragment", "http", null, null, "/path/info;param", "param", "query", - "fragment"}, - - // Everything and the kitchen sink - {"http://user@host:8080/path/info;param?query#fragment", "http", "host", "8080", "/path/info;param", - "param", "query", "fragment"}, - {"xxxxx://user@host:8080/path/info;param?query#fragment", "xxxxx", "host", "8080", "/path/info;param", - "param", "query", "fragment"}, - - // No host, parameter with no content - {"http:///;?#", "http", null, null, "/;", "", "", ""}, - - // Path with query that has no value - {"/path/info?a=?query", null, null, null, "/path/info", null, "a=?query", null}, - - // Path with query alt syntax - {"/path/info?a=;query", null, null, null, "/path/info", null, "a=;query", null}, - - // URI with host character - {"/@path/info", null, null, null, "/@path/info", null, null, null}, - {"/user@path/info", null, null, null, "/user@path/info", null, null, null}, - {"//user@host/info", null, "host", null, "/info", null, null, null}, - {"//@host/info", null, "host", null, "/info", null, null, null}, - {"@host/info", null, null, null, "@host/info", null, null, null}, - - // Scheme-less, with host and port (overlapping with path) - {"//host:8080//", null, "host", "8080", "//", null, null, null}, - - // File reference - {"file:///path/info", "file", null, null, "/path/info", null, null, null}, - {"file:/path/info", "file", null, null, "/path/info", null, null, null}, - - // Bad URI (no scheme, no host, no path) - {"//", null, null, null, null, null, null, null}, - - // Simple localhost references - {"http://localhost/", "http", "localhost", null, "/", null, null, null}, - {"http://localhost:8080/", "http", "localhost", "8080", "/", null, null, null}, - {"http://localhost/?x=y", "http", "localhost", null, "/", null, "x=y", null}, - - // Simple path with parameter - {"/;param", null, null, null, "/;param", "param", null, null}, - {";param", null, null, null, ";param", "param", null, null}, - - // Simple path with query - {"/?x=y", null, null, null, "/", null, "x=y", null}, - {"/?abc=test", null, null, null, "/", null, "abc=test", null}, - - // Simple path with fragment - {"/#fragment", null, null, null, "/", null, null, "fragment"}, - - // Simple IPv4 host with port (default path) - {"http://192.0.0.1:8080/", "http", "192.0.0.1", "8080", "/", null, null, null}, - - // Simple IPv6 host with port (default path) - - {"http://[2001:db8::1]:8080/", "http", "[2001:db8::1]", "8080", "/", null, null, null}, - // IPv6 authenticated host with port (default path) - - {"http://user@[2001:db8::1]:8080/", "http", "[2001:db8::1]", "8080", "/", null, null, null}, - - // Simple IPv6 host no port (default path) - {"http://[2001:db8::1]/", "http", "[2001:db8::1]", null, "/", null, null, null}, - - // Scheme-less IPv6, host with port (default path) - {"//[2001:db8::1]:8080/", null, "[2001:db8::1]", "8080", "/", null, null, null}, - - // Interpreted as relative path of "*" (no - // host/port/scheme/query/fragment) - {"*", null, null, null, "*", null, null, null}, - - // Path detection Tests (seen from JSP/JSTL and use - {"http://host:8080/path/info?q1=v1&q2=v2", "http", "host", "8080", "/path/info", null, "q1=v1&q2=v2", - null}, - {"/path/info?q1=v1&q2=v2", null, null, null, "/path/info", null, "q1=v1&q2=v2", null}, - {"/info?q1=v1&q2=v2", null, null, null, "/info", null, "q1=v1&q2=v2", null}, - {"info?q1=v1&q2=v2", null, null, null, "info", null, "q1=v1&q2=v2", null}, - {"info;q1=v1?q2=v2", null, null, null, "info;q1=v1", "q1=v1", "q2=v2", null}, - - // Path-less, query only (seen from JSP/JSTL and use - {"?q1=v1&q2=v2", null, null, null, "", null, "q1=v1&q2=v2", null} - }; - - return Arrays.asList(tests); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void testParseString(String input, String scheme, String host, String port, String path, String param, String query, String fragment) { - HttpURI httpUri = new HttpURI(input); - - try { - new URI(input); - // URI is valid (per java.net.URI parsing) - - // Test case sanity check - assertNotNull(path); - - // Assert expectations - assertEquals(host, httpUri.getHost()); - assertEquals(port == null ? -1 : Integer.parseInt(port), httpUri.getPort()); - assertEquals(path, httpUri.getPath()); - assertEquals(param, httpUri.getParam()); - assertEquals(query, httpUri.getQuery()); - assertEquals(fragment, httpUri.getFragment()); - assertEquals(input, httpUri.toString()); - assertEquals(scheme, httpUri.getScheme()); - } catch (URISyntaxException e) { - // Assert HttpURI values for invalid URI (such as "//") - assertNull(httpUri.getScheme()); - assertNull(httpUri.getHost()); - assertEquals(-1, httpUri.getPort()); - assertNull(httpUri.getPath()); - assertNull(httpUri.getParam()); - assertNull(httpUri.getQuery()); - assertNull(httpUri.getFragment()); - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void testParseURI(String input, String scheme, String host, String port, String path, String param, String query, String fragment) { - URI javaUri = null; - try { - javaUri = new URI(input); - } catch (URISyntaxException ignore) { - } - - assumeTrue(javaUri != null); - HttpURI httpUri = new HttpURI(javaUri); - - assertEquals(scheme, httpUri.getScheme()); - assertEquals(host, httpUri.getHost()); - assertEquals(port == null ? -1 : Integer.parseInt(port), httpUri.getPort()); - assertEquals(path, httpUri.getPath()); - assertEquals(param, httpUri.getParam()); - assertEquals(query, httpUri.getQuery()); - assertEquals(fragment, httpUri.getFragment()); - assertEquals(input, httpUri.toString()); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void testCompareToJavaNetURI(String input, String scheme, String host, String port, String path, String param, String query, String fragment) { - URI javaUri = null; - try { - javaUri = new URI(input); - } catch (URISyntaxException ignore) { - } - - assumeTrue(javaUri != null); - HttpURI httpUri = new HttpURI(javaUri); - - assertEquals(javaUri.getScheme(), httpUri.getScheme()); - assertEquals(javaUri.getHost(), httpUri.getHost()); - assertEquals(javaUri.getPort(), httpUri.getPort()); - assertEquals(javaUri.getRawPath(), httpUri.getPath()); - assertEquals(javaUri.getRawQuery(), httpUri.getQuery()); - assertEquals(javaUri.getFragment(), httpUri.getFragment()); - assertEquals(javaUri.toASCIIString(), httpUri.toString()); - } -} \ No newline at end of file diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/InclusiveByteRangeTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/InclusiveByteRangeTest.java deleted file mode 100644 index 7c99d8e25..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/InclusiveByteRangeTest.java +++ /dev/null @@ -1,297 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - -import static org.junit.jupiter.api.Assertions.*; - -public class InclusiveByteRangeTest { - private void assertInvalidRange(String rangeString) { - Vector strings = new Vector<>(); - strings.add(rangeString); - - List ranges = InclusiveByteRange.satisfiableRanges(strings.elements(), 200); - assertNull(ranges, "Invalid Range [" + rangeString + "] should result in no satisfiable ranges"); - } - - private void assertRange(String msg, int expectedFirst, int expectedLast, int size, InclusiveByteRange actualRange) { - assertEquals(expectedFirst, actualRange.getFirst(), msg + " - first"); - assertEquals(expectedLast, actualRange.getLast(), msg + " - last"); - String expectedHeader = String.format("bytes %d-%d/%d", expectedFirst, expectedLast, size); - assertEquals(expectedHeader, actualRange.toHeaderRangeString(size)); - } - - private void assertSimpleRange(int expectedFirst, int expectedLast, String rangeId, int size) { - InclusiveByteRange range = parseRange(rangeId, size); - - assertEquals(expectedFirst, range.getFirst(), "Range [" + rangeId + "] - first"); - assertEquals(expectedLast, range.getLast(), "Range [" + rangeId + "] - last"); - String expectedHeader = String.format("bytes %d-%d/%d", expectedFirst, expectedLast, size); - assertEquals(expectedHeader, range.toHeaderRangeString(size), "Range [" + rangeId + "] - header range string"); - } - - private InclusiveByteRange parseRange(String rangeString, int size) { - Vector strings = new Vector<>(); - strings.add(rangeString); - - List ranges = InclusiveByteRange.satisfiableRanges(strings.elements(), size); - assertNotNull(ranges, "Satisfiable Ranges should not be null"); - assertEquals(1, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - return ranges.iterator().next(); - } - - private List parseRanges(int size, String... rangeString) { - Vector strings = new Vector<>(Arrays.asList(rangeString)); - - List ranges = InclusiveByteRange.satisfiableRanges(strings.elements(), size); - assertNotNull(ranges, "Satisfiable Ranges should not be null"); - return ranges; - } - - @Test - public void testHeader416RangeString() { - assertEquals("bytes */100", InclusiveByteRange.to416HeaderRangeString(100), "416 Header on size 100"); - assertEquals("bytes */123456789", InclusiveByteRange.to416HeaderRangeString(123456789), "416 Header on size 123456789"); - } - - @Test - public void testInvalidRanges() { - // Invalid if parsing "Range" header - assertInvalidRange("bytes=a-b"); // letters invalid - assertInvalidRange("byte=10-3"); // key is bad - assertInvalidRange("onceuponatime=5-10"); // key is bad - assertInvalidRange("bytes=300-310"); // outside of size (200) - } - - /** - * Ranges have a multiple ranges, all absolutely defined. - */ - @Test - public void testMultipleAbsoluteRanges() { - int size = 50; - String rangeString; - - rangeString = "bytes=5-20,35-65"; - - List ranges = parseRanges(size, rangeString); - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 5, 20, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 35, 49, size, inclusiveByteRangeIterator.next()); - } - - /** - * Ranges have a multiple ranges, all absolutely defined. - */ - @Test - public void testMultipleAbsoluteRangesSplit() { - int size = 50; - - List ranges = parseRanges(size, "bytes=5-20", "bytes=35-65"); - assertEquals(2, ranges.size()); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("testMultipleAbsoluteRangesSplit[0]", 5, 20, size, inclusiveByteRangeIterator.next()); - assertRange("testMultipleAbsoluteRangesSplit[1]", 35, 49, size, inclusiveByteRangeIterator.next()); - } - - /** - * Range definition has a range that is clipped due to the size. - */ - @Test - public void testMultipleRangesClipped() { - int size = 50; - String rangeString; - - rangeString = "bytes=5-20,35-65,-5"; - - List ranges = parseRanges(size, rangeString); - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 5, 20, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 35, 49, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testMultipleRangesOverlapping() { - int size = 200; - String rangeString; - - rangeString = "bytes=5-20,15-25"; - - List ranges = parseRanges(size, rangeString); - assertEquals(1, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 5, 25, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testMultipleRangesSplit() { - int size = 200; - String rangeString; - rangeString = "bytes=5-10,15-20"; - - List ranges = parseRanges(size, rangeString); - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 5, 10, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 15, 20, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testMultipleSameRangesSplit() { - int size = 200; - String rangeString; - rangeString = "bytes=5-10,15-20,5-10,15-20,5-10,5-10,5-10,5-10,5-10,5-10"; - - List ranges = parseRanges(size, rangeString); - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 5, 10, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 15, 20, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testMultipleOverlappingRanges() { - int size = 200; - String rangeString; - rangeString = "bytes=5-15,20-30,10-25"; - - List ranges = parseRanges(size, rangeString); - assertEquals(1, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 5, 30, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testMultipleOverlappingRangesOrdered() { - int size = 200; - String rangeString; - rangeString = "bytes=20-30,5-15,0-5,25-35"; - - List ranges = parseRanges(size, rangeString); - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 20, 35, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 0, 15, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testMultipleOverlappingRangesOrderedSplit() { - int size = 200; - String rangeString; - rangeString = "bytes=20-30,5-15,0-5,25-35"; - List ranges = parseRanges(size, "bytes=20-30", "bytes=5-15", "bytes=0-5,25-35"); - - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 20, 35, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 0, 15, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testNasty() { - int size = 200; - String rangeString; - - rangeString = "bytes=90-100, 10-20, 30-40, -161"; - List ranges = parseRanges(size, rangeString); - - assertEquals(2, ranges.size(), "Satisfiable Ranges of [" + rangeString + "] count"); - Iterator inclusiveByteRangeIterator = ranges.iterator(); - assertRange("Range [" + rangeString + "]", 30, 199, size, inclusiveByteRangeIterator.next()); - assertRange("Range [" + rangeString + "]", 10, 20, size, inclusiveByteRangeIterator.next()); - } - - @Test - public void testRangeOpenEnded() { - assertSimpleRange(50, 499, "bytes=50-", 500); - } - - @Test - public void testSimpleRange() { - assertSimpleRange(5, 10, "bytes=5-10", 200); - assertSimpleRange(195, 199, "bytes=-5", 200); - assertSimpleRange(50, 119, "bytes=50-150", 120); - assertSimpleRange(50, 119, "bytes=50-", 120); - - assertSimpleRange(1, 50, "bytes= 1 - 50", 120); - } - - // evaluate this vs assertInvalidRange() above, which behavior is correct? null? or empty list? - private void assertBadRangeList(int size, String badRange) { - Vector strings = new Vector<>(); - strings.add(badRange); - - List ranges = InclusiveByteRange.satisfiableRanges(strings.elements(), size); - // if one part is bad, the entire set of ranges should be treated as bad, per RFC7233 - assertNull(ranges); - } - - @Test - @Disabled - public void testBadRangeSetPartiallyBad() { - assertBadRangeList(500, "bytes=1-50,1-b,a-50"); - } - - @Test - public void testBadRangeNoNumbers() { - assertBadRangeList(500, "bytes=a-b"); - } - - @Test - public void testBadRangeEmpty() { - assertBadRangeList(500, "bytes="); - } - - @Test - @Disabled - public void testBadRangeZeroPrefixed() { - assertBadRangeList(500, "bytes=01-050"); - } - - @Test - public void testBadRangeHex() { - assertBadRangeList(500, "bytes=0F-FF"); - } - - @Test - @Disabled - public void testBadRangeTabWhitespace() { - assertBadRangeList(500, "bytes=\t1\t-\t50"); - } - - @Test - public void testBadRangeTabDelim() { - assertBadRangeList(500, "bytes=1-50\t90-101\t200-250"); - } - - @Test - public void testBadRangeSemiColonDelim() { - assertBadRangeList(500, "bytes=1-50;90-101;200-250"); - } - - @Test - public void testBadRangeNegativeSize() { - assertBadRangeList(500, "bytes=50-1"); - } - - @Test - public void testBadRangeDoubleDash() { - assertBadRangeList(500, "bytes=1--20"); - } - - @Test - public void testBadRangeTrippleDash() { - assertBadRangeList(500, "bytes=1---"); - } - - @Test - public void testBadRangeZeroedNegativeSize() { - assertBadRangeList(500, "bytes=050-001"); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/MultiPartParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/MultiPartParserTest.java deleted file mode 100644 index 402ffeb06..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/MultiPartParserTest.java +++ /dev/null @@ -1,665 +0,0 @@ -package com.fireflysource.net.http.common.codec; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.exception.BadMessageException; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; - -import static com.fireflysource.net.http.common.codec.MultiPartParser.State; -import static org.junit.jupiter.api.Assertions.*; - -public class MultiPartParserTest { - - @Test - public void testEmptyPreamble() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() { - }, "BOUNDARY"); - ByteBuffer data = BufferUtils.toBuffer(""); - - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - } - - @Test - public void testNoPreamble() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() { - }, "BOUNDARY"); - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY \r\n"); - - parser.parse(data, false); - assertTrue(parser.isState(State.BODY_PART)); - assertEquals(0, data.remaining()); - } - - @Test - public void testPreamble() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() { - }, "BOUNDARY"); - ByteBuffer data; - - data = BufferUtils.toBuffer("This is not part of a part\r\n"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - - data = BufferUtils.toBuffer("More data that almost includes \n--BOUNDARY but no CR before."); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - - data = BufferUtils.toBuffer("Could be a boundary \r\n--BOUNDAR"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - - data = BufferUtils.toBuffer("but not it isn't \r\n--BOUN"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - - data = BufferUtils.toBuffer("DARX nor is this"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - } - - @Test - public void testPreambleCompleteBoundary() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() { - }, "BOUNDARY"); - ByteBuffer data = BufferUtils.toBuffer("This is not part of a part\r\n--BOUNDARY \r\n"); - - parser.parse(data, false); - assertEquals(MultiPartParser.State.BODY_PART, parser.getState()); - assertEquals(0, data.remaining()); - } - - @Test - public void testPreambleSplitBoundary() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() { - }, "BOUNDARY"); - ByteBuffer data = BufferUtils.toBuffer("This is not part of a part\r\n"); - - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - data = BufferUtils.toBuffer("-"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - data = BufferUtils.toBuffer("-"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - data = BufferUtils.toBuffer("B"); - parser.parse(data, false); - assertEquals(State.PREAMBLE, parser.getState()); - assertEquals(0, data.remaining()); - data = BufferUtils.toBuffer("OUNDARY-"); - parser.parse(data, false); - assertEquals(State.DELIMITER_CLOSE, parser.getState()); - assertEquals(0, data.remaining()); - data = BufferUtils.toBuffer("ignore\r"); - parser.parse(data, false); - assertEquals(State.DELIMITER_PADDING, parser.getState()); - assertEquals(0, data.remaining()); - data = BufferUtils.toBuffer("\n"); - parser.parse(data, false); - assertEquals(State.BODY_PART, parser.getState()); - assertEquals(0, data.remaining()); - } - - @Test - public void testFirstPartNoFields() { - MultiPartParser parser = new MultiPartParser(new MultiPartParser.Handler() { - }, "BOUNDARY"); - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n\r\n"); - - parser.parse(data, false); - assertEquals(State.FIRST_OCTETS, parser.getState()); - assertEquals(0, data.remaining()); - } - - @Test - public void testFirstPartFields() { - TestHandler handler = new TestHandler() { - @Override - public boolean headerComplete() { - super.headerComplete(); - return true; - } - }; - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name0: value0\r\n" + - "name1 :value1 \r\n" + - "name2:value\r\n" + - " 2\r\n" + - "\r\n" + - "Content"); - - parser.parse(data, false); - assertEquals(State.FIRST_OCTETS, parser.getState()); - assertEquals(7, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name0: value0", "name1: value1", "name2: value 2", "<>"))); - } - - @Test - public void testFirstPartNoContent() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name: value\r\n" + - "\r\n" + - "\r\n" + - "--BOUNDARY"); - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.contains("<>")); - } - - @Test - public void testFirstPartNoContentNoCRLF() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name: value\r\n" + - "\r\n" + - "--BOUNDARY"); - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.contains("<>")); - } - - @Test - public void testFirstPartContentLookingLikeNoCRLF() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name: value\r\n" + - "\r\n" + - "-"); - parser.parse(data, false); - data = BufferUtils.toBuffer("Content!"); - parser.parse(data, false); - - assertEquals(State.OCTETS, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("-", "Content!"))); - } - - @Test - public void testFirstPartPartialContent() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name: value\n" + - "\r\n" + - "Hello\r\n"); - parser.parse(data, false); - assertEquals(State.OCTETS, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.contains("Hello")); - - data = BufferUtils.toBuffer( - "Now is the time for all good ment to come to the aid of the party.\r\n" + - "How now brown cow.\r\n" + - "The quick brown fox jumped over the lazy dog.\r\n" + - "this is not a --BOUNDARY\r\n"); - parser.parse(data, false); - assertEquals(State.OCTETS, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Hello", "\r\n", "Now is the time for all good ment to come to the aid of the party.\r\n" + - "How now brown cow.\r\n" + - "The quick brown fox jumped over the lazy dog.\r\n" + - "this is not a --BOUNDARY"))); - } - - @Test - public void testFirstPartShortContent() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name: value\n" + - "\r\n" + - "Hello\r\n" + - "--BOUNDARY"); - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Hello", "<>"))); - } - - @Test - public void testFirstPartLongContent() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\r\n" + - "name: value\n" + - "\r\n" + - "Now is the time for all good ment to come to the aid of the party.\r\n" + - "How now brown cow.\r\n" + - "The quick brown fox jumped over the lazy dog.\r\n" + - "\r\n" + - "--BOUNDARY"); - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Now is the time for all good ment to come to the aid of the party.\r\n" + - "How now brown cow.\r\n" + - "The quick brown fox jumped over the lazy dog.\r\n", "<>"))); - } - - @Test - public void testFirstPartLongContentNoCarriageReturn() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - //boundary still requires carriage return - ByteBuffer data = BufferUtils.toBuffer("--BOUNDARY\n" + - "name: value\n" + - "\n" + - "Now is the time for all good men to come to the aid of the party.\n" + - "How now brown cow.\n" + - "The quick brown fox jumped over the lazy dog.\n" + - "\r\n" + - "--BOUNDARY"); - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertEquals(0, data.remaining()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Now is the time for all good men to come to the aid of the party.\n" + - "How now brown cow.\n" + - "The quick brown fox jumped over the lazy dog.\n", "<>"))); - } - - @Test - public void testBinaryPart() { - byte[] random = new byte[8192]; - final ByteBuffer bytes = BufferUtils.allocate(random.length); - ThreadLocalRandom.current().nextBytes(random); - // Arrays.fill(random,(byte)'X'); - - TestHandler handler = new TestHandler() { - @Override - public boolean content(ByteBuffer buffer, boolean last) { - BufferUtils.append(bytes, buffer); - return last; - } - }; - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - String preamble = "Blah blah blah\r\n--BOUNDARY\r\n\r\n"; - String epilogue = "\r\n--BOUNDARY\r\nBlah blah blah!\r\n"; - - ByteBuffer data = BufferUtils.allocate(preamble.length() + random.length + epilogue.length()); - BufferUtils.append(data, BufferUtils.toBuffer(preamble)); - BufferUtils.append(data, ByteBuffer.wrap(random)); - BufferUtils.append(data, BufferUtils.toBuffer(epilogue)); - - parser.parse(data, true); - assertEquals(State.DELIMITER, parser.getState()); - assertEquals(19, data.remaining()); - assertArrayEquals(random, bytes.array()); - } - - @Test - public void testEpilogue() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("" + - "--BOUNDARY\r\n" + - "name: value\n" + - "\r\n" + - "Hello\r\n" + - "--BOUNDARY--" + - "epilogue here:" + - "\r\n" + - "--BOUNDARY--" + - "\r\n" + - "--BOUNDARY"); - - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Hello", "<>"))); - System.out.println(data.remaining()); - System.out.println(BufferUtils.toString(data)); - - parser.parse(data, true); - assertEquals(State.END, parser.getState()); - } - - @Test - public void testMultipleContent() { - TestHandler handler = new TestHandler(); - MultiPartParser parser = new MultiPartParser(handler, "BOUNDARY"); - - ByteBuffer data = BufferUtils.toBuffer("" + - "--BOUNDARY\r\n" + - "name: value\n" + - "\r\n" + - "Hello" + - "\r\n" + - "--BOUNDARY\r\n" + - "powerLevel: 9001\n" + - "\r\n" + - "secondary" + - "\r\n" + - "content" + - "\r\n--BOUNDARY--" + - "epilogue here"); - - /* Test First Content Section */ - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Hello", "<>"))); - - /* Test Second Content Section */ - parser.parse(data, false); - assertEquals(State.DELIMITER, parser.getState()); - assertTrue(handler.fields.containsAll(Arrays.asList("name: value", "<>", "powerLevel: 9001", "<>"))); - assertTrue(handler.content.containsAll(Arrays.asList("Hello", "<>", "secondary\r\ncontent", "<>"))); - System.out.println(data.remaining()); - System.out.println(BufferUtils.toString(data)); - - /* Test Progression to END State */ - parser.parse(data, true); - assertEquals(State.END, parser.getState()); - assertEquals(0, data.remaining()); - } - - @Test - public void testCrAsLineTermination() { - TestHandler handler = new TestHandler() { - @Override - public boolean messageComplete() { - return true; - } - - @Override - public boolean content(ByteBuffer buffer, boolean last) { - super.content(buffer, last); - return false; - } - }; - MultiPartParser parser = new MultiPartParser(handler, "AaB03x"); - - ByteBuffer data = BufferUtils.toBuffer( - "--AaB03x\r\n" + - "content-disposition: form-data; name=\"field1\"\r\n" + - "\r" + - "Joe Blow\r\n" + - "--AaB03x--\r\n"); - - BadMessageException x = assertThrows(BadMessageException.class, - () -> parser.parse(data, true), - "Invalid EOL"); - assertTrue(x.getMessage().contains("Bad EOL")); - } - - @Test - public void testBadHeaderNames() { - String[] bad = new String[] - { - "Foo\\Bar: value\r\n", - "Foo@Bar: value\r\n", - "Foo,Bar: value\r\n", - "Foo}Bar: value\r\n", - "Foo{Bar: value\r\n", - "Foo=Bar: value\r\n", - "Foo>Bar: value\r\n", - "FooContent of a.html.\n" + - "\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Field1: value1\n" + - "Field2: value2\n" + - "Field3: value3\n" + - "Field4: value4\n" + - "Field5: value5\n" + - "Field6: value6\n" + - "Field7: value7\n" + - "Field8: value8\n" + - "Field9: value\n" + - " 9\n" + - "\r\n" + - "-----------------------------9051914041544843365972754266\n" + - "Field1: value1\n" + - "\r\n" + - "But the amount of denudation which the strata have\n" + - "in many places suffered, independently of the rate\n" + - "of accumulation of the degraded matter, probably\n" + - "offers the best evidence of the lapse of time. I remember\n" + - "having been much struck with the evidence of\n" + - "denudation, when viewing volcanic islands, which\n" + - "have been worn by the waves and pared all round\n" + - "into perpendicular cliffs of one or two thousand feet\n" + - "in height; for the gentle slope of the lava-streams,\n" + - "due to their formerly liquid state, showed at a glance\n" + - "how far the hard, rocky beds had once extended into\n" + - "the open ocean.\n" + - "\r\n" + - "-----------------------------9051914041544843365972754266--" + - "===== ajlkfja;lkdj;lakjd;lkjf ==== epilogue here ==== kajflajdfl;kjafl;kjl;dkfja ====\n\r\n\r\r\r\n\n\n"); - - int length = data.remaining(); - for (int i = 0; i < length - 1; i++) { - //partition 0 to i - ByteBuffer dataSeg = data.slice(); - dataSeg.position(0); - dataSeg.limit(i); - assertFalse(parser.parse(dataSeg, false)); - - //partition i - dataSeg = data.slice(); - dataSeg.position(i); - dataSeg.limit(i + 1); - assertFalse(parser.parse(dataSeg, false)); - - //partition i to length - dataSeg = data.slice(); - dataSeg.position(i + 1); - dataSeg.limit(length); - assertTrue(parser.parse(dataSeg, true)); - - assertTrue(handler.fields.containsAll(Arrays.asList("Content-Disposition: form-data; name=\"text\"", "<>", - "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"", - "Content-Type: text/plain", "<>", - "Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"", - "Content-Type: text/html", "<>", - "Field1: value1", "Field2: value2", "Field3: value3", - "Field4: value4", "Field5: value5", "Field6: value6", - "Field7: value7", "Field8: value8", "Field9: value 9", "<>", - "Field1: value1", "<>"))); - - assertEquals("text default" + "<>" + - "Content of a.txt.\n" + "<>" + - "Content of a.html.\n" + "<>" + - "<>" + - "But the amount of denudation which the strata have\n" + - "in many places suffered, independently of the rate\n" + - "of accumulation of the degraded matter, probably\n" + - "offers the best evidence of the lapse of time. I remember\n" + - "having been much struck with the evidence of\n" + - "denudation, when viewing volcanic islands, which\n" + - "have been worn by the waves and pared all round\n" + - "into perpendicular cliffs of one or two thousand feet\n" + - "in height; for the gentle slope of the lava-streams,\n" + - "due to their formerly liquid state, showed at a glance\n" + - "how far the hard, rocky beds had once extended into\n" + - "the open ocean.\n" + "<>", handler.contentString()); - - handler.clear(); - parser.reset(); - } - } - - @Test - public void testGeneratedForm() { - TestHandler handler = new TestHandler() { - @Override - public boolean messageComplete() { - return true; - } - - @Override - public boolean content(ByteBuffer buffer, boolean last) { - super.content(buffer, last); - return false; - } - - @Override - public boolean headerComplete() { - return false; - } - }; - - MultiPartParser parser = new MultiPartParser(handler, "WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW"); - ByteBuffer data = BufferUtils.toBuffer("" + - "Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + - "\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\r\n" + - "Content-Disposition: form-data; name=\"part1\"\r\n" + - "\n" + - "wNfミxVam﾿t\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW\n" + - "Content-Disposition: form-data; name=\"part2\"\r\n" + - "\r\n" + - "&ᄈᄎ￙ᅱᅢO\r\n" + - "--WebKitFormBoundary7MA4YWf7OaKlSxkTrZu0gW--"); - - parser.parse(data, true); - assertEquals(State.END, parser.getState()); - assertEquals(2, handler.fields.size()); - } - - static class TestHandler implements MultiPartParser.Handler { - List fields = new ArrayList<>(); - List content = new ArrayList<>(); - - @Override - public void parsedField(String name, String value) { - fields.add(name + ": " + value); - } - - public String contentString() { - StringBuilder sb = new StringBuilder(); - for (String s : content) { - sb.append(s); - } - return sb.toString(); - } - - @Override - public boolean headerComplete() { - fields.add("<>"); - return false; - } - - @Override - public boolean content(ByteBuffer buffer, boolean last) { - if (BufferUtils.hasContent(buffer)) - content.add(BufferUtils.toString(buffer)); - if (last) - content.add("<>"); - return last; - } - - public void clear() { - fields.clear(); - content.clear(); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URIUtilsCanonicalPathTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URIUtilsCanonicalPathTest.java deleted file mode 100644 index dcc2c85c5..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URIUtilsCanonicalPathTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -public class URIUtilsCanonicalPathTest { - - public static Stream testParametersProvider() { - return data().stream().map(arr -> arguments(Arrays.asList(arr).toArray())); - } - - public static List data() { - String[][] canonical = { - // Basic examples (no changes expected) - {"/hello.html", "/hello.html"}, - {"/css/main.css", "/css/main.css"}, - {"/", "/"}, - {"", ""}, - {"/aaa/bbb/", "/aaa/bbb/"}, - {"/aaa/bbb", "/aaa/bbb"}, - {"aaa/bbb", "aaa/bbb"}, - {"aaa/", "aaa/"}, - {"aaa", "aaa"}, - {"a", "a"}, - {"a/", "a/"}, - - // Extra slashes - {"/aaa//bbb/", "/aaa//bbb/"}, - {"/aaa//bbb", "/aaa//bbb"}, - {"/aaa///bbb/", "/aaa///bbb/"}, - - // Path traversal with current references "./" - {"/aaa/./bbb/", "/aaa/bbb/"}, - {"/aaa/./bbb", "/aaa/bbb"}, - {"./bbb/", "bbb/"}, - {"./aaa/../bbb/", "bbb/"}, - {"/foo/.", "/foo/"}, - {"./", ""}, - {".", ""}, - {".//", "/"}, - {".///", "//"}, - {"/.", "/"}, - {"//.", "//"}, - {"///.", "///"}, - - // Path traversal directory (but not past root) - {"/aaa/../bbb/", "/bbb/"}, - {"/aaa/../bbb", "/bbb"}, - {"/aaa..bbb/", "/aaa..bbb/"}, - {"/aaa..bbb", "/aaa..bbb"}, - {"/aaa/..bbb/", "/aaa/..bbb/"}, - {"/aaa/..bbb", "/aaa/..bbb"}, - {"/aaa/./../bbb/", "/bbb/"}, - {"/aaa/./../bbb", "/bbb"}, - {"/aaa/bbb/ccc/../../ddd/", "/aaa/ddd/"}, - {"/aaa/bbb/ccc/../../ddd", "/aaa/ddd"}, - {"/foo/../bar//", "/bar//"}, - {"/ctx/../bar/../ctx/all/index.txt", "/ctx/all/index.txt"}, - {"/down/.././index.html", "/index.html"}, - - // Path traversal up past root - {"..", null}, - {"./..", null}, - {"aaa/../..", null}, - {"/foo/bar/../../..", null}, - {"/../foo", null}, - {"a/.", "a/"}, - {"a/..", ""}, - {"a/../..", null}, - {"/foo/../../bar", null}, - - // Query parameter specifics - {"/ctx/dir?/../index.html", "/ctx/index.html"}, - {"/get-files?file=/etc/passwd", "/get-files?file=/etc/passwd"}, - {"/get-files?file=../../../../../passwd", null}, - - // Known windows shell quirks - {"file.txt ", "file.txt "}, // with spaces - {"file.txt...", "file.txt..."}, // extra dots ignored by windows - // BREAKS Jenkins: {"file.txt\u0000", "file.txt\u0000"}, // null terminated is ignored by windows - {"file.txt\r", "file.txt\r"}, // CR terminated is ignored by windows - {"file.txt\n", "file.txt\n"}, // LF terminated is ignored by windows - {"file.txt\"\"\"\"", "file.txt\"\"\"\""}, // extra quotes ignored by windows - {"file.txt<<<>>><", "file.txt<<<>>><"}, // angle brackets at end of path ignored by windows - {"././././././file.txt", "file.txt"}, - - // Oddball requests that look like path traversal, but are not - {"/....", "/...."}, - {"/..../ctx/..../blah/logo.jpg", "/..../ctx/..../blah/logo.jpg"}, - - // paths with encoded segments should remain encoded - // canonicalPath() is not responsible for decoding characters - {"%2e%2e/", "%2e%2e/"}, - }; - return Arrays.asList(canonical); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void test(String input, String expect) { - assertEquals(expect, URIUtils.canonicalPath(input)); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URIUtilsTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URIUtilsTest.java deleted file mode 100644 index 212dfe694..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URIUtilsTest.java +++ /dev/null @@ -1,501 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - - -/** - * Util meta Tests. - */ -@SuppressWarnings("SpellCheckingInspection") -class URIUtilsTest { - - static Stream encodePathSource() { - return Stream.of( - Arguments.of("/foo%23+;,:=/b a r/?info ", "/foo%2523+%3B,:=/b%20a%20r/%3Finfo%20"), - Arguments.of("/context/'list'/\"me\"/;", - "/context/%27list%27/%22me%22/%3B%3Cscript%3Ewindow.alert(%27xss%27)%3B%3C/script%3E"), - Arguments.of("test\u00f6?\u00f6:\u00df", "test%C3%B6%3F%C3%B6:%C3%9F"), - Arguments.of("test?\u00f6?\u00f6:\u00df", "test%3F%C3%B6%3F%C3%B6:%C3%9F") - ); - } - - @ParameterizedTest(name = "[{index}] {0}") - @MethodSource("encodePathSource") - void testEncodePath(String rawPath, String expectedEncoded) { - // test basic encode/decode - StringBuilder buf = new StringBuilder(); - buf.setLength(0); - URIUtils.encodePath(buf, rawPath); - assertEquals(expectedEncoded, buf.toString()); - } - - @Test - void testEncodeString() { - StringBuilder buf = new StringBuilder(); - buf.setLength(0); - URIUtils.encodeString(buf, "foo%23;,:=b a r", ";,= "); - assertEquals("foo%2523%3b%2c:%3db%20a%20r", buf.toString()); - } - - static Stream decodePathSource() { - List arguments = new ArrayList<>(); - arguments.add(Arguments.of("/foo/bar", "/foo/bar")); - - arguments.add(Arguments.of("/f%20o/b%20r", "/f o/b r")); - arguments.add(Arguments.of("fää%2523%3b%2c:%3db%20a%20r%3D", "f\u00e4\u00e4%23;,:=b a r=")); - arguments.add(Arguments.of("f%d8%a9%d8%a9%2523%3b%2c:%3db%20a%20r", "f\u0629\u0629%23;,:=b a r")); - - // path parameters should be ignored - arguments.add(Arguments.of("/foo;ignore/bar;ignore", "/foo/bar")); - arguments.add(Arguments.of("/f\u00e4\u00e4;ignore/bar;ignore", "/fää/bar")); - arguments.add(Arguments.of("/f%d8%a9%d8%a9%2523;ignore/bar;ignore", "/f\u0629\u0629%23/bar")); - arguments.add(Arguments.of("foo%2523%3b%2c:%3db%20a%20r;rubbish", "foo%23;,:=b a r")); - - // Test for null character (real world ugly test case) - byte[] oddBytes = {'/', 0x00, '/'}; - String odd = new String(oddBytes, StandardCharsets.ISO_8859_1); - arguments.add(Arguments.of("/%00/", odd)); - - // Deprecated Microsoft Percent-U encoding - arguments.add(Arguments.of("abc%u3040", "abc\u3040")); - - // Lenient decode - arguments.add(Arguments.of("abc%xyz", "abc%xyz")); // not a "%##" - arguments.add(Arguments.of("abc%", "abc%")); // percent at end of string - arguments.add(Arguments.of("abc%A", "abc%A")); // incomplete "%##" at end of string - arguments.add(Arguments.of("abc%uvwxyz", "abc%uvwxyz")); // not a valid "%u####" - arguments.add(Arguments.of("abc%uEFGHIJ", "abc%uEFGHIJ")); // not a valid "%u####" - arguments.add(Arguments.of("abc%uABC", "abc%uABC")); // incomplete "%u####" - arguments.add(Arguments.of("abc%uAB", "abc%uAB")); // incomplete "%u####" - arguments.add(Arguments.of("abc%uA", "abc%uA")); // incomplete "%u####" - arguments.add(Arguments.of("abc%u", "abc%u")); // incomplete "%u####" - - return arguments.stream(); - } - - @ParameterizedTest(name = "[{index}] {0}") - @MethodSource("decodePathSource") - void testDecodePath(String encodedPath, String expectedPath) { - String path = URIUtils.decodePath(encodedPath); - assertEquals(expectedPath, path); - } - - @Test - void testDecodePathSubstring() { - String path = URIUtils.decodePath("xx/foo/barxx", 2, 8); - assertEquals("/foo/bar", path); - - path = URIUtils.decodePath("xxx/foo/bar%2523%3b%2c:%3db%20a%20r%3Dxxx;rubbish", 3, 35); - assertEquals("/foo/bar%23;,:=b a r=", path); - } - - static Stream addEncodedPathsSource() { - return Stream.of( - Arguments.of(null, null, null), - Arguments.of(null, "", ""), - Arguments.of(null, "bbb", "bbb"), - Arguments.of(null, "/", "/"), - Arguments.of(null, "/bbb", "/bbb"), - - Arguments.of("", null, ""), - Arguments.of("", "", ""), - Arguments.of("", "bbb", "bbb"), - Arguments.of("", "/", "/"), - Arguments.of("", "/bbb", "/bbb"), - - Arguments.of("aaa", null, "aaa"), - Arguments.of("aaa", "", "aaa"), - Arguments.of("aaa", "bbb", "aaa/bbb"), - Arguments.of("aaa", "/", "aaa/"), - Arguments.of("aaa", "/bbb", "aaa/bbb"), - - Arguments.of("/", null, "/"), - Arguments.of("/", "", "/"), - Arguments.of("/", "bbb", "/bbb"), - Arguments.of("/", "/", "/"), - Arguments.of("/", "/bbb", "/bbb"), - - Arguments.of("aaa/", null, "aaa/"), - Arguments.of("aaa/", "", "aaa/"), - Arguments.of("aaa/", "bbb", "aaa/bbb"), - Arguments.of("aaa/", "/", "aaa/"), - Arguments.of("aaa/", "/bbb", "aaa/bbb"), - - Arguments.of(";JS", null, ";JS"), - Arguments.of(";JS", "", ";JS"), - Arguments.of(";JS", "bbb", "bbb;JS"), - Arguments.of(";JS", "/", "/;JS"), - Arguments.of(";JS", "/bbb", "/bbb;JS"), - - Arguments.of("aaa;JS", null, "aaa;JS"), - Arguments.of("aaa;JS", "", "aaa;JS"), - Arguments.of("aaa;JS", "bbb", "aaa/bbb;JS"), - Arguments.of("aaa;JS", "/", "aaa/;JS"), - Arguments.of("aaa;JS", "/bbb", "aaa/bbb;JS"), - - Arguments.of("aaa/;JS", null, "aaa/;JS"), - Arguments.of("aaa/;JS", "", "aaa/;JS"), - Arguments.of("aaa/;JS", "bbb", "aaa/bbb;JS"), - Arguments.of("aaa/;JS", "/", "aaa/;JS"), - Arguments.of("aaa/;JS", "/bbb", "aaa/bbb;JS"), - - Arguments.of("?A=1", null, "?A=1"), - Arguments.of("?A=1", "", "?A=1"), - Arguments.of("?A=1", "bbb", "bbb?A=1"), - Arguments.of("?A=1", "/", "/?A=1"), - Arguments.of("?A=1", "/bbb", "/bbb?A=1"), - - Arguments.of("aaa?A=1", null, "aaa?A=1"), - Arguments.of("aaa?A=1", "", "aaa?A=1"), - Arguments.of("aaa?A=1", "bbb", "aaa/bbb?A=1"), - Arguments.of("aaa?A=1", "/", "aaa/?A=1"), - Arguments.of("aaa?A=1", "/bbb", "aaa/bbb?A=1"), - - Arguments.of("aaa/?A=1", null, "aaa/?A=1"), - Arguments.of("aaa/?A=1", "", "aaa/?A=1"), - Arguments.of("aaa/?A=1", "bbb", "aaa/bbb?A=1"), - Arguments.of("aaa/?A=1", "/", "aaa/?A=1"), - Arguments.of("aaa/?A=1", "/bbb", "aaa/bbb?A=1"), - - Arguments.of(";JS?A=1", null, ";JS?A=1"), - Arguments.of(";JS?A=1", "", ";JS?A=1"), - Arguments.of(";JS?A=1", "bbb", "bbb;JS?A=1"), - Arguments.of(";JS?A=1", "/", "/;JS?A=1"), - Arguments.of(";JS?A=1", "/bbb", "/bbb;JS?A=1"), - - Arguments.of("aaa;JS?A=1", null, "aaa;JS?A=1"), - Arguments.of("aaa;JS?A=1", "", "aaa;JS?A=1"), - Arguments.of("aaa;JS?A=1", "bbb", "aaa/bbb;JS?A=1"), - Arguments.of("aaa;JS?A=1", "/", "aaa/;JS?A=1"), - Arguments.of("aaa;JS?A=1", "/bbb", "aaa/bbb;JS?A=1"), - - Arguments.of("aaa/;JS?A=1", null, "aaa/;JS?A=1"), - Arguments.of("aaa/;JS?A=1", "", "aaa/;JS?A=1"), - Arguments.of("aaa/;JS?A=1", "bbb", "aaa/bbb;JS?A=1"), - Arguments.of("aaa/;JS?A=1", "/", "aaa/;JS?A=1"), - Arguments.of("aaa/;JS?A=1", "/bbb", "aaa/bbb;JS?A=1") - ); - } - - @ParameterizedTest(name = "[{index}] {0}+{1}") - @MethodSource("addEncodedPathsSource") - void testAddEncodedPaths(String path1, String path2, String expected) { - String actual = URIUtils.addEncodedPaths(path1, path2); - assertEquals(expected, actual, String.format("%s+%s", path1, path2)); - } - - static Stream addDecodedPathsSource() { - return Stream.of( - Arguments.of(null, null, null), - Arguments.of(null, "", ""), - Arguments.of(null, "bbb", "bbb"), - Arguments.of(null, "/", "/"), - Arguments.of(null, "/bbb", "/bbb"), - - Arguments.of("", null, ""), - Arguments.of("", "", ""), - Arguments.of("", "bbb", "bbb"), - Arguments.of("", "/", "/"), - Arguments.of("", "/bbb", "/bbb"), - - Arguments.of("aaa", null, "aaa"), - Arguments.of("aaa", "", "aaa"), - Arguments.of("aaa", "bbb", "aaa/bbb"), - Arguments.of("aaa", "/", "aaa/"), - Arguments.of("aaa", "/bbb", "aaa/bbb"), - - Arguments.of("/", null, "/"), - Arguments.of("/", "", "/"), - Arguments.of("/", "bbb", "/bbb"), - Arguments.of("/", "/", "/"), - Arguments.of("/", "/bbb", "/bbb"), - - Arguments.of("aaa/", null, "aaa/"), - Arguments.of("aaa/", "", "aaa/"), - Arguments.of("aaa/", "bbb", "aaa/bbb"), - Arguments.of("aaa/", "/", "aaa/"), - Arguments.of("aaa/", "/bbb", "aaa/bbb"), - - Arguments.of(";JS", null, ";JS"), - Arguments.of(";JS", "", ";JS"), - Arguments.of(";JS", "bbb", ";JS/bbb"), - Arguments.of(";JS", "/", ";JS/"), - Arguments.of(";JS", "/bbb", ";JS/bbb"), - - Arguments.of("aaa;JS", null, "aaa;JS"), - Arguments.of("aaa;JS", "", "aaa;JS"), - Arguments.of("aaa;JS", "bbb", "aaa;JS/bbb"), - Arguments.of("aaa;JS", "/", "aaa;JS/"), - Arguments.of("aaa;JS", "/bbb", "aaa;JS/bbb"), - - Arguments.of("aaa/;JS", null, "aaa/;JS"), - Arguments.of("aaa/;JS", "", "aaa/;JS"), - Arguments.of("aaa/;JS", "bbb", "aaa/;JS/bbb"), - Arguments.of("aaa/;JS", "/", "aaa/;JS/"), - Arguments.of("aaa/;JS", "/bbb", "aaa/;JS/bbb"), - - Arguments.of("?A=1", null, "?A=1"), - Arguments.of("?A=1", "", "?A=1"), - Arguments.of("?A=1", "bbb", "?A=1/bbb"), - Arguments.of("?A=1", "/", "?A=1/"), - Arguments.of("?A=1", "/bbb", "?A=1/bbb"), - - Arguments.of("aaa?A=1", null, "aaa?A=1"), - Arguments.of("aaa?A=1", "", "aaa?A=1"), - Arguments.of("aaa?A=1", "bbb", "aaa?A=1/bbb"), - Arguments.of("aaa?A=1", "/", "aaa?A=1/"), - Arguments.of("aaa?A=1", "/bbb", "aaa?A=1/bbb"), - - Arguments.of("aaa/?A=1", null, "aaa/?A=1"), - Arguments.of("aaa/?A=1", "", "aaa/?A=1"), - Arguments.of("aaa/?A=1", "bbb", "aaa/?A=1/bbb"), - Arguments.of("aaa/?A=1", "/", "aaa/?A=1/"), - Arguments.of("aaa/?A=1", "/bbb", "aaa/?A=1/bbb") - ); - } - - @ParameterizedTest(name = "[{index}] {0}+{1}") - @MethodSource("addDecodedPathsSource") - void testAddDecodedPaths(String path1, String path2, String expected) { - String actual = URIUtils.addPaths(path1, path2); - assertEquals(expected, actual, String.format("%s+%s", path1, path2)); - } - - static Stream compactPathSource() { - return Stream.of( - Arguments.of("/foo/bar", "/foo/bar"), - Arguments.of("/foo/bar?a=b//c", "/foo/bar?a=b//c"), - - Arguments.of("//foo//bar", "/foo/bar"), - Arguments.of("//foo//bar?a=b//c", "/foo/bar?a=b//c"), - - Arguments.of("/foo///bar", "/foo/bar"), - Arguments.of("/foo///bar?a=b//c", "/foo/bar?a=b//c") - ); - } - - @ParameterizedTest(name = "[{index}] {0}") - @MethodSource("compactPathSource") - void testCompactPath(String path, String expected) { - String actual = URIUtils.compactPath(path); - assertEquals(expected, actual); - } - - static Stream parentPathSource() { - return Stream.of( - Arguments.of("/aaa/bbb/", "/aaa/"), - Arguments.of("/aaa/bbb", "/aaa/"), - Arguments.of("/aaa/", "/"), - Arguments.of("/aaa", "/"), - Arguments.of("/", null), - Arguments.of(null, null) - ); - } - - @ParameterizedTest(name = "[{index}] {0}") - @MethodSource("parentPathSource") - void testParentPath(String path, String expectedPath) { - String actual = URIUtils.parentPath(path); - assertEquals(expectedPath, actual, String.format("parent %s", path)); - } - - static Stream equalsIgnoreEncodingStringTrueSource() { - return Stream.of( - Arguments.of("http://example.com/foo/bar", "http://example.com/foo/bar"), - Arguments.of("/barry's", "/barry%27s"), - Arguments.of("/barry%27s", "/barry's"), - Arguments.of("/barry%27s", "/barry%27s"), - Arguments.of("/b rry's", "/b%20rry%27s"), - Arguments.of("/b rry%27s", "/b%20rry's"), - Arguments.of("/b rry%27s", "/b%20rry%27s"), - - Arguments.of("/foo%2fbar", "/foo%2fbar"), - Arguments.of("/foo%2fbar", "/foo%2Fbar"), - - // encoded vs not-encode ("%" symbol is encoded as "%25") - Arguments.of("/abc%25xyz", "/abc%xyz"), - Arguments.of("/abc%25xy", "/abc%xy"), - Arguments.of("/abc%25x", "/abc%x"), - Arguments.of("/zzz%25", "/zzz%") - ); - } - - @ParameterizedTest - @MethodSource("equalsIgnoreEncodingStringTrueSource") - void testEqualsIgnoreEncodingStringTrue(String uriA, String uriB) { - assertTrue(URIUtils.equalsIgnoreEncodings(uriA, uriB)); - } - - static Stream equalsIgnoreEncodingStringFalseSource() { - return Stream.of( - // case difference - Arguments.of("ABC", "abc"), - // Encoding difference ("'" is "%27") - Arguments.of("/barry's", "/barry%26s"), - // Never match on "%2f" differences - only intested in filename / directory name differences - // This could be a directory called "foo" with a file called "bar" on the left, and just a file "foo%2fbar" on the right - Arguments.of("/foo/bar", "/foo%2fbar"), - // not actually encoded - Arguments.of("/foo2fbar", "/foo/bar"), - // encoded vs not-encode ("%" symbol is encoded as "%25") - Arguments.of("/yyy%25zzz", "/aaa%xxx"), - Arguments.of("/zzz%25", "/aaa%") - ); - } - - @ParameterizedTest - @MethodSource("equalsIgnoreEncodingStringFalseSource") - void testEqualsIgnoreEncodingStringFalse(String uriA, String uriB) { - assertFalse(URIUtils.equalsIgnoreEncodings(uriA, uriB)); - } - - static Stream equalsIgnoreEncodingURITrueSource() { - return Stream.of( - Arguments.of( - URI.create("jar:file:/path/to/main.jar!/META-INF/versions/"), - URI.create("jar:file:/path/to/main.jar!/META-INF/%76ersions/") - ), - Arguments.of( - URI.create("JAR:FILE:/path/to/main.jar!/META-INF/versions/"), - URI.create("jar:file:/path/to/main.jar!/META-INF/versions/") - ) - ); - } - - @ParameterizedTest - @MethodSource("equalsIgnoreEncodingURITrueSource") - void testEqualsIgnoreEncodingURITrue(URI uriA, URI uriB) { - assertTrue(URIUtils.equalsIgnoreEncodings(uriA, uriB)); - } - - static Stream getJarSourceStringSource() { - return Stream.of( - Arguments.of("file:///tmp/", "file:///tmp/"), - Arguments.of("jar:file:///tmp/foo.jar", "file:///tmp/foo.jar"), - Arguments.of("jar:file:///tmp/foo.jar!/some/path", "file:///tmp/foo.jar") - ); - } - - @ParameterizedTest - @MethodSource("getJarSourceStringSource") - void testJarSourceString(String uri, String expectedJarUri) { - assertEquals(expectedJarUri, URIUtils.getJarSource(uri)); - } - - static Stream getJarSourceURISource() { - return Stream.of( - Arguments.of(URI.create("file:///tmp/"), URI.create("file:///tmp/")), - Arguments.of(URI.create("jar:file:///tmp/foo.jar"), URI.create("file:///tmp/foo.jar")), - Arguments.of(URI.create("jar:file:///tmp/foo.jar!/some/path"), URI.create("file:///tmp/foo.jar")) - ); - } - - @ParameterizedTest - @MethodSource("getJarSourceURISource") - void testJarSourceURI(URI uri, URI expectedJarUri) { - assertEquals(expectedJarUri, URIUtils.getJarSource(uri)); - } - - static Stream encodeSpacesSource() { - return Stream.of( - // null - Arguments.of(null, null), - - // no spaces - Arguments.of("abc", "abc"), - - // match - Arguments.of("a c", "a%20c"), - Arguments.of(" ", "%20%20%20"), - Arguments.of("a%20space", "a%20space") - ); - } - - @ParameterizedTest - @MethodSource("encodeSpacesSource") - void testEncodeSpaces(String raw, String expected) { - assertEquals(expected, URIUtils.encodeSpaces(raw)); - } - - static Stream encodeSpecific() { - return Stream.of( - // [raw, chars, expected] - - // null input - Arguments.of(null, null, null), - - // null chars - Arguments.of("abc", null, "abc"), - - // empty chars - Arguments.of("abc", "", "abc"), - - // no matches - Arguments.of("abc", ".;", "abc"), - Arguments.of("xyz", ".;", "xyz"), - Arguments.of(":::", ".;", ":::"), - - // matches - Arguments.of("a c", " ", "a%20c"), - Arguments.of("name=value", "=", "name%3Dvalue"), - Arguments.of("This has fewer then 10% hits.", ".%", "This has fewer then 10%25 hits%2E"), - - // partially encoded already - Arguments.of("a%20name=value%20pair", "=", "a%20name%3Dvalue%20pair"), - Arguments.of("a%20name=value%20pair", "=%", "a%2520name%3Dvalue%2520pair") - ); - } - - @ParameterizedTest - @MethodSource(value = "encodeSpecific") - void testEncodeSpecific(String raw, String chars, String expected) { - assertEquals(expected, URIUtils.encodeSpecific(raw, chars)); - } - - static Stream decodeSpecific() { - return Stream.of( - // [raw, chars, expected] - - // null input - Arguments.of(null, null, null), - - // null chars - Arguments.of("abc", null, "abc"), - - // empty chars - Arguments.of("abc", "", "abc"), - - // no matches - Arguments.of("abc", ".;", "abc"), - Arguments.of("xyz", ".;", "xyz"), - Arguments.of(":::", ".;", ":::"), - - // matches - Arguments.of("a%20c", " ", "a c"), - Arguments.of("name%3Dvalue", "=", "name=value"), - Arguments.of("This has fewer then 10%25 hits%2E", ".%", "This has fewer then 10% hits."), - - // partially decode - Arguments.of("a%20name%3Dvalue%20pair", "=", "a%20name=value%20pair"), - Arguments.of("a%2520name%3Dvalue%2520pair", "=%", "a%20name=value%20pair") - ); - } - - @ParameterizedTest - @MethodSource(value = "decodeSpecific") - void testDecodeSpecific(String raw, String chars, String expected) { - assertEquals(expected, URIUtils.decodeSpecific(raw, chars)); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URLEncodedTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URLEncodedTest.java deleted file mode 100644 index e26c5e2cb..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/URLEncodedTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.collection.map.MultiMap; -import com.fireflysource.common.object.TypeUtils; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Util meta Tests. - */ -class URLEncodedTest { - - private static final String __ISO_8859_1 = "iso-8859-1"; - private static final String __UTF8 = "utf-8"; - private static final String __UTF16 = "utf-16"; - - @Test - void testUrlEncoded() { - - UrlEncoded url_encoded = new UrlEncoded(); - assertEquals(0, url_encoded.size()); - - url_encoded.clear(); - url_encoded.decode(""); - assertEquals(0, url_encoded.size()); - - url_encoded.clear(); - url_encoded.decode("Name1=Value1"); - assertEquals(1, url_encoded.size()); - assertEquals("Name1=Value1", url_encoded.encode()); - assertEquals("Value1", url_encoded.getString("Name1")); - - url_encoded.clear(); - url_encoded.decode("Name2="); - assertEquals(1, url_encoded.size()); - assertEquals("Name2", url_encoded.encode()); - assertEquals("", url_encoded.getString("Name2")); - - url_encoded.clear(); - url_encoded.decode("Name3"); - assertEquals(1, url_encoded.size()); - assertEquals("Name3", url_encoded.encode()); - assertEquals("", url_encoded.getString("Name3")); - - url_encoded.clear(); - url_encoded.decode("Name4=V\u0629lue+4%21"); - assertEquals(1, url_encoded.size()); - assertEquals("Name4=V%D8%A9lue+4%21", url_encoded.encode()); - assertEquals("V\u0629lue 4!", url_encoded.getString("Name4")); - - url_encoded.clear(); - url_encoded.decode("Name4=Value%2B4%21"); - assertEquals(1, url_encoded.size()); - assertEquals("Name4=Value%2B4%21", url_encoded.encode()); - assertEquals("Value+4!", url_encoded.getString("Name4")); - - url_encoded.clear(); - url_encoded.decode("Name4=Value+4%21%20%214"); - assertEquals(1, url_encoded.size()); - assertEquals("Name4=Value+4%21+%214", url_encoded.encode()); - assertEquals("Value 4! !4", url_encoded.getString("Name4")); - - - url_encoded.clear(); - url_encoded.decode("Name5=aaa&Name6=bbb"); - assertEquals(2, url_encoded.size()); - assertTrue(url_encoded.encode().equals("Name5=aaa&Name6=bbb") || - url_encoded.encode().equals("Name6=bbb&Name5=aaa") - ); - assertEquals("aaa", url_encoded.getString("Name5")); - assertEquals("bbb", url_encoded.getString("Name6")); - - url_encoded.clear(); - url_encoded.decode("Name7=aaa&Name7=b%2Cb&Name7=ccc"); - assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", url_encoded.encode()); - assertEquals("aaa,b,b,ccc", url_encoded.getString("Name7")); - assertEquals("aaa", url_encoded.getValues("Name7").get(0)); - assertEquals("b,b", url_encoded.getValues("Name7").get(1)); - assertEquals("ccc", url_encoded.getValues("Name7").get(2)); - - url_encoded.clear(); - url_encoded.decode("Name8=xx%2C++yy++%2Czz"); - assertEquals(1, url_encoded.size()); - assertEquals("Name8=xx%2C++yy++%2Czz", url_encoded.encode()); - assertEquals("xx, yy ,zz", url_encoded.getString("Name8")); - } - - @Test - void testUrlEncodedStream() - throws Exception { - @SuppressWarnings("InjectedReferences") String[][] charsets = new String[][] - { - {__UTF8, null, "%30"}, - {__ISO_8859_1, __ISO_8859_1, "%30"}, - {__UTF8, __UTF8, "%30"}, - {__UTF16, __UTF16, "%00%30"}, - }; - - // Note: "%30" -> decode -> "0" - - for (int i = 0; i < charsets.length; i++) { - ByteArrayInputStream in = new ByteArrayInputStream(("name\n=value+" + charsets[i][2] + "&name1=&name2&n\u00e3me3=value+3").getBytes(charsets[i][0])); - MultiMap m = new MultiMap<>(); - UrlEncoded.decodeTo(in, m, charsets[i][1] == null ? null : Charset.forName(charsets[i][1]), -1, -1); - assertEquals(4, m.size()); - assertEquals("value 0", m.getString("name\n")); - assertEquals("", m.getString("name1")); - assertEquals("", m.getString("name2")); - assertEquals("value 3", m.getString("n\u00e3me3")); - } - - - if (Charset.isSupported("Shift_JIS")) { - ByteArrayInputStream in2 = new ByteArrayInputStream("name=%83e%83X%83g".getBytes(StandardCharsets.ISO_8859_1)); - MultiMap m2 = new MultiMap<>(); - UrlEncoded.decodeTo(in2, m2, Charset.forName("Shift_JIS"), -1, -1); - assertEquals(1, m2.size()); - assertEquals("\u30c6\u30b9\u30c8", m2.getString("name")); - } else - assertTrue(true); - - } - - @Test - void testUtf8() - throws Exception { - UrlEncoded url_encoded = new UrlEncoded(); - assertEquals(0, url_encoded.size()); - - url_encoded.clear(); - url_encoded.decode("text=%E0%B8%9F%E0%B8%AB%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%81%E0%B8%9F%E0%B8%A7%E0%B8%AB%E0%B8%AA%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%AB%E0%B8%9F%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%AA%E0%B8%B2%E0%B8%9F%E0%B8%81%E0%B8%AB%E0%B8%A3%E0%B8%94%E0%B9%89%E0%B8%9F%E0%B8%AB%E0%B8%99%E0%B8%81%E0%B8%A3%E0%B8%94%E0%B8%B5&Action=Submit"); - - String hex = "E0B89FE0B8ABE0B881E0B8A7E0B894E0B8B2E0B988E0B881E0B89FE0B8A7E0B8ABE0B8AAE0B894E0B8B2E0B988E0B8ABE0B89FE0B881E0B8A7E0B894E0B8AAE0B8B2E0B89FE0B881E0B8ABE0B8A3E0B894E0B989E0B89FE0B8ABE0B899E0B881E0B8A3E0B894E0B8B5"; - String expected = new String(TypeUtils.fromHexString(hex), StandardCharsets.UTF_8); - assertEquals(expected, url_encoded.getString("text")); - } - - @Test - void testUtf8_MultiByteCodePoint() { - String input = "text=test%C3%A4"; - UrlEncoded url_encoded = new UrlEncoded(); - url_encoded.decode(input); - - // http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex - // Should be "testä" - // "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS - - String expected = "test\u00e4"; - assertEquals(expected, url_encoded.getString("text")); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/UrlEncodedUtf8Test.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/UrlEncodedUtf8Test.java deleted file mode 100644 index 50696e1b1..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/codec/UrlEncodedUtf8Test.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.fireflysource.net.http.common.codec; - -import com.fireflysource.common.collection.map.MultiMap; -import com.fireflysource.common.string.Utf8Appendable; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -class UrlEncodedUtf8Test { - - static void fromString(String test, String s, String field, String expected, boolean thrown) { - MultiMap values = new MultiMap<>(); - try { - UrlEncoded.decodeUtf8To(s, 0, s.length(), values); - if (thrown) - fail(); - assertEquals(expected, values.getString(field), test); - } catch (Exception e) { - if (!thrown) - throw e; - } - } - - static void fromInputStream(String test, byte[] b, String field, String expected, boolean thrown) throws Exception { - InputStream is = new ByteArrayInputStream(b); - MultiMap values = new MultiMap<>(); - try { - UrlEncoded.decodeUtf8To(is, values, 1000000, -1); - if (thrown) - fail(); - assertEquals(expected, values.getString(field), test); - } catch (Exception e) { - if (!thrown) - throw e; - } - } - - @Test - void testIncompleteSequestAtTheEnd() throws Exception { - byte[] bytes = {97, 98, 61, 99, -50}; - String test = new String(bytes, StandardCharsets.UTF_8); - String expected = "c" + Utf8Appendable.REPLACEMENT; - - fromString(test, test, "ab", expected, false); - fromInputStream(test, bytes, "ab", expected, false); - } - - @Test - void testIncompleteSequestAtTheEnd2() throws Exception { - byte[] bytes = {97, 98, 61, -50}; - String test = new String(bytes, StandardCharsets.UTF_8); - String expected = "" + Utf8Appendable.REPLACEMENT; - - fromString(test, test, "ab", expected, false); - fromInputStream(test, bytes, "ab", expected, false); - - } - - @Test - void testIncompleteSequestInName() throws Exception { - byte[] bytes = {101, -50, 61, 102, 103, 38, 97, 98, 61, 99, 100}; - String test = new String(bytes, StandardCharsets.UTF_8); - String name = "e" + Utf8Appendable.REPLACEMENT; - String value = "fg"; - - fromString(test, test, name, value, false); - fromInputStream(test, bytes, name, value, false); - } - - @Test - void testIncompleteSequestInValue() throws Exception { - byte[] bytes = {101, 102, 61, 103, -50, 38, 97, 98, 61, 99, 100}; - String test = new String(bytes, StandardCharsets.UTF_8); - String name = "ef"; - String value = "g" + Utf8Appendable.REPLACEMENT; - - fromString(test, test, name, value, false); - fromInputStream(test, bytes, name, value, false); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HostPortTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HostPortTest.java deleted file mode 100644 index a76460320..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HostPortTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * @author Pengtao Qiu - */ -class HostPortTest { - private static Stream validAuthorityProvider() { - return Stream.of( - Arguments.of("", "", null), - Arguments.of(":80", "", "80"), - Arguments.of("host", "host", null), - Arguments.of("host:80", "host", "80"), - Arguments.of("10.10.10.1", "10.10.10.1", null), - Arguments.of("10.10.10.1:80", "10.10.10.1", "80"), - Arguments.of("[0::0::0::1]", "[0::0::0::1]", null), - Arguments.of("[0::0::0::1]:80", "[0::0::0::1]", "80") - ); - } - - private static Stream invalidAuthorityProvider() { - return Stream.of( - null, - "host:", - "127.0.0.1:", - "[0::0::0::0::1]:", - "host:xxx", - "127.0.0.1:xxx", - "[0::0::0::0::1]:xxx", - "host:-80", - "127.0.0.1:-80", - "[0::0::0::0::1]:-80") - .map(Arguments::of); - } - - @ParameterizedTest - @MethodSource("validAuthorityProvider") - void testValidAuthority(String authority, String expectedHost, Integer expectedPort) { - try { - HostPort hostPort = new HostPort(authority); - assertEquals(expectedHost, hostPort.getHost(), authority); - - if (expectedPort == null) - assertEquals(0, hostPort.getPort(), authority); - else - assertEquals(expectedPort, Integer.valueOf(hostPort.getPort()), authority); - } catch (Exception e) { - if (expectedHost != null) - e.printStackTrace(); - assertNull(authority, expectedHost); - } - } - - @ParameterizedTest - @MethodSource("invalidAuthorityProvider") - void testInvalidAuthority(String authority) { - assertThrows(IllegalArgumentException.class, () -> new HostPort(authority)); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpFieldsTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpFieldsTest.java deleted file mode 100644 index 48f303010..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpFieldsTest.java +++ /dev/null @@ -1,485 +0,0 @@ -package com.fireflysource.net.http.common.model; - - -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.NoSuchElementException; - -import static org.junit.jupiter.api.Assertions.*; - - -class HttpFieldsTest { - - @Test - void testPut() { - HttpFields header = new HttpFields(); - - header.put("name0", "value:0"); - header.put("name1", "value1"); - - assertEquals(2, header.size()); - assertEquals("value:0", header.get("name0")); - assertEquals("value1", header.get("name1")); - assertNull(header.get("name2")); - - int matches = 0; - Enumeration e = header.getFieldNames(); - while (e.hasMoreElements()) { - Object o = e.nextElement(); - if ("name0".equals(o)) - matches++; - if ("name1".equals(o)) - matches++; - } - assertEquals(2, matches); - - e = header.getValues("name0"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value:0"); - assertFalse(e.hasMoreElements()); - } - - @Test - void testGet() { - HttpFields header = new HttpFields(); - - header.put("name0", "value0"); - header.put("name1", "value1"); - - assertEquals("value0", header.get("name0")); - assertEquals("value0", header.get("Name0")); - assertEquals("value1", header.get("name1")); - assertEquals("value1", header.get("Name1")); - assertNull(header.get("Name2")); - - assertEquals("value0", header.getField("name0").getValue()); - assertEquals("value0", header.getField("Name0").getValue()); - assertEquals("value1", header.getField("name1").getValue()); - assertEquals("value1", header.getField("Name1").getValue()); - assertNull(header.getField("Name2")); - - assertEquals("value0", header.getField(0).getValue()); - assertEquals("value1", header.getField(1).getValue()); - try { - header.getField(2); - fail(); - } catch (NoSuchElementException ignored) { - } - } - - @Test - void testGetKnown() { - HttpFields header = new HttpFields(); - - header.put("Connection", "value0"); - header.put(HttpHeader.ACCEPT, "value1"); - - assertEquals("value0", header.get(HttpHeader.CONNECTION)); - assertEquals("value1", header.get(HttpHeader.ACCEPT)); - - assertEquals("value0", header.getField(HttpHeader.CONNECTION).getValue()); - assertEquals("value1", header.getField(HttpHeader.ACCEPT).getValue()); - - assertNull(header.getField(HttpHeader.AGE)); - assertNull(header.get(HttpHeader.AGE)); - } - - @Test - void testRePut() { - HttpFields header = new HttpFields(); - - header.put("name0", "value0"); - header.put("name1", "xxxxxx"); - header.put("name2", "value2"); - - assertEquals("value0", header.get("name0")); - assertEquals("xxxxxx", header.get("name1")); - assertEquals("value2", header.get("name2")); - - header.put("name1", "value1"); - - assertEquals("value0", header.get("name0")); - assertEquals("value1", header.get("name1")); - assertEquals("value2", header.get("name2")); - assertNull(header.get("name3")); - - int matches = 0; - Enumeration e = header.getFieldNames(); - while (e.hasMoreElements()) { - String o = e.nextElement(); - if ("name0".equals(o)) - matches++; - if ("name1".equals(o)) - matches++; - if ("name2".equals(o)) - matches++; - } - assertEquals(3, matches); - - e = header.getValues("name1"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value1"); - assertFalse(e.hasMoreElements()); - } - - @Test - void testRemovePut() { - HttpFields header = new HttpFields(1); - - header.put("name0", "value0"); - header.put("name1", "value1"); - header.put("name2", "value2"); - - assertEquals("value0", header.get("name0")); - assertEquals("value1", header.get("name1")); - assertEquals("value2", header.get("name2")); - - header.remove("name1"); - - assertEquals("value0", header.get("name0")); - assertNull(header.get("name1")); - assertEquals("value2", header.get("name2")); - assertNull(header.get("name3")); - - int matches = 0; - Enumeration e = header.getFieldNames(); - while (e.hasMoreElements()) { - Object o = e.nextElement(); - if ("name0".equals(o)) - matches++; - if ("name1".equals(o)) - matches++; - if ("name2".equals(o)) - matches++; - } - assertEquals(2, matches); - - e = header.getValues("name1"); - assertFalse(e.hasMoreElements()); - } - - @Test - void testAdd() { - HttpFields fields = new HttpFields(); - - fields.add("name0", "value0"); - fields.add("name1", "valueA"); - fields.add("name2", "value2"); - - assertEquals("value0", fields.get("name0")); - assertEquals("valueA", fields.get("name1")); - assertEquals("value2", fields.get("name2")); - - fields.add("name1", "valueB"); - - assertEquals("value0", fields.get("name0")); - assertEquals("valueA", fields.get("name1")); - assertEquals("value2", fields.get("name2")); - assertNull(fields.get("name3")); - - int matches = 0; - Enumeration e = fields.getFieldNames(); - while (e.hasMoreElements()) { - Object o = e.nextElement(); - if ("name0".equals(o)) - matches++; - if ("name1".equals(o)) - matches++; - if ("name2".equals(o)) - matches++; - } - assertEquals(3, matches); - - e = fields.getValues("name1"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "valueA"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "valueB"); - assertFalse(e.hasMoreElements()); - } - - @Test - void testGetValues() { - HttpFields fields = new HttpFields(); - - fields.put("name0", "value0A,value0B"); - fields.add("name0", "value0C,value0D"); - fields.put("name1", "value1A, \"value\t, 1B\" "); - fields.add("name1", "\"value1C\",\tvalue1D"); - - Enumeration e = fields.getValues("name0"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0A,value0B"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0C,value0D"); - assertFalse(e.hasMoreElements()); - -// e = fields.getValues("name0",","); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value0A"); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value0B"); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value0C"); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value0D"); -// assertEquals(false, e.hasMoreElements()); -// -// e = fields.getValues("name1",","); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value1A"); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value\t, 1B"); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value1C"); -// assertEquals(true, e.hasMoreElements()); -// assertEquals(e.nextElement(), "value1D"); -// assertEquals(false, e.hasMoreElements()); - } - - @Test - void testGetCSV() { - HttpFields fields = new HttpFields(); - - fields.put("name0", "value0A,value0B"); - fields.add("name0", "value0C,value0D"); - fields.put("name1", "value1A, \"value\t, 1B\" "); - fields.add("name1", "\"value1C\",\tvalue1D"); - - Enumeration e = fields.getValues("name0"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0A,value0B"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0C,value0D"); - assertFalse(e.hasMoreElements()); - - e = Collections.enumeration(fields.getCSV("name0", false)); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0A"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0B"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0C"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value0D"); - assertFalse(e.hasMoreElements()); - - e = Collections.enumeration(fields.getCSV("name1", false)); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value1A"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value\t, 1B"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value1C"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value1D"); - assertFalse(e.hasMoreElements()); - } - - @Test - void testAddQuotedCSV() { - HttpFields fields = new HttpFields(); - - fields.put("some", "value"); - fields.add("name", "\"zero\""); - fields.add("name", "one, \"1 + 1\""); - fields.put("other", "value"); - fields.add("name", "three"); - fields.add("name", "four, I V"); - - List list = fields.getCSV("name", false); - assertEquals("zero", HttpFields.valueParameters(list.get(0), null)); - assertEquals("one", HttpFields.valueParameters(list.get(1), null)); - assertEquals("1 + 1", HttpFields.valueParameters(list.get(2), null)); - assertEquals("three", HttpFields.valueParameters(list.get(3), null)); - assertEquals("four", HttpFields.valueParameters(list.get(4), null)); - assertEquals("I V", HttpFields.valueParameters(list.get(5), null)); - - fields.addCSV("name", "six"); - list = fields.getCSV("name", false); - assertEquals("zero", HttpFields.valueParameters(list.get(0), null)); - assertEquals("one", HttpFields.valueParameters(list.get(1), null)); - assertEquals("1 + 1", HttpFields.valueParameters(list.get(2), null)); - assertEquals("three", HttpFields.valueParameters(list.get(3), null)); - assertEquals("four", HttpFields.valueParameters(list.get(4), null)); - assertEquals("I V", HttpFields.valueParameters(list.get(5), null)); - assertEquals("six", HttpFields.valueParameters(list.get(6), null)); - - fields.addCSV("name", "1 + 1", "7", "zero"); - list = fields.getCSV("name", false); - assertEquals("zero", HttpFields.valueParameters(list.get(0), null)); - assertEquals("one", HttpFields.valueParameters(list.get(1), null)); - assertEquals("1 + 1", HttpFields.valueParameters(list.get(2), null)); - assertEquals("three", HttpFields.valueParameters(list.get(3), null)); - assertEquals("four", HttpFields.valueParameters(list.get(4), null)); - assertEquals("I V", HttpFields.valueParameters(list.get(5), null)); - assertEquals("six", HttpFields.valueParameters(list.get(6), null)); - assertEquals("7", HttpFields.valueParameters(list.get(7), null)); - } - - @Test - void testGetQualityCSV() { - HttpFields fields = new HttpFields(); - - fields.put("some", "value"); - fields.add("name", "zero;q=0.9,four;q=0.1"); - fields.put("other", "value"); - fields.add("name", "nothing;q=0"); - fields.add("name", "one;q=0.4"); - fields.add("name", "three;x=y;q=0.2;a=b,two;q=0.3"); - - List list = fields.getQualityCSV("name"); - assertEquals("zero", HttpFields.valueParameters(list.get(0), null)); - assertEquals("one", HttpFields.valueParameters(list.get(1), null)); - assertEquals("two", HttpFields.valueParameters(list.get(2), null)); - assertEquals("three", HttpFields.valueParameters(list.get(3), null)); - assertEquals("four", HttpFields.valueParameters(list.get(4), null)); - } - - @Test - void testDateFields() { - HttpFields fields = new HttpFields(); - - fields.put("D0", "Wed, 31 Dec 1969 23:59:59 GMT"); - fields.put("D1", "Fri, 31 Dec 1999 23:59:59 GMT"); - fields.put("D2", "Friday, 31-Dec-99 23:59:59 GMT"); - fields.put("D3", "Fri Dec 31 23:59:59 1999"); - fields.put("D4", "Mon Jan 1 2000 00:00:01"); - fields.put("D5", "Tue Feb 29 2000 12:00:00"); - - long d1 = fields.getDateField("D1"); - long d0 = fields.getDateField("D0"); - long d2 = fields.getDateField("D2"); - long d3 = fields.getDateField("D3"); - long d4 = fields.getDateField("D4"); - long d5 = fields.getDateField("D5"); - assertTrue(d0 != -1); - assertTrue(d1 > 0); - assertTrue(d2 > 0); - assertEquals(d1, d2); - assertEquals(d2, d3); - assertEquals(d3 + 2000, d4); - assertEquals(951825600000L, d5); - - d1 = fields.getDateField("D1"); - d2 = fields.getDateField("D2"); - d3 = fields.getDateField("D3"); - d4 = fields.getDateField("D4"); - d5 = fields.getDateField("D5"); - assertTrue(d1 > 0); - assertTrue(d2 > 0); - assertEquals(d1, d2); - assertEquals(d2, d3); - assertEquals(d3 + 2000, d4); - assertEquals(951825600000L, d5); - - fields.putDateField("D2", d1); - assertEquals("Fri, 31 Dec 1999 23:59:59 GMT", fields.get("D2")); - } - - @Test - void testNegDateFields() { - HttpFields fields = new HttpFields(); - - fields.putDateField("Dzero", 0); - assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", fields.get("Dzero")); - - fields.putDateField("Dminus", -1); - assertEquals("Wed, 31 Dec 1969 23:59:59 GMT", fields.get("Dminus")); - - fields.putDateField("Dminus", -1000); - assertEquals("Wed, 31 Dec 1969 23:59:59 GMT", fields.get("Dminus")); - - fields.putDateField("Dancient", Long.MIN_VALUE); - assertEquals("Sun, 02 Dec 55 16:47:04 GMT", fields.get("Dancient")); - } - - @Test - void testLongFields() { - HttpFields header = new HttpFields(); - - header.put("I1", "42"); - header.put("I2", " 43 99"); - header.put("I3", "-44"); - header.put("I4", " - 45abc"); - header.put("N1", " - "); - header.put("N2", "xx"); - - long i1 = header.getLongField("I1"); - try { - header.getLongField("I2"); - fail(); - } catch (NumberFormatException e) { - assertTrue(true); - } - - long i3 = header.getLongField("I3"); - - try { - header.getLongField("I4"); - fail(); - } catch (NumberFormatException e) { - assertTrue(true); - } - - try { - header.getLongField("N1"); - fail(); - } catch (NumberFormatException e) { - assertTrue(true); - } - - try { - header.getLongField("N2"); - fail(); - } catch (NumberFormatException e) { - assertTrue(true); - } - - assertEquals(42, i1); - assertEquals(-44, i3); - - header.putLongField("I5", 46); - header.putLongField("I6", -47); - assertEquals("46", header.get("I5")); - assertEquals("-47", header.get("I6")); - } - - @Test - void testContains() { - HttpFields header = new HttpFields(); - - header.add("n0", ""); - header.add("n1", ","); - header.add("n2", ",,"); - header.add("N3", "abc"); - header.add("N4", "def"); - header.add("n5", "abc,def,hig"); - header.add("N6", "abc"); - header.add("n6", "def"); - header.add("N6", "hig"); - header.add("n7", "abc , def;q=0.9 , hig"); - header.add("n8", "abc , def;q=0 , hig"); - header.add(HttpHeader.ACCEPT, "abc , def;q=0 , hig"); - - for (int i = 0; i < 8; i++) { - assertTrue(header.containsKey("n" + i)); - assertTrue(header.containsKey("N" + i)); - assertFalse(header.contains("n" + i, "xyz")); - assertEquals(i >= 4, header.contains("n" + i, "def")); - } - - assertTrue(header.contains(new HttpField("N5", "def"))); - assertTrue(header.contains(new HttpField("accept", "abc"))); - assertTrue(header.contains(HttpHeader.ACCEPT, "abc")); - assertFalse(header.contains(new HttpField("N5", "xyz"))); - assertFalse(header.contains(new HttpField("N8", "def"))); - assertFalse(header.contains(HttpHeader.ACCEPT, "def")); - assertFalse(header.contains(HttpHeader.AGE, "abc")); - - assertFalse(header.containsKey("n11")); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpStatusCodeTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpStatusCodeTest.java deleted file mode 100644 index 71429076e..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpStatusCodeTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class HttpStatusCodeTest { - - @Test - void testInvalidGetCode() { - assertNull(HttpStatus.getCode(800), "Invalid code: 800"); - assertNull(HttpStatus.getCode(190), "Invalid code: 190"); - } - - - @Test - void testImATeapot() { - assertEquals("I'm a Teapot", HttpStatus.getMessage(418)); - assertEquals("Expectation Failed", HttpStatus.getMessage(417)); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpURITest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpURITest.java deleted file mode 100644 index 476902840..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/HttpURITest.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import com.fireflysource.common.collection.map.MultiMap; -import org.junit.jupiter.api.Test; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.*; - -class HttpURITest { - - @Test - void testInvalidAddress() { - assertInvalidURI("http://[ffff::1:8080/", "Invalid URL; no closing ']' -- should throw exception"); - assertInvalidURI("**", "only '*', not '**'"); - assertInvalidURI("*/", "only '*', not '*/'"); - } - - private void assertInvalidURI(String invalidURI, String message) { - HttpURI uri = new HttpURI(); - assertThrows(IllegalArgumentException.class, () -> uri.parse(invalidURI), message); - } - - @Test - void testParse() { - HttpURI uri = new HttpURI(); - - uri.parse("*"); - assertNull(uri.getHost()); - assertEquals("*", uri.getPath()); - - uri.parse("/foo/bar"); - assertNull(uri.getHost()); - assertEquals("/foo/bar", uri.getPath()); - - uri.parse("//foo/bar"); - assertEquals("foo", uri.getHost()); - assertEquals("/bar", uri.getPath()); - - uri.parse("http://foo/bar"); - assertEquals("foo", uri.getHost()); - assertEquals("/bar", uri.getPath()); - } - - @Test - void testParseRequestTarget() { - HttpURI uri = new HttpURI(); - - uri.parseRequestTarget("GET", "*"); - assertNull(uri.getHost()); - assertEquals("*", uri.getPath()); - - uri.parseRequestTarget("GET", "/foo/bar"); - assertNull(uri.getHost()); - assertEquals("/foo/bar", uri.getPath()); - - uri.parseRequestTarget("GET", "//foo/bar"); - assertNull(uri.getHost()); - assertEquals("//foo/bar", uri.getPath()); - - uri.parseRequestTarget("GET", "http://foo/bar"); - assertEquals("foo", uri.getHost()); - assertEquals("/bar", uri.getPath()); - } - - @Test - void testExtB() throws Exception { - for (String value : new String[]{"a", "abcdABCD", "\u00C0", "\u697C", "\uD869\uDED5", "\uD840\uDC08"}) { - HttpURI uri = new HttpURI("/path?value=" + URLEncoder.encode(value, "UTF-8")); - - MultiMap parameters = new MultiMap<>(); - uri.decodeQueryTo(parameters, StandardCharsets.UTF_8); - assertEquals(value, parameters.getString("value")); - } - } - - @Test - void testAt() { - HttpURI uri = new HttpURI("/@foo/bar"); - assertEquals("/@foo/bar", uri.getPath()); - } - - @Test - void testParams() { - HttpURI uri = new HttpURI("/foo/bar"); - assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - assertNull(uri.getParam()); - - uri = new HttpURI("/foo/bar;jsessionid=12345"); - assertEquals("/foo/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - assertEquals("jsessionid=12345", uri.getParam()); - - uri = new HttpURI("/foo;abc=123/bar;jsessionid=12345"); - assertEquals("/foo;abc=123/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - assertEquals("jsessionid=12345", uri.getParam()); - - uri = new HttpURI("/foo;abc=123/bar;jsessionid=12345?name=value"); - assertEquals("/foo;abc=123/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - assertEquals("jsessionid=12345", uri.getParam()); - - uri = new HttpURI("/foo;abc=123/bar;jsessionid=12345#target"); - assertEquals("/foo;abc=123/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - assertEquals("jsessionid=12345", uri.getParam()); - - uri = new HttpURI("/trainingCamp/poster.jpeg;jsessionid=12345?id=420"); - assertEquals("/trainingCamp/poster.jpeg;jsessionid=12345", uri.getPath()); - assertEquals("jsessionid=12345", uri.getParam()); - assertEquals("/trainingCamp/poster.jpeg", uri.getDecodedPath()); - } - - @Test - void testMutableURI() { - HttpURI uri = new HttpURI("/foo/bar"); - assertEquals("/foo/bar", uri.toString()); - assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - - uri.setScheme("http"); - assertEquals("http:/foo/bar", uri.toString()); - assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - - uri.setAuthority("host", 0); - assertEquals("http://host/foo/bar", uri.toString()); - assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - - uri.setAuthority("host", 8888); - assertEquals("http://host:8888/foo/bar", uri.toString()); - assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); - - uri.setPathQuery("/f%30%30;p0/bar;p1;p2"); - assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2", uri.toString()); - assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); - assertEquals(null, uri.getQuery()); - - uri.setPathQuery("/f%30%30;p0/bar;p1;p2?name=value"); - assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2?name=value", uri.toString()); - assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); - assertEquals("name=value", uri.getQuery()); - - uri.setQuery("other=123456"); - assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2?other=123456", uri.toString()); - assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); - assertEquals("other=123456", uri.getQuery()); - } - - @Test - void testSchemeAndOrAuthority() { - HttpURI uri = new HttpURI("/path/info"); - assertEquals("/path/info", uri.toString()); - - uri.setAuthority("host", 0); - assertEquals("//host/path/info", uri.toString()); - - uri.setAuthority("host", 8888); - assertEquals("//host:8888/path/info", uri.toString()); - - uri.setScheme("http"); - assertEquals("http://host:8888/path/info", uri.toString()); - - uri.setAuthority(null, 0); - assertEquals("http:/path/info", uri.toString()); - - } - - @Test - void testBasicAuthCredentials() { - HttpURI uri = new HttpURI("http://user:password@example.com:8888/blah"); - assertEquals("http://user:password@example.com:8888/blah", uri.toString()); - assertEquals("example.com:8888", uri.getAuthority()); - assertEquals("user:password", uri.getUser()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/MimeTypesTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/MimeTypesTest.java deleted file mode 100644 index 8bbb0f1c1..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/MimeTypesTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class MimeTypesTest { - - @Test - void testGetMimeByExtension_Gzip() { - assertMimeTypeByExtension("application/gzip", "test.gz"); - } - - @Test - void testGetMimeByExtension_Png() { - assertMimeTypeByExtension("image/png", "test.png"); - assertMimeTypeByExtension("image/png", "TEST.PNG"); - assertMimeTypeByExtension("image/png", "Test.Png"); - } - - @Test - void testGetMimeByExtension_Png_MultiDot() { - assertMimeTypeByExtension("image/png", "com.fireflysource.Logo.png"); - } - - @Test - void testGetMimeByExtension_Png_DeepPath() { - assertMimeTypeByExtension("image/png", "/com/fireflysource/Logo.png"); - } - - @Test - void testGetMimeByExtension_Text() { - assertMimeTypeByExtension("text/plain", "test.txt"); - assertMimeTypeByExtension("text/plain", "TEST.TXT"); - } - - @Test - void testGetMimeByExtension_NoExtension() { - MimeTypes mimetypes = new MimeTypes(); - String contentType = mimetypes.getMimeByExtension("README"); - assertNull(contentType); - } - - private void assertMimeTypeByExtension(String expectedMimeType, String filename) { - MimeTypes mimetypes = new MimeTypes(); - String contentType = mimetypes.getMimeByExtension(filename); - assertNotNull(contentType); - assertEquals(expectedMimeType, contentType); - } - - private void assertCharsetFromContentType(String contentType, String expectedCharset) { - assertEquals(expectedCharset, MimeTypes.getCharsetFromContentType(contentType)); - } - - @Test - void testCharsetFromContentType() { - assertCharsetFromContentType("foo/bar;charset=abc;some=else", "abc"); - assertCharsetFromContentType("foo/bar;charset=abc", "abc"); - assertCharsetFromContentType("foo/bar ; charset = abc", "abc"); - assertCharsetFromContentType("foo/bar ; charset = abc ; some=else", "abc"); - assertCharsetFromContentType("foo/bar;other=param;charset=abc;some=else", "abc"); - assertCharsetFromContentType("foo/bar;other=param;charset=abc", "abc"); - assertCharsetFromContentType("foo/bar other = param ; charset = abc", "abc"); - assertCharsetFromContentType("foo/bar other = param ; charset = abc ; some=else", "abc"); - assertCharsetFromContentType("foo/bar other = param ; charset = abc", "abc"); - assertCharsetFromContentType("foo/bar other = param ; charset = \"abc\" ; some=else", "abc"); - assertCharsetFromContentType("foo/bar", null); - assertCharsetFromContentType("foo/bar;charset=uTf8", "utf-8"); - assertCharsetFromContentType("foo/bar;other=\"charset=abc\";charset=uTf8", "utf-8"); - assertCharsetFromContentType("application/pdf; charset=UTF-8", "utf-8"); - assertCharsetFromContentType("application/pdf;; charset=UTF-8", "utf-8"); - assertCharsetFromContentType("application/pdf;;; charset=UTF-8", "utf-8"); - assertCharsetFromContentType("application/pdf;;;; charset=UTF-8", "utf-8"); - assertCharsetFromContentType("text/html;charset=utf-8", "utf-8"); - } - - @Test - void testContentTypeWithoutCharset() { - assertEquals("foo/bar;some=else", MimeTypes.getContentTypeWithoutCharset("foo/bar;charset=abc;some=else")); - assertEquals("foo/bar", MimeTypes.getContentTypeWithoutCharset("foo/bar;charset=abc")); - assertEquals("foo/bar", MimeTypes.getContentTypeWithoutCharset("foo/bar ; charset = abc")); - assertEquals("foo/bar;some=else", MimeTypes.getContentTypeWithoutCharset("foo/bar ; charset = abc ; some=else")); - assertEquals("foo/bar;other=param;some=else", MimeTypes.getContentTypeWithoutCharset("foo/bar;other=param;charset=abc;some=else")); - assertEquals("foo/bar;other=param", MimeTypes.getContentTypeWithoutCharset("foo/bar;other=param;charset=abc")); - assertEquals("foo/bar ; other = param", MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = abc")); - assertEquals("foo/bar ; other = param;some=else", MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = abc ; some=else")); - assertEquals("foo/bar ; other = param", MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = abc")); - assertEquals("foo/bar ; other = param;some=else", MimeTypes.getContentTypeWithoutCharset("foo/bar ; other = param ; charset = \"abc\" ; some=else")); - assertEquals("foo/bar", MimeTypes.getContentTypeWithoutCharset("foo/bar")); - assertEquals("foo/bar", MimeTypes.getContentTypeWithoutCharset("foo/bar;charset=uTf8")); - assertEquals("foo/bar;other=\"charset=abc\"", MimeTypes.getContentTypeWithoutCharset("foo/bar;other=\"charset=abc\";charset=uTf8")); - assertEquals("text/html", MimeTypes.getContentTypeWithoutCharset("text/html;charset=utf-8")); - } - - @Test - void testAcceptMimeTypes() { - List list = MimeTypes.parseAcceptMIMETypes("text/plain; q=0.9, text/html"); - assertEquals(2, list.size()); - assertEquals("text", list.get(0).getParentType()); - assertEquals("html", list.get(0).getChildType()); - assertEquals(1.0F, list.get(0).getQuality()); - assertEquals("text", list.get(1).getParentType()); - assertEquals("plain", list.get(1).getChildType()); - assertEquals(0.9F, list.get(1).getQuality()); - - list = MimeTypes.parseAcceptMIMETypes("text/plain, text/html"); - assertEquals(2, list.size()); - assertEquals("text", list.get(0).getParentType()); - assertEquals("plain", list.get(0).getChildType()); - assertEquals("text", list.get(1).getParentType()); - assertEquals("html", list.get(1).getChildType()); - - list = MimeTypes.parseAcceptMIMETypes("text/plain"); - assertEquals(1, list.size()); - assertEquals("text", list.get(0).getParentType()); - assertEquals("plain", list.get(0).getChildType()); - - list = MimeTypes.parseAcceptMIMETypes("*/*; q=0.8, text/plain; q=0.9, text/html, */json"); - assertEquals(4, list.size()); - - assertEquals("text", list.get(0).getParentType()); - assertEquals("html", list.get(0).getChildType()); - assertEquals(1.0F, list.get(0).getQuality()); - assertEquals(AcceptMIMEMatchType.EXACT, list.get(0).getMatchType()); - - assertEquals("*", list.get(1).getParentType()); - assertEquals("json", list.get(1).getChildType()); - assertEquals(1.0F, list.get(1).getQuality()); - assertEquals(AcceptMIMEMatchType.CHILD, list.get(1).getMatchType()); - - assertEquals("text", list.get(2).getParentType()); - assertEquals("plain", list.get(2).getChildType()); - assertEquals(0.9F, list.get(2).getQuality()); - assertEquals(AcceptMIMEMatchType.EXACT, list.get(2).getMatchType()); - - assertEquals("*", list.get(3).getParentType()); - assertEquals("*", list.get(3).getChildType()); - assertEquals(0.8F, list.get(3).getQuality()); - assertEquals(AcceptMIMEMatchType.ALL, list.get(3).getMatchType()); - - - } - - @Test - @DisplayName("should skip the non quality field") - void testSkipNonQualityField() { - List list = MimeTypes.parseAcceptMIMETypes("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"); - assertEquals("application", list.get(5).getParentType()); - assertEquals("signed-exchange", list.get(5).getChildType()); - assertEquals(0.9F, list.get(5).getQuality()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/QuotedCSVTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/QuotedCSVTest.java deleted file mode 100644 index 7f65dabeb..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/QuotedCSVTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -class QuotedCSVTest { - - @SafeVarargs - static void assertContains(List src, T... dest) { - Arrays.stream(dest).forEach(e -> assertTrue(src.contains(e))); - } - - @Test - void testOWS() { - QuotedCSV values = new QuotedCSV(); - values.addValue(" value 0.5 ; pqy = vwz ; q =0.5 , value 1.0 , other ; param "); - assertContains(values.getValues(), "value 0.5;pqy=vwz;q=0.5", "value 1.0", "other;param"); - } - - @Test - void testEmpty() { - QuotedCSV values = new QuotedCSV(); - values.addValue(",aaaa, , bbbb ,,cccc,"); - assertContains(values.getValues(), "aaaa", "bbbb", "cccc"); - } - - @Test - void testQuoted() { - QuotedCSV values = new QuotedCSV(); - values.addValue("A;p=\"v\",B,\"C, D\""); - assertContains(values.getValues(), "A;p=\"v\"", "B", "\"C, D\""); - } - - @Test - void testOpenQuote() { - QuotedCSV values = new QuotedCSV(); - values.addValue("value;p=\"v"); - assertContains(values.getValues(), "value;p=\"v"); - } - - @Test - void testQuotedNoQuotes() { - QuotedCSV values = new QuotedCSV(false); - values.addValue("A;p=\"v\",B,\"C, D\""); - assertContains(values.getValues(), "A;p=v", "B", "C, D"); - } - - @Test - void testOpenQuoteNoQuotes() { - QuotedCSV values = new QuotedCSV(false); - values.addValue("value;p=\"v"); - assertContains(values.getValues(), "value;p=v"); - } - - @Test - void testParamsOnly() { - QuotedCSV values = new QuotedCSV(false); - values.addValue("for=192.0.2.43, for=\"[2001:db8:cafe::17]\", for=unknown"); - assertContains(values.getValues(), "for=192.0.2.43", "for=[2001:db8:cafe::17]", "for=unknown"); - } - - @Test - void testMutation() { - QuotedCSV values = new QuotedCSV(false) { - - @Override - protected void parsedValue(StringBuilder buffer) { - if (buffer.toString().contains("DELETE")) { - String s = buffer.toString().replace("DELETE", ""); - buffer.setLength(0); - buffer.append(s); - } - if (buffer.toString().contains("APPEND")) { - String s = buffer.toString().replace("APPEND", "Append") + "!"; - buffer.setLength(0); - buffer.append(s); - } - } - - @Override - protected void parsedParam(StringBuilder buffer, int valueLength, int paramName, int paramValue) { - String name = paramValue > 0 ? buffer.substring(paramName, paramValue - 1) : buffer.substring(paramName); - if ("IGNORE".equals(name)) - buffer.setLength(paramName - 1); - } - - }; - - values.addValue("normal;param=val, testAPPENDandDELETEvalue ; n=v; IGNORE = this; x=y "); - - assertContains(values.getValues(), - "normal;param=val", - "testAppendandvalue!;n=v;x=y"); - } - - - @Test - void testUnQuote() { - assertEquals("", QuotedCSV.unquote("")); - assertEquals("", QuotedCSV.unquote("\"\"")); - assertEquals("foo", QuotedCSV.unquote("foo")); - assertEquals("foo", QuotedCSV.unquote("\"foo\"")); - assertEquals("foo", QuotedCSV.unquote("f\"o\"o")); - assertEquals("\"foo", QuotedCSV.unquote("\"\\\"foo\"")); - assertEquals("\\foo", QuotedCSV.unquote("\\foo")); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/QuotedQualityCSVTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/QuotedQualityCSVTest.java deleted file mode 100644 index ab3fcd253..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/QuotedQualityCSVTest.java +++ /dev/null @@ -1,316 +0,0 @@ -package com.fireflysource.net.http.common.model; - - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertTrue; - - -class QuotedQualityCSVTest { - - private static final String[] preferBrotli = {"br", "gzip"}; - private static final String[] preferGzip = {"gzip", "br"}; - private static final String[] noFormats = {}; - - @SafeVarargs - static void assertContains(List src, T... dest) { - Arrays.stream(dest).forEach(e -> assertTrue(src.contains(e))); - } - - @Test - void test7231_5_3_2_example1() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue(" audio/*; q=0.2, audio/basic"); - assertContains(values.getValues(), "audio/basic", "audio/*"); - } - - @Test - void test7231_5_3_2_example2() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("text/plain; q=0.5, text/html,"); - values.addValue("text/x-dvi; q=0.8, text/x-c"); - assertContains(values.getValues(), "text/html", "text/x-c", "text/x-dvi", "text/plain"); - } - - @Test - void test7231_5_3_2_example3() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("text/*, text/plain, text/plain;format=flowed, */*"); - - // Note this sort is only on quality and not the most specific type as per 5.3.2 - assertContains(values.getValues(), "text/*", "text/plain", "text/plain;format=flowed", "*/*"); - } - - @Test - void test7231_5_3_2_example3_most_specific() { - QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC); - values.addValue("text/*, text/plain, text/plain;format=flowed, */*"); - - assertContains(values.getValues(), "text/plain;format=flowed", "text/plain", "text/*", "*/*"); - } - - @Test - void test7231_5_3_2_example4() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("text/*;q=0.3, text/html;q=0.7, text/html;level=1,"); - values.addValue("text/html;level=2;q=0.4, */*;q=0.5"); - assertContains(values.getValues(), - "text/html;level=1", - "text/html", - "*/*", - "text/html;level=2", - "text/*" - ); - } - - @Test - void test7231_5_3_4_example1() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("compress, gzip"); - values.addValue(""); - values.addValue("*"); - values.addValue("compress;q=0.5, gzip;q=1.0"); - values.addValue("gzip;q=1.0, identity; q=0.5, *;q=0"); - - assertContains(values.getValues(), - "compress", - "gzip", - "*", - "gzip", - "gzip", - "compress", - "identity" - ); - } - - @Test - void testOWS() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue(" value 0.5 ; p = v ; q =0.5 , value 1.0 "); - assertContains(values.getValues(), - "value 1.0", - "value 0.5;p=v"); - } - - @Test - void testEmpty() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue(",aaaa, , bbbb ,,cccc,"); - assertContains(values.getValues(), - "aaaa", - "bbbb", - "cccc"); - } - - @Test - void testQuoted() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue(" value 0.5 ; p = \"v ; q = \\\"0.5\\\" , value 1.0 \" "); - assertContains(values.getValues(), - "value 0.5;p=\"v ; q = \\\"0.5\\\" , value 1.0 \""); - } - - @Test - void testOpenQuote() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("value;p=\"v"); - assertContains(values.getValues(), - "value;p=\"v"); - } - - /* ------------------------------------------------------------ */ - - @Test - void testQuotedQuality() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue(" value 0.5 ; p = v ; q = \"0.5\" , value 1.0 "); - assertContains(values.getValues(), - "value 1.0", - "value 0.5;p=v"); - } - - @Test - void testBadQuality() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("value0.5;p=v;q=0.5,value1.0,valueBad;q=X"); - assertContains(values.getValues(), - "value1.0", - "value0.5;p=v"); - } - - @Test - void testBad() { - QuotedQualityCSV values = new QuotedQualityCSV(); - - - // None of these should throw exceptions - values.addValue(null); - values.addValue(""); - - values.addValue(";"); - values.addValue("="); - values.addValue(","); - - values.addValue(";;"); - values.addValue(";="); - values.addValue(";,"); - values.addValue("=;"); - values.addValue("=="); - values.addValue("=,"); - values.addValue(",;"); - values.addValue(",="); - values.addValue(",,"); - - values.addValue(";;;"); - values.addValue(";;="); - values.addValue(";;,"); - values.addValue(";=;"); - values.addValue(";=="); - values.addValue(";=,"); - values.addValue(";,;"); - values.addValue(";,="); - values.addValue(";,,"); - - values.addValue("=;;"); - values.addValue("=;="); - values.addValue("=;,"); - values.addValue("==;"); - values.addValue("==="); - values.addValue("==,"); - values.addValue("=,;"); - values.addValue("=,="); - values.addValue("=,,"); - - values.addValue(",;;"); - values.addValue(",;="); - values.addValue(",;,"); - values.addValue(",=;"); - values.addValue(",=="); - values.addValue(",=,"); - values.addValue(",,;"); - values.addValue(",,="); - values.addValue(",,,"); - - values.addValue("x;=1"); - values.addValue("=1"); - values.addValue("q=x"); - values.addValue("q=0"); - values.addValue("q="); - values.addValue("q=,"); - values.addValue("q=;"); - - } - - @Test - void testFirefoxContentEncodingWithBrotliPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferBrotli); - values.addValue("gzip, deflate, br"); - assertContains(values.getValues(), "br", "gzip", "deflate"); - } - - @Test - void testFirefoxContentEncodingWithGzipPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferGzip); - values.addValue("gzip, deflate, br"); - assertContains(values.getValues(), "gzip", "br", "deflate"); - } - - @Test - void testFirefoxContentEncodingWithNoPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(noFormats); - values.addValue("gzip, deflate, br"); - assertContains(values.getValues(), "gzip", "deflate", "br"); - } - - @Test - void testChromeContentEncodingWithBrotliPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferBrotli); - values.addValue("gzip, deflate, sdch, br"); - assertContains(values.getValues(), "br", "gzip", "deflate", "sdch"); - } - - @Test - void testComplexEncodingWithGzipPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferGzip); - values.addValue("gzip;q=0.9, identity;q=0.1, *;q=0.01, deflate;q=0.9, sdch;q=0.7, br;q=0.9"); - assertContains(values.getValues(), "gzip", "br", "deflate", "sdch", "identity", "*"); - } - - @Test - void testComplexEncodingWithBrotliPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferBrotli); - values.addValue("gzip;q=0.9, identity;q=0.1, *;q=0, deflate;q=0.9, sdch;q=0.7, br;q=0.99"); - assertContains(values.getValues(), "br", "gzip", "deflate", "sdch", "identity"); - } - - @Test - void testStarEncodingWithGzipPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferGzip); - values.addValue("br, *"); - assertContains(values.getValues(), "*", "br"); - } - - @Test - void testStarEncodingWithBrotliPreference() { - QuotedQualityCSV values = new QuotedQualityCSV(preferBrotli); - values.addValue("gzip, *"); - assertContains(values.getValues(), "*", "gzip"); - } - - - @Test - void testSameQuality() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("one;q=0.5,two;q=0.5,three;q=0.5"); - assertContains(values.getValues(), "one", "two", "three"); - } - - @Test - void testNoQuality() { - QuotedQualityCSV values = new QuotedQualityCSV(); - values.addValue("one,two;,three;x=y"); - assertContains(values.getValues(), "one", "two", "three;x=y"); - } - - - @Test - void testQuality() { - List results = new ArrayList<>(); - - QuotedQualityCSV values = new QuotedQualityCSV() { - @Override - protected void parsedValue(StringBuilder buffer) { - results.add("parsedValue: " + buffer.toString()); - - super.parsedValue(buffer); - } - - @Override - protected void parsedParam(StringBuilder buffer, int valueLength, int paramName, int paramValue) { - String param = buffer.substring(paramName, buffer.length()); - results.add("parsedParam: " + param); - - super.parsedParam(buffer, valueLength, paramName, paramValue); - } - }; - - - // The provided string is not legal according to some RFCs ( not a token because of = and not a parameter because not preceded by ; ) - // The string is legal according to RFC7239 which allows for just parameters (called forwarded-pairs) - values.addValue("p=0.5,q=0.5"); - - - // The QuotedCSV implementation is lenient and adopts the later interpretation and thus sees q=0.5 and p=0.5 both as parameters - assertContains(results, "parsedValue: ", "parsedParam: p=0.5", - "parsedValue: ", "parsedParam: q=0.5"); - - - // However the QuotedQualityCSV only handles the q parameter and that is consumed from the parameter string. - assertContains(values.getValues(), "p=0.5", ""); - - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpField.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpField.java deleted file mode 100644 index 834bd0c2a..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpField.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.fireflysource.net.http.common.model; - - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - - -class TestHttpField { - - @Test - void testContainsSimple() { - HttpField field = new HttpField("name", "SomeValue"); - assertTrue(field.contains("somevalue")); - assertTrue(field.contains("sOmEvAlUe")); - assertTrue(field.contains("SomeValue")); - assertFalse(field.contains("other")); - assertFalse(field.contains("some")); - assertFalse(field.contains("Some")); - assertFalse(field.contains("value")); - assertFalse(field.contains("v")); - assertFalse(field.contains("")); - assertFalse(field.contains(null)); - - field = new HttpField(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"); - assertTrue(field.contains("Upgrade")); - assertTrue(field.contains("HTTP2-Settings")); - } - - @Test - void testCaseInsensitiveHashcode_KnownField() { - HttpField fieldFoo1 = new HttpField("Cookie", "foo"); - HttpField fieldFoo2 = new HttpField("cookie", "foo"); - - assertEquals(fieldFoo2.hashCode(), fieldFoo1.hashCode()); - } - - @Test - void testCaseInsensitiveHashcode_UnknownField() { - HttpField fieldFoo1 = new HttpField("X-Foo", "bar"); - HttpField fieldFoo2 = new HttpField("x-foo", "bar"); - - assertEquals(fieldFoo2.hashCode(), fieldFoo1.hashCode()); - } - - @Test - void testContainsList() { - HttpField field = new HttpField("name", ",aaa,Bbb,CCC, ddd , e e, \"\\\"f,f\\\"\", "); - assertTrue(field.contains("aaa")); - assertTrue(field.contains("bbb")); - assertTrue(field.contains("ccc")); - assertTrue(field.contains("Aaa")); - assertTrue(field.contains("Bbb")); - assertTrue(field.contains("Ccc")); - assertTrue(field.contains("AAA")); - assertTrue(field.contains("BBB")); - assertTrue(field.contains("CCC")); - assertTrue(field.contains("ddd")); - assertTrue(field.contains("e e")); - assertTrue(field.contains("\"f,f\"")); - assertFalse(field.contains("")); - assertFalse(field.contains("aa")); - assertFalse(field.contains("bb")); - assertFalse(field.contains("cc")); - assertFalse(field.contains(null)); - } - - @Test - void testQualityContainsList() { - HttpField field; - - field = new HttpField("name", "yes"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", ",yes,"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "other,yes,other"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "other, yes ,other"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "other, y s ,other"); - assertTrue(field.contains("y s")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "other, \"yes\" ,other"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "other, \"\\\"yes\\\"\" ,other"); - assertTrue(field.contains("\"yes\"")); - assertFalse(field.contains("no")); - - field = new HttpField("name", ";no,yes,;no"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "no;q=0,yes;q=1,no; q = 0"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "no;q=0.0000,yes;q=0.0001,no; q = 0.00000"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - field = new HttpField("name", "no;q=0.0000,Yes;Q=0.0001,no; Q = 0.00000"); - assertTrue(field.contains("yes")); - assertFalse(field.contains("no")); - - } - - @Test - void testValues() { - String[] values = new HttpField("name", "value").getValues(); - assertEquals(1, values.length); - assertEquals("value", values[0]); - - values = new HttpField("name", "a,b,c").getValues(); - assertEquals(3, values.length); - assertEquals("a", values[0]); - assertEquals("b", values[1]); - assertEquals("c", values[2]); - - values = new HttpField("name", "a,\"x,y,z\",c").getValues(); - assertEquals(3, values.length); - assertEquals("a", values[0]); - assertEquals("x,y,z", values[1]); - assertEquals("c", values[2]); - - values = new HttpField("name", "a,\"x,\\\"p,q\\\",z\",c").getValues(); - assertEquals(3, values.length); - assertEquals("a", values[0]); - assertEquals("x,\"p,q\",z", values[1]); - assertEquals("c", values[2]); - - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpMethod.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpMethod.java deleted file mode 100644 index e054659d3..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpMethod.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -/** - * @author Pengtao Qiu - */ -class TestHttpMethod { - - static Stream testParametersProvider() { - return Arrays.stream(HttpMethod.values()).map(m -> arguments(m, m.getValue())); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void test(HttpMethod method, String name) { - assertEquals(method, HttpMethod.from(name)); - assertTrue(method.is(name.toLowerCase())); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpScheme.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpScheme.java deleted file mode 100644 index 3dd7d2599..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpScheme.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -/** - * @author Pengtao Qiu - */ -class TestHttpScheme { - - static Stream testParametersProvider() { - return Arrays.stream(HttpScheme.values()).map(m -> arguments(m, m.getValue())); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void test(HttpScheme scheme, String name) { - assertEquals(scheme, HttpScheme.from(name)); - assertTrue(scheme.is(name.toLowerCase())); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpVersion.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpVersion.java deleted file mode 100644 index ea4c7504a..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/model/TestHttpVersion.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.fireflysource.net.http.common.model; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -/** - * @author Pengtao Qiu - */ -class TestHttpVersion { - - static Stream testParametersProvider() { - return Arrays.stream(HttpVersion.values()).map(m -> arguments(m, m.getValue())); - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - void test(HttpVersion version, String name) { - assertEquals(version, HttpVersion.from(name)); - assertTrue(version.is(name.toLowerCase())); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/decoder/HttpParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/decoder/HttpParserTest.java deleted file mode 100644 index 41b9a05d9..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/decoder/HttpParserTest.java +++ /dev/null @@ -1,2314 +0,0 @@ -package com.fireflysource.net.http.common.v1.decoder; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.exception.BadMessageException; -import com.fireflysource.net.http.common.model.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.fireflysource.net.http.common.model.HttpComplianceSection.NO_FIELD_FOLDING; -import static com.fireflysource.net.http.common.model.HttpHeader.EXPECT; -import static com.fireflysource.net.http.common.model.HttpHeaderValue.CONTINUE; -import static com.fireflysource.net.http.common.v1.decoder.HttpParser.State; -import static org.junit.jupiter.api.Assertions.*; - -@SuppressWarnings("deprecation") -class HttpParserTest { - static { - HttpCompliance.CUSTOM0.sections().remove(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME); - } - - private final List complianceViolation = new ArrayList<>(); - private String host; - private int port; - private String bad; - private String content; - private String methodOrVersion; - private String uriOrStatus; - private String versionOrReason; - private List fields = new ArrayList<>(); - private List trailers = new ArrayList<>(); - private String[] hdr; - private String[] val; - private int headers; - private boolean early; - private boolean headerCompleted; - private boolean messageCompleted; - - /** - * Parse until {@link State#END} state. - * If the parser is already in the END state, then it is {@link HttpParser#reset()} and re-parsed. - * - * @param parser The parser to test - * @param buffer the buffer to parse - * @throws IllegalStateException If the buffers have already been partially parsed. - */ - static void parseAll(HttpParser parser, ByteBuffer buffer) { - if (parser.isState(State.END)) - parser.reset(); - if (!parser.isState(State.START)) - throw new IllegalStateException("!START"); - - // continue parsing - int remaining = buffer.remaining(); - while (!parser.isState(State.END) && remaining > 0) { - int was_remaining = remaining; - parser.parseNext(buffer); - remaining = buffer.remaining(); - if (remaining == was_remaining) - break; - } - } - - @Test - void HttpMethodTest() { - assertNull(HttpMethod.lookAheadGet(BufferUtils.toBuffer("Wibble "))); - assertNull(HttpMethod.lookAheadGet(BufferUtils.toBuffer("GET"))); - assertNull(HttpMethod.lookAheadGet(BufferUtils.toBuffer("MO"))); - - assertEquals(HttpMethod.GET, HttpMethod.lookAheadGet(BufferUtils.toBuffer("GET "))); - assertEquals(HttpMethod.MOVE, HttpMethod.lookAheadGet(BufferUtils.toBuffer("MOVE "))); - - ByteBuffer b = BufferUtils.allocateDirect(128); - BufferUtils.append(b, BufferUtils.toBuffer("GET")); - assertNull(HttpMethod.lookAheadGet(b)); - - BufferUtils.append(b, BufferUtils.toBuffer(" ")); - assertEquals(HttpMethod.GET, HttpMethod.lookAheadGet(b)); - } - - @Test - void testLineParse_Mock_IP() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /mock/127.0.0.1 HTTP/1.1\r\n" + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("POST", methodOrVersion); - assertEquals("/mock/127.0.0.1", uriOrStatus); - assertEquals("HTTP/1.1", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testLineParse0() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /foo HTTP/1.0\r\n" + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("POST", methodOrVersion); - assertEquals("/foo", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testLineParse1_RFC2616() { - ByteBuffer buffer = BufferUtils.toBuffer("GET /999\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY); - parseAll(parser, buffer); - - assertNull(bad); - assertEquals("GET", methodOrVersion); - assertEquals("/999", uriOrStatus); - assertEquals("HTTP/0.9", versionOrReason); - assertEquals(-1, headers); - assertTrue(complianceViolation.contains(HttpComplianceSection.NO_HTTP_0_9)); - } - - @Test - void testLineParse1() { - ByteBuffer buffer = BufferUtils.toBuffer("GET /999\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("HTTP/0.9 not supported", bad); - assertTrue(complianceViolation.isEmpty()); - } - - @Test - void testLineParse2_RFC2616() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /222 \r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY); - parseAll(parser, buffer); - - assertNull(bad); - assertEquals("POST", methodOrVersion); - assertEquals("/222", uriOrStatus); - assertEquals("HTTP/0.9", versionOrReason); - assertEquals(-1, headers); - assertTrue(complianceViolation.contains(HttpComplianceSection.NO_HTTP_0_9)); - } - - @Test - void testLineParse2() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /222 \r\n"); - - versionOrReason = null; - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("HTTP/0.9 not supported", bad); - assertTrue(complianceViolation.isEmpty()); - } - - @Test - void testLineParse3() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /fo\u0690 HTTP/1.0\r\n" + "\r\n", StandardCharsets.UTF_8); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("POST", methodOrVersion); - assertEquals("/fo\u0690", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testLineParse4() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /foo?param=\u0690 HTTP/1.0\r\n" + "\r\n", StandardCharsets.UTF_8); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("POST", methodOrVersion); - assertEquals("/foo?param=\u0690", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testLongURLParse() { - ByteBuffer buffer = BufferUtils.toBuffer("POST /123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/ HTTP/1.0\r\n" + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("POST", methodOrVersion); - assertEquals("/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testAllowedLinePreamble() { - ByteBuffer buffer = BufferUtils.toBuffer("\r\n\r\nGET / HTTP/1.0\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testDisallowedLinePreamble() { - ByteBuffer buffer = BufferUtils.toBuffer("\r\n \r\nGET / HTTP/1.0\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("Illegal character SPACE=' '", bad); - } - - @Test - void testConnect() { - ByteBuffer buffer = BufferUtils.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\r\n" + "\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("CONNECT", methodOrVersion); - assertEquals("192.168.1.2:80", uriOrStatus); - assertEquals("HTTP/1.1", versionOrReason); - assertEquals(-1, headers); - } - - @Test - void testSimple() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Connection: close\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Connection", hdr[1]); - assertEquals("close", val[1]); - assertEquals(1, headers); - } - - @Test - void testFoldedField2616() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name: value\r\n" + - " extra\r\n" + - "Name2: \r\n" + - "\tvalue2\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY); - parseAll(parser, buffer); - - assertNull(bad); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals(2, headers); - assertEquals("Name", hdr[1]); - assertEquals("value extra", val[1]); - assertEquals("Name2", hdr[2]); - assertEquals("value2", val[2]); - Arrays.asList(NO_FIELD_FOLDING, NO_FIELD_FOLDING) - .forEach(e -> assertTrue(complianceViolation.contains(e))); - } - - @Test - void testFoldedField7230() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name: value\r\n" + - " extra\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230_LEGACY); - parseAll(parser, buffer); - - assertNotNull(bad); - assertTrue(bad.contains("Header Folding")); - assertTrue(complianceViolation.isEmpty()); - } - - @Test - void testWhiteSpaceInName() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "N ame: value\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230_LEGACY); - parseAll(parser, buffer); - - assertNotNull(bad); - assertTrue(bad.contains("Illegal character")); - } - - @Test - void testWhiteSpaceAfterName() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name : value\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230_LEGACY); - parseAll(parser, buffer); - - assertNotNull(bad); - assertTrue(bad.contains("Illegal character")); - } - - @Test - // TODO: Parameterize Test - void testWhiteSpaceBeforeRequest() { - HttpCompliance[] complianceArray = { - HttpCompliance.RFC7230, HttpCompliance.RFC2616 - }; - - String[][] whitespaces = { - {" ", "Illegal character SPACE"}, - {"\t", "Illegal character HTAB"}, - {"\n", null}, - {"\r", "Bad EOL"}, - {"\r\n", null}, - {"\r\n\r\n", null}, - {"\r\n \r\n", "Illegal character SPACE"}, - {"\r\n\t\r\n", "Illegal character HTAB"}, - {"\r\t\n", "Bad EOL"}, - {"\r\r\n", "Bad EOL"}, - {"\t\r\t\r\n", "Illegal character HTAB"}, - {" \t \r \t \n\n", "Illegal character SPACE"}, - {" \r \t \r\n\r\n\r\n", "Illegal character SPACE"} - }; - - - for (HttpCompliance compliance : complianceArray) { - for (int j = 0; j < whitespaces.length; j++) { - String request = - whitespaces[j][0] + - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Name: value" + j + "\r\n" + - "Connection: close\r\n" + - "\r\n"; - - ByteBuffer buffer = BufferUtils.toBuffer(request); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, 4096, compliance); - bad = null; - parseAll(parser, buffer); - - String test = "whitespace.[" + compliance + "].[" + j + "]"; - String expected = whitespaces[j][1]; - if (expected == null) - assertNull(bad, test); - else - assertTrue(bad.contains(expected), test); - } - } - } - - @Test - void testNoValue() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name0: \r\n" + - "Name1:\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Name0", hdr[1]); - assertEquals("", val[1]); - assertEquals("Name1", hdr[2]); - assertEquals("", val[2]); - assertEquals(2, headers); - } - - @Test - void testSpaceInNameCustom0() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name with space: value\r\n" + - "Other: value\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.CUSTOM0); - parseAll(parser, buffer); - - assertTrue(bad.contains("Illegal character")); - assertTrue(complianceViolation.contains(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME)); - } - - @Test - void testNoColonCustom0() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name \r\n" + - "Other: value\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.CUSTOM0); - parseAll(parser, buffer); - - assertTrue(bad.contains("Illegal character")); - assertTrue(complianceViolation.contains(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME)); - } - - @Test - void testTrailingSpacesInHeaderNameInCustom0Mode() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 204 No Content\r\n" + - "Access-Control-Allow-Headers : Origin\r\n" + - "Other\t : value\r\n" + - "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, -1, HttpCompliance.CUSTOM0); - parseAll(parser, buffer); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("204", uriOrStatus); - assertEquals("No Content", versionOrReason); - assertNull(content); - - assertEquals(1, headers); - System.out.println(Arrays.asList(hdr)); - System.out.println(Arrays.asList(val)); - assertEquals("Access-Control-Allow-Headers", hdr[0]); - assertEquals("Origin", val[0]); - assertEquals("Other", hdr[1]); - assertEquals("value", val[1]); - - Arrays.asList(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME, HttpComplianceSection.NO_WS_AFTER_FIELD_NAME) - .forEach(e -> assertTrue(complianceViolation.contains(e))); - } - - @Test - void testTrailingSpacesInHeaderNameNoCustom0() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 204 No Content\r\n" + - "Access-Control-Allow-Headers : Origin\r\n" + - "Other: value\r\n" + - "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("204", uriOrStatus); - assertEquals("No Content", versionOrReason); - assertTrue(bad.contains("Illegal character ")); - } - - @Test - void testNoColon7230() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Name\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC7230_LEGACY); - parseAll(parser, buffer); - assertTrue(bad.contains("Illegal character")); - assertTrue(complianceViolation.isEmpty()); - } - - @Test - void testHeaderParseDirect() { - ByteBuffer b0 = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Header1: value1\r\n" + - "Header2: value 2a \r\n" + - "Header3: 3\r\n" + - "Header4:value4\r\n" + - "Server5: notServer\r\n" + - "HostHeader: notHost\r\n" + - "Connection: close\r\n" + - "Accept-Encoding: gzip, deflated\r\n" + - "Accept: unknown\r\n" + - "\r\n"); - ByteBuffer buffer = BufferUtils.allocateDirect(b0.capacity()); - int pos = BufferUtils.flipToFill(buffer); - BufferUtils.put(b0, buffer); - BufferUtils.flipToFlush(buffer, pos); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Header1", hdr[1]); - assertEquals("value1", val[1]); - assertEquals("Header2", hdr[2]); - assertEquals("value 2a", val[2]); - assertEquals("Header3", hdr[3]); - assertEquals("3", val[3]); - assertEquals("Header4", hdr[4]); - assertEquals("value4", val[4]); - assertEquals("Server5", hdr[5]); - assertEquals("notServer", val[5]); - assertEquals("HostHeader", hdr[6]); - assertEquals("notHost", val[6]); - assertEquals("Connection", hdr[7]); - assertEquals("close", val[7]); - assertEquals("Accept-Encoding", hdr[8]); - assertEquals("gzip, deflated", val[8]); - assertEquals("Accept", hdr[9]); - assertEquals("unknown", val[9]); - assertEquals(9, headers); - } - - @Test - void testHeaderParseCRLF() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Header1: value1\r\n" + - "Header2: value 2a \r\n" + - "Header3: 3\r\n" + - "Header4:value4\r\n" + - "Server5: notServer\r\n" + - "HostHeader: notHost\r\n" + - "Connection: close\r\n" + - "Accept-Encoding: gzip, deflated\r\n" + - "Accept: unknown\r\n" + - "\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Header1", hdr[1]); - assertEquals("value1", val[1]); - assertEquals("Header2", hdr[2]); - assertEquals("value 2a", val[2]); - assertEquals("Header3", hdr[3]); - assertEquals("3", val[3]); - assertEquals("Header4", hdr[4]); - assertEquals("value4", val[4]); - assertEquals("Server5", hdr[5]); - assertEquals("notServer", val[5]); - assertEquals("HostHeader", hdr[6]); - assertEquals("notHost", val[6]); - assertEquals("Connection", hdr[7]); - assertEquals("close", val[7]); - assertEquals("Accept-Encoding", hdr[8]); - assertEquals("gzip, deflated", val[8]); - assertEquals("Accept", hdr[9]); - assertEquals("unknown", val[9]); - assertEquals(9, headers); - } - - @Test - void testHeaderParseLF() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\n" + - "Host: localhost\n" + - "Header1: value1\n" + - "Header2: value 2a value 2b \n" + - "Header3: 3\n" + - "Header4:value4\n" + - "Server5: notServer\n" + - "HostHeader: notHost\n" + - "Connection: close\n" + - "Accept-Encoding: gzip, deflated\n" + - "Accept: unknown\n" + - "\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Header1", hdr[1]); - assertEquals("value1", val[1]); - assertEquals("Header2", hdr[2]); - assertEquals("value 2a value 2b", val[2]); - assertEquals("Header3", hdr[3]); - assertEquals("3", val[3]); - assertEquals("Header4", hdr[4]); - assertEquals("value4", val[4]); - assertEquals("Server5", hdr[5]); - assertEquals("notServer", val[5]); - assertEquals("HostHeader", hdr[6]); - assertEquals("notHost", val[6]); - assertEquals("Connection", hdr[7]); - assertEquals("close", val[7]); - assertEquals("Accept-Encoding", hdr[8]); - assertEquals("gzip, deflated", val[8]); - assertEquals("Accept", hdr[9]); - assertEquals("unknown", val[9]); - assertEquals(9, headers); - } - - @Test - void testQuoted() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\n" + - "Name0: \"value0\"\t\n" + - "Name1: \"value\t1\"\n" + - "Name2: \"value\t2A\",\"value,2B\"\t\n" + - "\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Name0", hdr[0]); - assertEquals("\"value0\"", val[0]); - assertEquals("Name1", hdr[1]); - assertEquals("\"value\t1\"", val[1]); - assertEquals("Name2", hdr[2]); - assertEquals("\"value\t2A\",\"value,2B\"", val[2]); - assertEquals(2, headers); - } - - @Test - void testEncodedHeader() { - ByteBuffer buffer = BufferUtils.allocate(4096); - BufferUtils.flipToFill(buffer); - BufferUtils.put(BufferUtils.toBuffer("GET "), buffer); - buffer.put("/foo/\u0690/".getBytes(StandardCharsets.UTF_8)); - BufferUtils.put(BufferUtils.toBuffer(" HTTP/1.0\r\n"), buffer); - BufferUtils.put(BufferUtils.toBuffer("Header1: "), buffer); - buffer.put("\u00e6 \u00e6".getBytes(StandardCharsets.ISO_8859_1)); - BufferUtils.put(BufferUtils.toBuffer(" \r\nHeader2: "), buffer); - buffer.put((byte) -1); - BufferUtils.put(BufferUtils.toBuffer("\r\n\r\n"), buffer); - BufferUtils.flipToFlush(buffer, 0); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/foo/\u0690/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Header1", hdr[0]); - assertEquals("\u00e6 \u00e6", val[0]); - assertEquals("Header2", hdr[1]); - assertEquals("" + (char) 255, val[1]); - assertEquals(1, headers); - assertNull(bad); - } - - @Test - void testResponseBufferUpgradeFrom() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 101 Upgrade\r\n" + - "Connection: upgrade\r\n" + - "Content-Length: 0\r\n" + - "Sec-WebSocket-Accept: 4GnyoUP4Sc1JD+2pCbNYAhFYVVA\r\n" + - "\r\n" + - "FOOGRADE"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - System.out.println(buffer.remaining()); - - while (!parser.isState(State.END)) { - parser.parseNext(buffer); - } - System.out.println(buffer.remaining()); - - assertEquals("FOOGRADE", BufferUtils.toUTF8String(buffer)); - } - - @Test - void testBadMethodEncoding() { - ByteBuffer buffer = BufferUtils.toBuffer( - "G\u00e6T / HTTP/1.0\r\nHeader0: value0\r\n\n\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertNotNull(bad); - } - - @Test - void testBadVersionEncoding() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / H\u00e6P/1.0\r\nHeader0: value0\r\n\n\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertNotNull(bad); - } - - @Test - void testBadHeaderEncoding() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" - + "H\u00e6der0: value0\r\n" - + "\n\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertNotNull(bad); - } - - @Test - // TODO: Parameterize Test - void testBadHeaderNames() { - String[] bad = new String[] - { - "Foo\\Bar: value\r\n", - "Foo@Bar: value\r\n", - "Foo,Bar: value\r\n", - "Foo}Bar: value\r\n", - "Foo{Bar: value\r\n", - "Foo=Bar: value\r\n", - "Foo>Bar: value\r\n", - "Foo assertTrue(complianceViolation.contains(e))); - - } - - @Test - void testSplitHeaderParse() { - ByteBuffer buffer = BufferUtils.toBuffer( - "XXXXSPLIT / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - "Header1: value1\r\n" + - "Header2: value 2a \r\n" + - "Header3: 3\r\n" + - "Header4:value4\r\n" + - "Server5: notServer\r\n" + - "\r\nZZZZ"); - buffer.position(2); - buffer.limit(buffer.capacity() - 2); - buffer = buffer.slice(); - - for (int i = 0; i < buffer.capacity() - 4; i++) { - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - buffer.position(2); - buffer.limit(2 + i); - - if (!parser.parseNext(buffer)) { - // consumed all - assertEquals(0, buffer.remaining()); - - // parse the rest - buffer.limit(buffer.capacity() - 2); - parser.parseNext(buffer); - } - - assertEquals("SPLIT", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Header1", hdr[1]); - assertEquals("value1", val[1]); - assertEquals("Header2", hdr[2]); - assertEquals("value 2a", val[2]); - assertEquals("Header3", hdr[3]); - assertEquals("3", val[3]); - assertEquals("Header4", hdr[4]); - assertEquals("value4", val[4]); - assertEquals("Server5", hdr[5]); - assertEquals("notServer", val[5]); - assertEquals(5, headers); - } - } - - @Test - void testChunkParse() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - + "\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(1, headers); - assertEquals("Header1", hdr[0]); - assertEquals("value1", val[0]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testBadChunkParse() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked, identity\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - + "\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertTrue(bad.contains("Bad chunking")); - } - - @Test - void testChunkParseTrailer() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - + "Trailer: value\r\n" - + "\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(1, headers); - assertEquals("Header1", hdr[0]); - assertEquals("value1", val[0]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - assertEquals(1, trailers.size()); - HttpField trailer1 = trailers.get(0); - assertEquals("Trailer", trailer1.getName()); - assertEquals("value", trailer1.getValue()); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testChunkParseTrailers() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - + "Trailer: value\r\n" - + "Foo: bar\r\n" - + "\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(0, headers); - assertEquals("Transfer-Encoding", hdr[0]); - assertEquals("chunked", val[0]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - assertEquals(2, trailers.size()); - HttpField trailer1 = trailers.get(0); - assertEquals("Trailer", trailer1.getName()); - assertEquals("value", trailer1.getValue()); - HttpField trailer2 = trailers.get(1); - assertEquals("Foo", trailer2.getName()); - assertEquals("bar", trailer2.getValue()); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testChunkParseBadTrailer() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - + "Trailer: value"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(1, headers); - assertEquals("Header1", hdr[0]); - assertEquals("value1", val[0]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - - assertTrue(headerCompleted); - assertTrue(early); - assertFalse(messageCompleted); - } - - @Test - void testChunkParseNoTrailer() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(1, headers); - assertEquals("Header1", hdr[0]); - assertEquals("value1", val[0]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testStartEOF() { - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - - assertTrue(early); - assertNull(bad); - } - - @Test - void testEarlyEOF() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /uri HTTP/1.0\r\n" - + "Content-Length: 20\r\n" - + "\r\n" - + "0123456789"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.atEOF(); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/uri", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals("0123456789", content); - - assertTrue(early); - } - - @Test - void testChunkEarlyEOF() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /chunk HTTP/1.0\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n"); - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.atEOF(); - parseAll(parser, buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(1, headers); - assertEquals("Header1", hdr[0]); - assertEquals("value1", val[0]); - assertEquals("0123456789", content); - - assertTrue(early); - } - - @Test - void testMultiParse() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET /mp HTTP/1.0\r\n" - + "Connection: Keep-Alive\r\n" - + "Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - - + "\r\n" - - + "POST /foo HTTP/1.0\r\n" - + "Connection: Keep-Alive\r\n" - + "Header2: value2\r\n" - + "Content-Length: 0\r\n" - + "\r\n" - - + "PUT /doodle HTTP/1.0\r\n" - + "Connection: close\r\n" - + "Header3: value3\r\n" - + "Content-Length: 10\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("GET", methodOrVersion); - assertEquals("/mp", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(2, headers); - assertEquals("Header1", hdr[1]); - assertEquals("value1", val[1]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - - parser.reset(); - init(); - parser.parseNext(buffer); - assertEquals("POST", methodOrVersion); - assertEquals("/foo", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(2, headers); - assertEquals("Header2", hdr[1]); - assertEquals("value2", val[1]); - assertNull(content); - - parser.reset(); - init(); - parser.parseNext(buffer); - parser.atEOF(); - assertEquals("PUT", methodOrVersion); - assertEquals("/doodle", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(2, headers); - assertEquals("Header3", hdr[1]); - assertEquals("value3", val[1]); - assertEquals("0123456789", content); - } - - @Test - void testMultiParseEarlyEOF() { - ByteBuffer buffer0 = BufferUtils.toBuffer( - "GET /mp HTTP/1.0\r\n" - + "Connection: Keep-Alive\r\n"); - - ByteBuffer buffer1 = BufferUtils.toBuffer("Header1: value1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "a;\r\n" - + "0123456789\r\n" - + "1a\r\n" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" - + "0\r\n" - - + "\r\n" - - + "POST /foo HTTP/1.0\r\n" - + "Connection: Keep-Alive\r\n" - + "Header2: value2\r\n" - + "Content-Length: 0\r\n" - + "\r\n" - - + "PUT /doodle HTTP/1.0\r\n" - + "Connection: close\r\n" - + "Header3: value3\r\n" - + "Content-Length: 10\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer0); - parser.atEOF(); - parser.parseNext(buffer1); - assertEquals("GET", methodOrVersion); - assertEquals("/mp", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(2, headers); - assertEquals("Header1", hdr[1]); - assertEquals("value1", val[1]); - assertEquals("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", content); - - parser.reset(); - init(); - parser.parseNext(buffer1); - assertEquals("POST", methodOrVersion); - assertEquals("/foo", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(2, headers); - assertEquals("Header2", hdr[1]); - assertEquals("value2", val[1]); - assertNull(content); - - parser.reset(); - init(); - parser.parseNext(buffer1); - assertEquals("PUT", methodOrVersion); - assertEquals("/doodle", uriOrStatus); - assertEquals("HTTP/1.0", versionOrReason); - assertEquals(2, headers); - assertEquals("Header3", hdr[1]); - assertEquals("value3", val[1]); - assertEquals("0123456789", content); - } - - @Test - void testResponseParse0() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 200 Correct\r\n" - + "Content-Length: 10\r\n" - + "Content-Type: text/plain\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertEquals("Correct", versionOrReason); - assertEquals(10, content.length()); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponseParse1() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 304 Not-Modified\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("304", uriOrStatus); - assertEquals("Not-Modified", versionOrReason); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponseParse2() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 204 No-Content\r\n" - + "Header: value\r\n" - + "\r\n" - - + "HTTP/1.1 200 Correct\r\n" - + "Content-Length: 10\r\n" - + "Content-Type: text/plain\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("204", uriOrStatus); - assertEquals("No-Content", versionOrReason); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - - parser.reset(); - init(); - - parser.parseNext(buffer); - parser.atEOF(); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertEquals("Correct", versionOrReason); - assertEquals(content.length(), 10); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponseParse3() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 200\r\n" - + "Content-Length: 10\r\n" - + "Content-Type: text/plain\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertNull(versionOrReason); - assertEquals(content.length(), 10); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponseParse4() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 200 \r\n" - + "Content-Length: 10\r\n" - + "Content-Type: text/plain\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertNull(versionOrReason); - assertEquals(content.length(), 10); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponseEOFContent() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 200 \r\n" - + "Content-Type: text/plain\r\n" - + "\r\n" - + "0123456789\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.atEOF(); - parser.parseNext(buffer); - - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertNull(versionOrReason); - assertEquals(12, content.length()); - assertEquals("0123456789\r\n", content); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponse304WithContentLength() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 304 found\r\n" - + "Content-Length: 10\r\n" - + "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("304", uriOrStatus); - assertEquals("found", versionOrReason); - assertNull(content); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponse101WithTransferEncoding() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 101 switching protocols\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("101", uriOrStatus); - assertEquals("switching protocols", versionOrReason); - assertNull(content); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @Test - void testResponseReasonIso8859_1() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 302 déplacé temporairement\r\n" - + "Content-Length: 0\r\n" - + "\r\n", StandardCharsets.ISO_8859_1); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("302", uriOrStatus); - assertEquals("déplacé temporairement", versionOrReason); - } - - @Test - void testSeekEOF() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 200 OK\r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n" - + "\r\n" // extra CRLF ignored - + "HTTP/1.1 400 OK\r\n"); // extra data causes close ?? - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertEquals("OK", versionOrReason); - assertNull(content); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - - parser.close(); - parser.reset(); - parser.parseNext(buffer); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testNoURI() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET\r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("No URI", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testNoURI2() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET \r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("No URI", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testUnknownResponseVersion() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HPPT/7.7 200 OK\r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("Unknown Version", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - - } - - @Test - void testNoStatus() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1\r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("No Status", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testNoStatus2() { - ByteBuffer buffer = BufferUtils.toBuffer( - "HTTP/1.1 \r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("No Status", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testBadRequestVersion() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HPPT/7.7\r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("Unknown Version", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - - buffer = BufferUtils.toBuffer( - "GET / HTTP/1.01\r\n" - + "Content-Length: 0\r\n" - + "Connection: close\r\n" - + "\r\n"); - - handler = new Handler(); - parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertNull(methodOrVersion); - assertEquals("Unknown Version", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testBadCR() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" - + "Content-Length: 0\r" - + "Connection: close\r" - + "\r"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("Bad EOL", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - - buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r" - + "Content-Length: 0\r" - + "Connection: close\r" - + "\r"); - - handler = new Handler(); - parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("Bad EOL", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testBadContentLength0() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" - + "Content-Length: abc\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("GET", methodOrVersion); - assertEquals("Invalid Content-Length Value", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testBadContentLength1() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" - + "Content-Length: 9999999999999999999999999999999999999999999999\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("GET", methodOrVersion); - assertEquals("Invalid Content-Length Value", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testBadContentLength2() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.0\r\n" - + "Content-Length: 1.5\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("GET", methodOrVersion); - assertEquals("Invalid Content-Length Value", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testMultipleContentLengthWithLargerThenCorrectValue() { - ByteBuffer buffer = BufferUtils.toBuffer( - "POST / HTTP/1.1\r\n" - + "Content-Length: 2\r\n" - + "Content-Length: 1\r\n" - + "Connection: close\r\n" - + "\r\n" - + "X"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("POST", methodOrVersion); - assertEquals("Multiple Content-Lengths", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testMultipleContentLengthWithCorrectThenLargerValue() { - ByteBuffer buffer = BufferUtils.toBuffer( - "POST / HTTP/1.1\r\n" - + "Content-Length: 1\r\n" - + "Content-Length: 2\r\n" - + "Connection: close\r\n" - + "\r\n" - + "X"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - - parser.parseNext(buffer); - assertEquals("POST", methodOrVersion); - assertEquals("Multiple Content-Lengths", bad); - assertFalse(buffer.hasRemaining()); - assertEquals(HttpParser.State.CLOSE, parser.getState()); - parser.atEOF(); - parser.parseNext(BufferUtils.EMPTY_BUFFER); - assertEquals(HttpParser.State.CLOSED, parser.getState()); - } - - @Test - void testTransferEncodingChunkedThenContentLength() { - ByteBuffer buffer = BufferUtils.toBuffer( - "POST /chunk HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Transfer-Encoding: chunked\r\n" - + "Content-Length: 1\r\n" - + "\r\n" - + "1\r\n" - + "X\r\n" - + "0\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY); - parseAll(parser, buffer); - - assertEquals("POST", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.1", versionOrReason); - assertEquals("X", content); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - - assertTrue(complianceViolation.contains(HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH)); - } - - @Test - void testContentLengthThenTransferEncodingChunked() { - ByteBuffer buffer = BufferUtils.toBuffer( - "POST /chunk HTTP/1.1\r\n" - + "Host: localhost\r\n" - + "Content-Length: 1\r\n" - + "Transfer-Encoding: chunked\r\n" - + "\r\n" - + "1\r\n" - + "X\r\n" - + "0\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY); - parseAll(parser, buffer); - - assertEquals("POST", methodOrVersion); - assertEquals("/chunk", uriOrStatus); - assertEquals("HTTP/1.1", versionOrReason); - assertEquals("X", content); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - - assertTrue(complianceViolation.contains(HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH)); - } - - @Test - void testHost() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: host\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("host", host); - assertEquals(0, port); - } - - @Test - void testUriHost11() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET http://host/ HTTP/1.1\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("No Host", bad); - assertEquals("http://host/", uriOrStatus); - assertEquals(0, port); - } - - @Test - void testUriHost10() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET http://host/ HTTP/1.0\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertNull(bad); - assertEquals("http://host/", uriOrStatus); - assertEquals(0, port); - } - - @Test - void testNoHost() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("No Host", bad); - } - - @Test - void testIPHost() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: 192.168.0.1\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("192.168.0.1", host); - assertEquals(0, port); - } - - @Test - void testIPv6Host() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: [::1]\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("[::1]", host); - assertEquals(0, port); - } - - @Test - void testBadIPv6Host() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: [::1\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertTrue(bad.contains("Bad")); - } - - @Test - void testHostPort() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: myhost:8888\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("myhost", host); - assertEquals(8888, port); - } - - @Test - void testHostBadPort() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: myhost:testBadPort\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertTrue(bad.contains("Bad Host")); - assertFalse(messageCompleted); - } - - @Test - void testIPHostPort() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: 192.168.0.1:8888\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("192.168.0.1", host); - assertEquals(8888, port); - } - - @Test - void testIPv6HostPort() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host: [::1]:8888\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertEquals("[::1]", host); - assertEquals(8888, port); - } - - @Test - void testEmptyHostPort() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" - + "Host:\r\n" - + "Connection: close\r\n" - + "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - assertNull(host); - assertNull(bad); - } - - @Test - void testCachedField() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" + - "Host: www.smh.com.au\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - assertEquals("www.smh.com.au", parser.getFieldCache().get("Host: www.smh.com.au").getValue()); - HttpField field = fields.get(0); - - buffer.position(0); - parseAll(parser, buffer); - assertSame(field, fields.get(0)); - } - - @Test - void testParseRequest() { - ByteBuffer buffer = BufferUtils.toBuffer( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Header1: value1\r\n" + - "Connection: close\r\n" + - "Accept-Encoding: gzip, deflated\r\n" + - "Accept: unknown\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer); - - assertEquals("GET", methodOrVersion); - assertEquals("/", uriOrStatus); - assertEquals("HTTP/1.1", versionOrReason); - assertEquals("Host", hdr[0]); - assertEquals("localhost", val[0]); - assertEquals("Connection", hdr[2]); - assertEquals("close", val[2]); - assertEquals("Accept-Encoding", hdr[3]); - assertEquals("gzip, deflated", val[3]); - assertEquals("Accept", hdr[4]); - assertEquals("unknown", val[4]); - } - - @Test - void testHTTP2Preface() { - ByteBuffer buffer = BufferUtils.toBuffer( - "PRI * HTTP/2.0\r\n" + - "\r\n" + - "SM\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parseAll(parser, buffer); - - assertTrue(headerCompleted); - assertTrue(messageCompleted); - assertEquals("PRI", methodOrVersion); - assertEquals("*", uriOrStatus); - assertEquals("HTTP/2.0", versionOrReason); - assertEquals(-1, headers); - assertNull(bad); - } - - @Test - @DisplayName("should parse 100 continue response successfully.") - void testExpect100Continue() { - ByteBuffer buffer1 = BufferUtils.toBuffer("HTTP/1.1 100 Continue\r\n"); - ByteBuffer buffer2 = BufferUtils.toBuffer("HTTP/1.1 200 OK\r\n"); - ByteBuffer buffer3 = BufferUtils.toBuffer("Content-Length: 4\r\n" + - "\r\n" + - "test"); - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - boolean exit = parser.parseNext(buffer1); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("100", uriOrStatus); - assertEquals("Continue", versionOrReason); - assertEquals(State.HEADER, parser.getState()); - assertTrue(exit); - parser.reset(); - - assertEquals(State.START, parser.getState()); - System.out.println(parser.getState()); - - parser.parseNext(buffer2); - assertEquals("HTTP/1.1", methodOrVersion); - assertEquals("200", uriOrStatus); - assertEquals("OK", versionOrReason); - System.out.println(parser.getState()); - - parser.parseNext(buffer3); - assertEquals("Content-Length", hdr[0]); - assertEquals("4", val[0]); - assertEquals("test", content); - System.out.println(parser.getState()); - } - - @Test - @DisplayName("should parse expect 100-continue request successfully.") - void testServerAcceptExpect100Header() { - ByteBuffer buffer = BufferUtils.toBuffer( - "POST /test/data HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Expect: 100-continue\r\n" + - "Content-Length: 4\r\n" + - "Accept-Encoding: gzip, deflated\r\n" + - "Accept: unknown\r\n" + "\r\n"); - - ByteBuffer content = BufferUtils.toBuffer("test"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - System.out.println(buffer.remaining()); - boolean exit = parser.parseNext(buffer); - System.out.println(buffer.remaining()); - assertTrue(exit); - assertEquals(State.CONTENT, parser.getState()); - - exit = parser.parseNext(content); - assertTrue(exit); - assertEquals(State.END, parser.getState()); - } - - @Test - @DisplayName("should parse 101 successfully.") - void test101SwitchingProtocols() { - ByteBuffer buffer1 = BufferUtils.toBuffer("HTTP/1.1 101 Switching Protocols\r\n\r\n"); - HttpParser.ResponseHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler); - parser.parseNext(buffer1); - assertEquals(-1, headers); - assertTrue(headerCompleted); - assertTrue(messageCompleted); - } - - @BeforeEach - void init() { - bad = null; - content = null; - methodOrVersion = null; - uriOrStatus = null; - versionOrReason = null; - hdr = null; - val = null; - headers = 0; - headerCompleted = false; - messageCompleted = false; - complianceViolation.clear(); - } - - private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler { - - @Override - public boolean content(ByteBuffer ref) { - if (content == null) - content = ""; - String c = BufferUtils.toString(ref, StandardCharsets.UTF_8); - content = content + c; - ref.position(ref.limit()); - return false; - } - - @Override - public boolean startRequest(String method, String uri, HttpVersion version) { - fields.clear(); - trailers.clear(); - headers = -1; - hdr = new String[10]; - val = new String[10]; - methodOrVersion = method; - uriOrStatus = uri; - versionOrReason = version == null ? null : version.getValue(); - messageCompleted = false; - headerCompleted = false; - early = false; - return false; - } - - @Override - public void parsedHeader(HttpField field) { - fields.add(field); - hdr[++headers] = field.getName(); - val[headers] = field.getValue(); - - if (field instanceof HostPortHttpField) { - HostPortHttpField hpfield = (HostPortHttpField) field; - host = hpfield.getHost(); - port = hpfield.getPort(); - } - } - - @Override - public boolean headerComplete() { - content = null; - headerCompleted = true; - return fields.stream().anyMatch(f -> f.getHeader() == EXPECT && f.getValue().equals(CONTINUE.getValue())); - } - - @Override - public void parsedTrailer(HttpField field) { - trailers.add(field); - } - - @Override - public boolean contentComplete() { - return false; - } - - @Override - public boolean messageComplete() { - messageCompleted = true; - return true; - } - - @Override - public void badMessage(BadMessageException failure) { - String reason = failure.getReason(); - bad = reason == null ? String.valueOf(failure.getCode()) : reason; - } - - @Override - public boolean startResponse(HttpVersion version, int status, String reason) { - fields.clear(); - trailers.clear(); - methodOrVersion = version.getValue(); - uriOrStatus = Integer.toString(status); - versionOrReason = reason; - headers = -1; - hdr = new String[10]; - val = new String[10]; - messageCompleted = false; - headerCompleted = false; - return status == HttpStatus.CONTINUE_100; - } - - @Override - public void earlyEOF() { - early = true; - } - - @Override - public int getHeaderCacheSize() { - return 4096; - } - - @Override - public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason) { - complianceViolation.add(violation); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorClientTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorClientTest.java deleted file mode 100644 index 992587c2f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorClientTest.java +++ /dev/null @@ -1,358 +0,0 @@ -package com.fireflysource.net.http.common.v1.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.model.*; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class HttpGeneratorClientTest { - - @Test - void testGETRequestNoContent() { - ByteBuffer header = BufferUtils.allocate(2048); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("GET", "/index.html"); - info.getFields().add("Host", "something"); - info.getFields().add("User-Agent", "test"); - assertFalse(gen.isChunking()); - - result = gen.generateRequest(info, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertFalse(gen.isChunking()); - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateRequest(null, null, null, null, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - assertFalse(gen.isChunking()); - - assertEquals(0, gen.getContentPrepared()); - assertTrue(out.contains("GET /index.html HTTP/1.1")); - assertFalse(out.contains("Content-Length")); - } - - @Test - void testEmptyHeaders() { - ByteBuffer header = BufferUtils.allocate(2048); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("GET", "/index.html"); - info.getFields().add("Host", "something"); - info.getFields().add("Null", null); - info.getFields().add("Empty", ""); - assertFalse(gen.isChunking()); - - result = gen.generateRequest(info, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertFalse(gen.isChunking()); - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - assertFalse(gen.isChunking()); - - assertEquals(0, gen.getContentPrepared()); - assertTrue(out.contains("GET /index.html HTTP/1.1")); - assertFalse(out.contains("Content-Length")); - assertTrue(out.contains("Empty:")); - assertFalse(out.contains("Null:")); - } - - @Test - void testPOSTRequestNoContent() { - ByteBuffer header = BufferUtils.allocate(2048); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("POST", "/index.html"); - info.getFields().add("Host", "something"); - info.getFields().add("User-Agent", "test"); - assertFalse(gen.isChunking()); - - result = gen.generateRequest(info, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertFalse(gen.isChunking()); - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateRequest(null, null, null, null, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - assertFalse(gen.isChunking()); - - assertEquals(0, gen.getContentPrepared()); - assertTrue(out.contains("POST /index.html HTTP/1.1")); - assertTrue(out.contains("Content-Length: 0")); - } - - @Test - void testRequestWithContent() { - String out; - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World. The quick brown fox jumped over the lazy dog."); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, content0, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("POST", "/index.html"); - info.getFields().add("Host", "something"); - info.getFields().add("User-Agent", "test"); - - result = gen.generateRequest(info, null, null, content0, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, content0, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertFalse(gen.isChunking()); - - out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - assertFalse(gen.isChunking()); - - - assertTrue(out.contains("POST /index.html HTTP/1.1")); - assertTrue(out.contains("Host: something")); - assertTrue(out.contains("Content-Length: 58")); - assertTrue(out.contains("Hello World. The quick brown fox jumped over the lazy dog.")); - - assertEquals(58, gen.getContentPrepared()); - } - - @Test - void testRequestWithChunkedContent() { - String out; - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World. "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog."); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, null, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("POST", "/index.html"); - info.getFields().add("Host", "something"); - info.getFields().add("User-Agent", "test"); - - result = gen.generateRequest(info, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, null, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - assertTrue(gen.isChunking()); - out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateRequest(info, header, chunk, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - assertTrue(gen.isChunking()); - - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateRequest(null, null, null, content1, false); - assertEquals(HttpGenerator.Result.NEED_CHUNK, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - result = gen.generateRequest(null, null, chunk, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - assertTrue(gen.isChunking()); - - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateRequest(null, null, chunk, content1, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertTrue(gen.isChunking()); - - - result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_CHUNK, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertTrue(gen.isChunking()); - - result = gen.generateRequest(null, null, chunk, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - assertFalse(gen.isChunking()); - - result = gen.generateRequest(null, null, chunk, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("POST /index.html HTTP/1.1")); - assertTrue(out.contains("Host: something")); - assertTrue(out.contains("Transfer-Encoding: chunked")); - assertTrue(out.contains("\r\nD\r\nHello World. \r\n")); - assertTrue(out.contains("\r\n2D\r\nThe quick brown fox jumped over the lazy dog.\r\n")); - assertTrue(out.contains("\r\n0\r\n\r\n")); - - assertEquals(58, gen.getContentPrepared()); - } - - @Test - void testRequestWithKnownContent() { - String out; - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World. "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog."); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("POST", "/index.html", 58); - info.getFields().add("Host", "something"); - info.getFields().add("User-Agent", "test"); - - result = gen.generateRequest(info, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - assertFalse(gen.isChunking()); - out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateRequest(null, null, null, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - assertFalse(gen.isChunking()); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertFalse(gen.isChunking()); - - result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - - assertTrue(out.contains("POST /index.html HTTP/1.1")); - assertTrue(out.contains("Host: something")); - assertTrue(out.contains("Content-Length: 58")); - assertTrue(out.contains("\r\n\r\nHello World. The quick brown fox jumped over the lazy dog.")); - - assertEquals(58, gen.getContentPrepared()); - } - - @Test - void testAddFields() { - ByteBuffer header = BufferUtils.allocate(2048); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - Info info = new Info("GET", "/index.html"); - info.getFields().add("Host", "something1"); - info.getFields().add("User-Agent", "test1"); - info.getFields().add("Connection", HttpHeaderValue.KEEP_ALIVE.getValue()); - List values = info.getFields().getValuesList("Connection"); - if (values != null && !values.isEmpty()) { - info.getFields().remove("Connection"); - List newValues = new LinkedList<>(values); - newValues.add("Upgrade"); - newValues.add("HTTP2-Settings"); - info.getFields().addCSV("Connection", newValues.toArray(new String[0])); - } - assertFalse(gen.isChunking()); - - result = gen.generateRequest(info, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateRequest(info, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - assertFalse(gen.isChunking()); - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateRequest(null, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - assertFalse(gen.isChunking()); - - System.out.println(out); - assertTrue(out.contains("keep-alive, Upgrade, HTTP2-Settings")); - } - - class Info extends MetaData.Request { - Info(String method, String uri) { - super(method, new HttpURI(uri), HttpVersion.HTTP_1_1, new HttpFields(), -1); - } - - Info(String method, String uri, int contentLength) { - super(method, new HttpURI(uri), HttpVersion.HTTP_1_1, new HttpFields(), contentLength); - } - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorServerHTTPTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorServerHTTPTest.java deleted file mode 100644 index 6c2c545f4..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorServerHTTPTest.java +++ /dev/null @@ -1,307 +0,0 @@ -package com.fireflysource.net.http.common.v1.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.exception.BadMessageException; -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpVersion; -import com.fireflysource.net.http.common.model.MetaData; -import com.fireflysource.net.http.common.v1.decoder.HttpParser; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -class HttpGeneratorServerHTTPTest { - public final static String CONTENT = "The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n"; - private String content; - private String reason; - - public static Stream data() { - Result[] results = { - new Result(200, null, -1, null, false), - new Result(200, null, -1, CONTENT, false), - new Result(200, null, CONTENT.length(), null, true), - new Result(200, null, CONTENT.length(), CONTENT, false), - new Result(200, "text/html", -1, null, true), - new Result(200, "text/html", -1, CONTENT, false), - new Result(200, "text/html", CONTENT.length(), null, true), - new Result(200, "text/html", CONTENT.length(), CONTENT, false) - }; - - ArrayList data = new ArrayList<>(); - - // For each test result - for (Result result : results) { - // Loop over HTTP versions - for (int v = 10; v <= 11; v++) { - // Loop over chunks - for (int chunks = 1; chunks <= 6; chunks++) { - // Loop over Connection values - for (ConnectionType connection : ConnectionType.values()) { - if (connection.isSupportedByHttp(v)) { - data.add(Arguments.of(new Run(result, v, chunks, connection))); - } - } - } - } - } - - return data.stream(); - } - - @ParameterizedTest - @MethodSource("data") - void testHTTP(Run run) throws Exception { - Handler handler = new Handler(); - - HttpGenerator gen = new HttpGenerator(); - - String msg = run.toString(); - - run.result.getHttpFields().clear(); - - String response = run.result.build(run.httpVersion, gen, "OK\r\nTest", run.connection.val, null, run.chunks); - - HttpParser parser = new HttpParser(handler); - parser.setHeadResponse(run.result.head); - - parser.parseNext(BufferUtils.toBuffer(response)); - - if (run.result.body != null) - assertEquals(run.result.body, this.content, msg); - - // TODO: Break down rationale more clearly, these should be separate checks and/or assertions - if (run.httpVersion == 10) - assertTrue(gen.isPersistent() || run.result.contentLength >= 0 || EnumSet.of(ConnectionType.CLOSE, ConnectionType.KEEP_ALIVE, ConnectionType.NONE).contains(run.connection), msg); - else - assertTrue(gen.isPersistent() || EnumSet.of(ConnectionType.CLOSE, ConnectionType.TE_CLOSE).contains(run.connection), msg); - - assertEquals("OK??Test", reason); - - if (content == null) - assertNull(run.result.body, msg); - else - assertTrue(run.result.contentLength == content.length() || run.result.contentLength == -1, msg); - } - - private enum ConnectionType { - NONE(null, 9, 10, 11), - KEEP_ALIVE("keep-alive", 9, 10, 11), - CLOSE("close", 9, 10, 11), - TE_CLOSE("TE, close", 11); - - private String val; - private int[] supportedHttpVersions; - - ConnectionType(String val, int... supportedHttpVersions) { - this.val = val; - this.supportedHttpVersions = supportedHttpVersions; - } - - public boolean isSupportedByHttp(int version) { - for (int supported : supportedHttpVersions) { - if (supported == version) { - return true; - } - } - return false; - } - } - - private static class Result { - private final String body; - private final int code; - private final boolean head; - private HttpFields fields = new HttpFields(); - private String connection; - private int contentLength; - private String contentType; - private String other; - private String te; - - private Result(int code, String contentType, int contentLength, String content, boolean head) { - this.code = code; - this.contentType = contentType; - this.contentLength = contentLength; - other = "value"; - body = content; - this.head = head; - } - - private String build(int version, HttpGenerator gen, String reason, String connection, String te, int nchunks) throws Exception { - String response = ""; - this.connection = connection; - this.te = te; - - if (contentType != null) - fields.put("Content-Type", contentType); - if (contentLength >= 0) - fields.put("Content-Length", "" + contentLength); - if (this.connection != null) - fields.put("Connection", this.connection); - if (this.te != null) - fields.put("Transfer-Encoding", this.te); - if (other != null) - fields.put("Other", other); - - ByteBuffer source = body == null ? null : BufferUtils.toBuffer(body); - ByteBuffer[] chunks = new ByteBuffer[nchunks]; - ByteBuffer content = null; - int c = 0; - if (source != null) { - for (int i = 0; i < nchunks; i++) { - chunks[i] = source.duplicate(); - chunks[i].position(i * (source.capacity() / nchunks)); - if (i > 0) - chunks[i - 1].limit(chunks[i].position()); - } - content = chunks[c++]; - } - ByteBuffer header = null; - ByteBuffer chunk = null; - MetaData.Response info = null; - - loop: - while (true) { - // if we have unwritten content - if (source != null && content != null && content.remaining() == 0 && c < nchunks) - content = chunks[c++]; - - // Generate - boolean last = !BufferUtils.hasContent(content); - - HttpGenerator.Result result = gen.generateResponse(info, head, header, chunk, content, last); - - switch (result) { - case NEED_INFO: - info = new MetaData.Response(HttpVersion.fromVersion(version), code, reason, fields, contentLength); - continue; - - case NEED_HEADER: - header = BufferUtils.allocate(2048); - continue; - - case NEED_CHUNK: - chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - continue; - - case NEED_CHUNK_TRAILER: - chunk = BufferUtils.allocate(2048); - continue; - - - case FLUSH: - if (BufferUtils.hasContent(header)) { - response += BufferUtils.toString(header); - header.position(header.limit()); - } - if (BufferUtils.hasContent(chunk)) { - response += BufferUtils.toString(chunk); - chunk.position(chunk.limit()); - } - if (BufferUtils.hasContent(content)) { - response += BufferUtils.toString(content); - content.position(content.limit()); - } - break; - - case CONTINUE: - continue; - - case SHUTDOWN_OUT: - break; - - case DONE: - break loop; - } - } - return response; - } - - @Override - public String toString() { - return "[" + code + "," + contentType + "," + contentLength + "," + (body == null ? "null" : "content") + "]"; - } - - public HttpFields getHttpFields() { - return fields; - } - } - - private static class Run { - private Result result; - private ConnectionType connection; - private int httpVersion; - private int chunks; - - public Run(Result result, int ver, int chunks, ConnectionType connection) { - this.result = result; - this.httpVersion = ver; - this.chunks = chunks; - this.connection = connection; - } - - @Override - public String toString() { - return String.format("result=%s,version=%d,chunks=%d,connection=%s", result, httpVersion, chunks, connection.name()); - } - } - - private class Handler implements HttpParser.ResponseHandler { - @Override - public boolean content(ByteBuffer ref) { - if (content == null) - content = ""; - content += BufferUtils.toString(ref); - ref.position(ref.limit()); - return false; - } - - @Override - public void earlyEOF() { - } - - @Override - public boolean headerComplete() { - content = null; - return false; - } - - @Override - public boolean contentComplete() { - return false; - } - - @Override - public boolean messageComplete() { - return true; - } - - @Override - public void parsedHeader(HttpField field) { - } - - @Override - public boolean startResponse(HttpVersion version, int status, String reason) { - HttpGeneratorServerHTTPTest.this.reason = reason; - return false; - } - - @Override - public void badMessage(BadMessageException failure) { - throw failure; - } - - @Override - public int getHeaderCacheSize() { - return 4096; - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorServerTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorServerTest.java deleted file mode 100644 index 0bed59c4b..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v1/encoder/HttpGeneratorServerTest.java +++ /dev/null @@ -1,861 +0,0 @@ -package com.fireflysource.net.http.common.v1.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.sys.ProjectVersion; -import com.fireflysource.net.http.common.codec.DateGenerator; -import com.fireflysource.net.http.common.exception.BadMessageException; -import com.fireflysource.net.http.common.model.*; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.*; - -class HttpGeneratorServerTest { - - @Test - void test_0_9() { - ByteBuffer header = BufferUtils.allocate(8096); - ByteBuffer content = BufferUtils.toBuffer("0123456789"); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_0_9, 200, null, new HttpFields(), 10); - info.getFields().add("Content-Type", "test/data"); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - - result = gen.generateResponse(info, false, null, null, content, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String response = BufferUtils.toString(header); - BufferUtils.clear(header); - response += BufferUtils.toString(content); - BufferUtils.clear(content); - - result = gen.generateResponse(null, false, null, null, content, false); - assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertEquals(10, gen.getContentPrepared()); - - assertFalse(response.contains("200 OK")); - assertFalse(response.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(response.contains("Content-Length: 10")); - assertTrue(response.contains("0123456789")); - } - - @Test - void testSimple() { - ByteBuffer header = BufferUtils.allocate(8096); - ByteBuffer content = BufferUtils.toBuffer("0123456789"); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10); - info.getFields().add("Content-Type", "test/data"); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - - result = gen.generateResponse(info, false, null, null, content, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - - result = gen.generateResponse(info, false, header, null, content, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String response = BufferUtils.toString(header); - BufferUtils.clear(header); - response += BufferUtils.toString(content); - BufferUtils.clear(content); - - result = gen.generateResponse(null, false, null, null, null, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertEquals(10, gen.getContentPrepared()); - - assertTrue(response.contains("HTTP/1.1 200 OK")); - assertTrue(response.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertTrue(response.contains("Content-Length: 10")); - assertTrue(response.contains("\r\n0123456789")); - } - - @Test - @DisplayName("should generate response with content length successfully") - void testGenerateHeaderNoLast() { - ByteBuffer header = BufferUtils.allocate(8096); - ByteBuffer content = BufferUtils.toBuffer("0123456789"); - - HttpGenerator gen = new HttpGenerator(); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10); - info.getFields().add("Content-Type", "test/data"); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - - HttpGenerator.Result result = gen.generateResponse(info, false, header, null, null, false); - System.out.println(result + ", " + gen.getState()); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - String response = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(null, false, null, null, content, false); - System.out.println(result + ", " + gen.getState()); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - response += BufferUtils.toString(content); - BufferUtils.clear(content); - - result = gen.generateResponse(null, false, null, null, null, true); - System.out.println(result + ", " + gen.getState()); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, null, null, true); - System.out.println(result + ", " + gen.getState()); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - System.out.println(response); - assertTrue(response.contains("HTTP/1.1 200 OK")); - assertTrue(response.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertTrue(response.contains("Content-Length: 10")); - assertTrue(response.contains("\r\n0123456789")); - } - - @Test - void test204() { - ByteBuffer header = BufferUtils.allocate(8096); - ByteBuffer content = BufferUtils.toBuffer("0123456789"); - - HttpGenerator gen = new HttpGenerator(); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 204, "Foo", new HttpFields(), 10); - info.getFields().add("Content-Type", "test/data"); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - - HttpGenerator.Result result = gen.generateResponse(info, false, header, null, content, true); - - assertTrue(gen.isNoContent()); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String responseHeaders = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(null, false, null, null, content, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(responseHeaders.contains("HTTP/1.1 204 Foo")); - assertTrue(responseHeaders.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(responseHeaders.contains("Content-Length: 10")); - - //Note: the HttpConnection.process() method is responsible for actually - //excluding the content from the response based on generator.isNoContent()==true - } - - - @Test - void testComplexChars() { - ByteBuffer header = BufferUtils.allocate(8096); - ByteBuffer content = BufferUtils.toBuffer("0123456789"); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, "ØÆ", new HttpFields(), 10); - info.getFields().add("Content-Type", "test/data;\r\nextra=value"); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - - result = gen.generateResponse(info, false, null, null, content, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - - result = gen.generateResponse(info, false, header, null, content, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String response = BufferUtils.toString(header); - BufferUtils.clear(header); - response += BufferUtils.toString(content); - BufferUtils.clear(content); - - result = gen.generateResponse(null, false, null, null, content, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertEquals(10, gen.getContentPrepared()); - - assertTrue(response.contains("HTTP/1.1 200 ØÆ")); - assertTrue(response.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertTrue(response.contains("Content-Type: test/data; extra=value")); - assertTrue(response.contains("Content-Length: 10")); - assertTrue(response.contains("\r\n0123456789")); - } - - @Test - void testSendServerXPoweredBy() { - ByteBuffer header = BufferUtils.allocate(8096); - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); - HttpFields fields = new HttpFields(); - fields.add(HttpHeader.SERVER, "SomeServer"); - fields.add(HttpHeader.X_POWERED_BY, "SomePower"); - MetaData.Response infoF = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, fields, -1); - String head; - - HttpGenerator gen = new HttpGenerator(true, true); - gen.generateResponse(info, false, header, null, null, true); - head = BufferUtils.toString(header); - BufferUtils.clear(header); - assertTrue(head.contains("HTTP/1.1 200 OK")); - assertTrue(head.contains("Server: Firefly(" + ProjectVersion.getValue() + ")")); - assertTrue(head.contains("X-Powered-By: Firefly(" + ProjectVersion.getValue() + ")")); - gen.reset(); - gen.generateResponse(infoF, false, header, null, null, true); - head = BufferUtils.toString(header); - BufferUtils.clear(header); - assertTrue(head.contains("HTTP/1.1 200 OK")); - assertFalse(head.contains("Server: Firefly(" + ProjectVersion.getValue() + ")")); - assertTrue(head.contains("Server: SomeServer")); - assertTrue(head.contains("X-Powered-By: Firefly(" + ProjectVersion.getValue() + ")")); - assertTrue(head.contains("X-Powered-By: SomePower")); - gen.reset(); - - gen = new HttpGenerator(false, false); - gen.generateResponse(info, false, header, null, null, true); - head = BufferUtils.toString(header); - BufferUtils.clear(header); - assertTrue(head.contains("HTTP/1.1 200 OK")); - assertFalse(head.contains("Server: Firefly(" + ProjectVersion.getValue() + ")")); - assertFalse(head.contains("X-Powered-By: Firefly(" + ProjectVersion.getValue() + ")")); - gen.reset(); - gen.generateResponse(infoF, false, header, null, null, true); - head = BufferUtils.toString(header); - BufferUtils.clear(header); - assertTrue(head.contains("HTTP/1.1 200 OK")); - assertFalse(head.contains("Server: Firefly(" + ProjectVersion.getValue() + ")")); - assertTrue(head.contains("Server: SomeServer")); - assertFalse(head.contains("X-Powered-By: Firefly(" + ProjectVersion.getValue() + ")")); - assertTrue(head.contains("X-Powered-By: SomePower")); - gen.reset(); - } - - @Test - void testResponseIncorrectContentLength() { - ByteBuffer header = BufferUtils.allocate(8096); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - info.getFields().add("Content-Length", "11"); - - result = gen.generateResponse(info, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - - BadMessageException e = assertThrows(BadMessageException.class, - () -> gen.generateResponse(info, false, header, null, null, true)); - assertEquals(500, e.getCode()); - } - - @Test - void testResponseNoContentPersistent() { - ByteBuffer header = BufferUtils.allocate(8096); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - - result = gen.generateResponse(info, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - - result = gen.generateResponse(info, false, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String head = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(null, false, null, null, null, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertEquals(0, gen.getContentPrepared()); - assertTrue(head.contains("HTTP/1.1 200 OK")); - assertTrue(head.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertTrue(head.contains("Content-Length: 0")); - } - - @Test - void testResponseKnownNoContentNotPersistent() { - ByteBuffer header = BufferUtils.allocate(8096); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - info.getFields().add("Connection", "close"); - - result = gen.generateResponse(info, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - - result = gen.generateResponse(info, false, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String head = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(null, false, null, null, null, false); - assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertEquals(0, gen.getContentPrepared()); - assertTrue(head.contains("HTTP/1.1 200 OK")); - assertTrue(head.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertTrue(head.contains("Connection: close")); - } - - @Test - void testResponseUpgrade() { - ByteBuffer header = BufferUtils.allocate(8096); - - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 101, null, new HttpFields(), -1); - info.getFields().add("Upgrade", "WebSocket"); - info.getFields().add("Connection", "Upgrade"); - info.getFields().add("Sec-WebSocket-Accept", "123456789=="); - - result = gen.generateResponse(info, false, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - String head = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(info, false, null, null, null, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertEquals(0, gen.getContentPrepared()); - - assertTrue(head.startsWith("HTTP/1.1 101 Switching Protocols")); - assertTrue(head.contains("Upgrade: WebSocket\r\n")); - assertTrue(head.contains("Connection: Upgrade\r\n")); - } - - @Test - void testResponseWithChunkedContent() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - result = gen.generateResponse(info, false, null, null, null, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, null, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(null, false, null, chunk, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateResponse(null, false, null, chunk, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, chunk, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - System.out.println(out); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("Content-Length")); - assertTrue(out.contains("Transfer-Encoding: chunked")); - - assertTrue(out.endsWith( - "\r\n\r\nD\r\n" + - "Hello World! \r\n" + - "2E\r\n" + - "The quick brown fox jumped over the lazy dog. \r\n" + - "0\r\n" + - "\r\n")); - } - - @Test - void testResponseWithHintedChunkedContent() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); - HttpGenerator gen = new HttpGenerator(); - gen.setPersistent(false); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - info.getFields().add(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED); - result = gen.generateResponse(info, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateResponse(null, false, null, null, content1, false); - assertEquals(HttpGenerator.Result.NEED_CHUNK, result); - - result = gen.generateResponse(null, false, null, chunk, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateResponse(null, false, null, chunk, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, chunk, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - - result = gen.generateResponse(null, false, null, chunk, null, true); - assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("Content-Length")); - assertTrue(out.contains("Transfer-Encoding: chunked")); - - assertTrue(out.endsWith( - "\r\n\r\nD\r\n" + - "Hello World! \r\n" + - "2E\r\n" + - "The quick brown fox jumped over the lazy dog. \r\n" + - "0\r\n" + - "\r\n")); - } - - @Test - void testResponseWithContentAndTrailer() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - ByteBuffer trailer = BufferUtils.allocate(4096); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); - HttpGenerator gen = new HttpGenerator(); - gen.setPersistent(false); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - info.getFields().add(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED); - info.setTrailerSupplier(() -> { - HttpFields trailer1 = new HttpFields(); - trailer1.add("T-Name0", "T-ValueA"); - trailer1.add("T-Name0", "T-ValueB"); - trailer1.add("T-Name1", "T-ValueC"); - return trailer1; - }); - - result = gen.generateResponse(info, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateResponse(null, false, null, null, content1, false); - assertEquals(HttpGenerator.Result.NEED_CHUNK, result); - - result = gen.generateResponse(null, false, null, chunk, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(chunk); - BufferUtils.clear(chunk); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, null, null, true); - - assertEquals(HttpGenerator.Result.NEED_CHUNK_TRAILER, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, trailer, null, true); - - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - out += BufferUtils.toString(trailer); - BufferUtils.clear(trailer); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("Content-Length")); - assertTrue(out.contains("Transfer-Encoding: chunked")); - - assertTrue(out.endsWith( - "\r\n\r\nD\r\n" + - "Hello World! \r\n" + - "2E\r\n" + - "The quick brown fox jumped over the lazy dog. \r\n" + - "0\r\n" + - "T-Name0: T-ValueA\r\n" + - "T-Name0: T-ValueB\r\n" + - "T-Name1: T-ValueC\r\n" + - "\r\n")); - } - - @Test - void testResponseWithTrailer() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); - ByteBuffer trailer = BufferUtils.allocate(4096); - HttpGenerator gen = new HttpGenerator(); - gen.setPersistent(false); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - info.getFields().add(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED); - info.setTrailerSupplier(() -> { - HttpFields trailer1 = new HttpFields(); - trailer1.add("T-Name0", "T-ValueA"); - trailer1.add("T-Name0", "T-ValueB"); - trailer1.add("T-Name1", "T-ValueC"); - return trailer1; - }); - - result = gen.generateResponse(info, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.NEED_CHUNK_TRAILER, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, chunk, null, true); - assertEquals(HttpGenerator.Result.NEED_CHUNK_TRAILER, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, trailer, null, true); - - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - out += BufferUtils.toString(trailer); - BufferUtils.clear(trailer); - - result = gen.generateResponse(null, false, null, trailer, null, true); - assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("Content-Length")); - assertTrue(out.contains("Transfer-Encoding: chunked")); - - assertTrue(out.endsWith( - "\r\n\r\n" + - "0\r\n" + - "T-Name0: T-ValueA\r\n" + - "T-Name0: T-ValueB\r\n" + - "T-Name1: T-ValueC\r\n" + - "\r\n")); - } - - @Test - void testResponseWithKnownContentLengthFromMetaData() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 59); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - result = gen.generateResponse(info, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateResponse(null, false, null, null, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("chunked")); - assertTrue(out.contains("Content-Length: 59")); - assertTrue(out.contains("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. ")); - } - - @Test - void testResponseWithKnownContentLengthFromHeader() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - info.getFields().add("Content-Length", "" + (content0.remaining() + content1.remaining())); - result = gen.generateResponse(info, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - - String out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateResponse(null, false, null, null, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("chunked")); - assertTrue(out.contains("Content-Length: 59")); - assertTrue(out.contains("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. ")); - } - - - @Test - void test100ThenResponseWithContent() { - ByteBuffer header = BufferUtils.allocate(4096); - ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); - ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); - HttpGenerator gen = new HttpGenerator(); - - HttpGenerator.Result result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, false, null, null, null, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, false, header, null, null, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMPLETING_1XX, gen.getState()); - String out = BufferUtils.toString(header); - - result = gen.generateResponse(null, false, null, null, null, false); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 100 Continue\r\n")); - - result = gen.generateResponse(null, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_INFO, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), BufferUtils.length(content0) + BufferUtils.length(content1)); - info.getFields().add("Last-Modified", DateGenerator.JAN_01_1970); - result = gen.generateResponse(info, false, null, null, content0, false); - assertEquals(HttpGenerator.Result.NEED_HEADER, result); - assertEquals(HttpGenerator.State.START, gen.getState()); - - result = gen.generateResponse(info, false, header, null, content0, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - - out = BufferUtils.toString(header); - BufferUtils.clear(header); - out += BufferUtils.toString(content0); - BufferUtils.clear(content0); - - result = gen.generateResponse(null, false, null, null, content1, false); - assertEquals(HttpGenerator.Result.FLUSH, result); - assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); - out += BufferUtils.toString(content1); - BufferUtils.clear(content1); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.CONTINUE, result); - assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - - result = gen.generateResponse(null, false, null, null, null, true); - assertEquals(HttpGenerator.Result.DONE, result); - assertEquals(HttpGenerator.State.END, gen.getState()); - - assertTrue(out.contains("HTTP/1.1 200 OK")); - assertTrue(out.contains("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); - assertFalse(out.contains("chunked")); - assertTrue(out.contains("Content-Length: 59")); - assertTrue(out.contains("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. ")); - } - - @Test - void testConnectionKeepAliveWithAdditionalCustomValue() { - HttpGenerator generator = new HttpGenerator(); - - HttpFields fields = new HttpFields(); - fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE); - String customValue = "test"; - fields.add(HttpHeader.CONNECTION, customValue); - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_0, 200, "OK", fields, -1); - ByteBuffer header = BufferUtils.allocate(4096); - HttpGenerator.Result result = generator.generateResponse(info, false, header, null, null, true); - assertSame(HttpGenerator.Result.FLUSH, result); - String headers = BufferUtils.toString(header); - assertTrue(headers.contains(HttpHeaderValue.KEEP_ALIVE.getValue())); - assertTrue(headers.contains(customValue)); - } - - @Test - void testResponseLine() { - HttpGenerator generator = new HttpGenerator(); - HttpFields fields = new HttpFields(); - MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_0, 200, "Connection Established", fields, -1); - ByteBuffer header = BufferUtils.allocate(4096); - HttpGenerator.Result result = generator.generateResponse(info, false, header, null, null, true); - assertEquals(HttpGenerator.Result.FLUSH, result); - - String headers = BufferUtils.toString(header); - System.out.println(headers); - assertTrue(headers.contains("HTTP/1.1 200 Connection Established\r\n\r\n")); - - HttpGenerator.Result endResult = generator.generateResponse(null, false, null, null, null, true); - System.out.println(generator.isChunking()); - System.out.println(endResult); - assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, endResult); - - HttpGenerator.Result done = generator.generateResponse(null, false, null, null, null, true); - System.out.println(done); - assertEquals(HttpGenerator.Result.DONE, done); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/ContinuationParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/ContinuationParseTest.java deleted file mode 100644 index 76a405df5..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/ContinuationParseTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - - -import com.fireflysource.net.http.common.model.*; -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.HeadersGenerator; -import com.fireflysource.net.http.common.v2.hpack.HpackEncoder; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.*; - -class ContinuationParseTest { - - @Test - void testParseOneByteAtATime() { - HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onHeaders(HeadersFrame frame) { - frames.add(frame); - } - - @Override - public void onConnectionFailure(int error, String reason) { - frames.add(new HeadersFrame(null, null, false)); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure the parser is properly reset. - for (int i = 0; i < 2; ++i) { - int streamId = 13; - HttpFields fields = new HttpFields(); - fields.put("Accept", "text/html"); - fields.put("User-Agent", "Firefly"); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - - FrameBytes frameBytes = generator.generateHeaders(streamId, metaData, null, true); - - List byteBuffers = frameBytes.getByteBuffers(); - assertEquals(2, byteBuffers.size()); - - ByteBuffer headersBody = byteBuffers.remove(1); - int start = headersBody.position(); - int length = headersBody.remaining(); - int oneThird = length / 3; - int lastThird = length - 2 * oneThird; - - // Adjust the length of the HEADERS frame. - ByteBuffer headersHeader = byteBuffers.get(0); - headersHeader.put(0, (byte) ((oneThird >>> 16) & 0xFF)); - headersHeader.put(1, (byte) ((oneThird >>> 8) & 0xFF)); - headersHeader.put(2, (byte) (oneThird & 0xFF)); - - // Remove the END_HEADERS flag from the HEADERS header. - headersHeader.put(4, (byte) (headersHeader.get(4) & ~Flags.END_HEADERS)); - - // New HEADERS body. - headersBody.position(start); - headersBody.limit(start + oneThird); - byteBuffers.add(headersBody.slice()); - - // Split the rest of the HEADERS body into CONTINUATION frames. - // First CONTINUATION header. - byte[] continuationHeader1 = new byte[9]; - continuationHeader1[0] = (byte) ((oneThird >>> 16) & 0xFF); - continuationHeader1[1] = (byte) ((oneThird >>> 8) & 0xFF); - continuationHeader1[2] = (byte) (oneThird & 0xFF); - continuationHeader1[3] = (byte) FrameType.CONTINUATION.getType(); - continuationHeader1[4] = Flags.NONE; - continuationHeader1[5] = 0x00; - continuationHeader1[6] = 0x00; - continuationHeader1[7] = 0x00; - continuationHeader1[8] = (byte) streamId; - byteBuffers.add(ByteBuffer.wrap(continuationHeader1)); - // First CONTINUATION body. - headersBody.position(start + oneThird); - headersBody.limit(start + 2 * oneThird); - byteBuffers.add(headersBody.slice()); - // Second CONTINUATION header. - byte[] continuationHeader2 = new byte[9]; - continuationHeader2[0] = (byte) ((lastThird >>> 16) & 0xFF); - continuationHeader2[1] = (byte) ((lastThird >>> 8) & 0xFF); - continuationHeader2[2] = (byte) (lastThird & 0xFF); - continuationHeader2[3] = (byte) FrameType.CONTINUATION.getType(); - continuationHeader2[4] = Flags.END_HEADERS; - continuationHeader2[5] = 0x00; - continuationHeader2[6] = 0x00; - continuationHeader2[7] = 0x00; - continuationHeader2[8] = (byte) streamId; - byteBuffers.add(ByteBuffer.wrap(continuationHeader2)); - headersBody.position(start + 2 * oneThird); - headersBody.limit(start + length); - byteBuffers.add(headersBody.slice()); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - HeadersFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertTrue(frame.isEndStream()); - MetaData.Request request = (MetaData.Request) frame.getMetaData(); - assertEquals(metaData.getMethod(), request.getMethod()); - assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) { - HttpField field = fields.getField(j); - assertTrue(request.getFields().contains(field)); - } - PriorityFrame priority = frame.getPriority(); - assertNull(priority); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/DataGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/DataGenerateParseTest.java deleted file mode 100644 index 04a4e432f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/DataGenerateParseTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.DataGenerator; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class DataGenerateParseTest { - private final byte[] smallContent = new byte[128]; - private final byte[] largeContent = new byte[128 * 1024]; - - DataGenerateParseTest() { - Random random = new Random(); - random.nextBytes(smallContent); - random.nextBytes(largeContent); - } - - @Test - void testGenerateParseNoContentNoPadding() { - testGenerateParseContent(BufferUtils.EMPTY_BUFFER); - } - - @Test - void testGenerateParseSmallContentNoPadding() { - testGenerateParseContent(ByteBuffer.wrap(smallContent)); - } - - private void testGenerateParseContent(ByteBuffer content) { - List frames = testGenerateParse(content); - assertEquals(1, frames.size()); - DataFrame frame = frames.get(0); - assertTrue(frame.getStreamId() != 0); - assertTrue(frame.isEndStream()); - assertEquals(content, frame.getData()); - } - - @Test - void testGenerateParseLargeContent() { - ByteBuffer content = ByteBuffer.wrap(largeContent); - List frames = testGenerateParse(content); - assertEquals(8, frames.size()); - ByteBuffer aggregate = ByteBuffer.allocate(content.remaining()); - for (int i = 1; i <= frames.size(); ++i) { - DataFrame frame = frames.get(i - 1); - assertTrue(frame.getStreamId() != 0); - assertEquals(i == frames.size(), frame.isEndStream()); - aggregate.put(frame.getData()); - } - aggregate.flip(); - assertEquals(content, aggregate); - } - - private List testGenerateParse(ByteBuffer data) { - DataGenerator generator = new DataGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onData(DataFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - ByteBuffer slice = data.slice(); - int generated = 0; - List list = new LinkedList<>(); - while (true) { - FrameBytes frameBytes = generator.generateData(13, slice, true, slice.remaining()); - generated += frameBytes.getLength(); - generated -= Frame.HEADER_LENGTH; - list.addAll(frameBytes.getByteBuffers()); - if (generated == data.remaining()) - break; - } - - frames.clear(); - for (ByteBuffer buffer : list) { - parser.parse(buffer); - } - } - - return frames; - } - - @Test - void testGenerateParseOneByteAtATime() { - DataGenerator generator = new DataGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onData(DataFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - ByteBuffer data = ByteBuffer.wrap(largeContent); - ByteBuffer slice = data.slice(); - int generated = 0; - List list = new LinkedList<>(); - while (true) { - FrameBytes frameBytes = generator.generateData(13, slice, true, slice.remaining()); - generated += frameBytes.getLength(); - generated -= Frame.HEADER_LENGTH; - list.addAll(frameBytes.getByteBuffers()); - if (generated == data.remaining()) - break; - } - - frames.clear(); - for (ByteBuffer buffer : list) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(largeContent.length, frames.size()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/GoAwayGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/GoAwayGenerateParseTest.java deleted file mode 100644 index 7c9aea8cf..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/GoAwayGenerateParseTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.GoAwayGenerator; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.*; - -class GoAwayGenerateParseTest { - - @Test - void testGenerateParse() { - GoAwayGenerator generator = new GoAwayGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onGoAway(GoAwayFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int lastStreamId = 13; - int error = 17; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateGoAway(lastStreamId, error, null); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - } - - assertEquals(1, frames.size()); - GoAwayFrame frame = frames.get(0); - assertEquals(lastStreamId, frame.getLastStreamId()); - assertEquals(error, frame.getError()); - assertNull(frame.getPayload()); - } - - @Test - void testGenerateParseOneByteAtATime() { - GoAwayGenerator generator = new GoAwayGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onGoAway(GoAwayFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int lastStreamId = 13; - int error = 17; - byte[] payload = new byte[16]; - new Random().nextBytes(payload); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateGoAway(lastStreamId, error, payload); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - GoAwayFrame frame = frames.get(0); - assertEquals(lastStreamId, frame.getLastStreamId()); - assertEquals(error, frame.getError()); - assertArrayEquals(payload, frame.getPayload()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/HeadersGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/HeadersGenerateParseTest.java deleted file mode 100644 index 838b3aff3..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/HeadersGenerateParseTest.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.model.*; -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.HeadersGenerator; -import com.fireflysource.net.http.common.v2.hpack.HpackEncoder; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.*; - -class HeadersGenerateParseTest { - - @Test - void testGenerateTrailer() { - HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); - - int streamId = 13; - HttpFields fields = new HttpFields(); - fields.put("trailer1", "foo"); - fields.put("trailer2", "bar"); - MetaData.Request metaData = new MetaData.Request(fields); - metaData.setOnlyTrailer(true); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onHeaders(HeadersFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - FrameBytes frameBytes = generator.generateHeaders(streamId, metaData, null, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - - assertEquals(1, frames.size()); - HeadersFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertTrue(frame.isEndStream()); - assertEquals("foo", frame.getMetaData().getFields().get("trailer1")); - assertEquals("bar", frame.getMetaData().getFields().get("trailer2")); - } - - @Test - void testGenerateParse() { - HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); - - int streamId = 13; - HttpFields fields = new HttpFields(); - fields.put("Accept", "text/html"); - fields.put("User-Agent", "Firefly"); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onHeaders(HeadersFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - PriorityFrame priorityFrame = new PriorityFrame(streamId, 3 * streamId, 200, true); - FrameBytes frameBytes = generator.generateHeaders(streamId, metaData, priorityFrame, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - - assertEquals(1, frames.size()); - HeadersFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertTrue(frame.isEndStream()); - MetaData.Request request = (MetaData.Request) frame.getMetaData(); - assertEquals(metaData.getMethod(), request.getMethod()); - assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) { - HttpField field = fields.getField(j); - assertTrue(request.getFields().contains(field)); - } - PriorityFrame priority = frame.getPriority(); - assertNotNull(priority); - assertEquals(priorityFrame.getStreamId(), priority.getStreamId()); - assertEquals(priorityFrame.getParentStreamId(), priority.getParentStreamId()); - assertEquals(priorityFrame.getWeight(), priority.getWeight()); - assertEquals(priorityFrame.isExclusive(), priority.isExclusive()); - } - } - - @Test - void testGenerateParseOneByteAtATime() { - HeadersGenerator generator = new HeadersGenerator(new HeaderGenerator(), new HpackEncoder()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onHeaders(HeadersFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - int streamId = 13; - HttpFields fields = new HttpFields(); - fields.put("Accept", "text/html"); - fields.put("User-Agent", "Firefly"); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - - PriorityFrame priorityFrame = new PriorityFrame(streamId, 3 * streamId, 200, true); - FrameBytes frameBytes = generator.generateHeaders(streamId, metaData, priorityFrame, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - buffer = buffer.slice(); - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - HeadersFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertTrue(frame.isEndStream()); - MetaData.Request request = (MetaData.Request) frame.getMetaData(); - assertEquals(metaData.getMethod(), request.getMethod()); - assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) { - HttpField field = fields.getField(j); - assertTrue(request.getFields().contains(field)); - } - PriorityFrame priority = frame.getPriority(); - assertNotNull(priority); - assertEquals(priorityFrame.getStreamId(), priority.getStreamId()); - assertEquals(priorityFrame.getParentStreamId(), priority.getParentStreamId()); - assertEquals(priorityFrame.getWeight(), priority.getWeight()); - assertEquals(priorityFrame.isExclusive(), priority.isExclusive()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/MaxFrameSizeParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/MaxFrameSizeParseTest.java deleted file mode 100644 index ca6184eb1..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/MaxFrameSizeParseTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class MaxFrameSizeParseTest { - - @Test - void testMaxFrameSize() { - int maxFrameLength = Frame.DEFAULT_MAX_LENGTH + 16; - - AtomicInteger failure = new AtomicInteger(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onConnectionFailure(int error, String reason) { - failure.set(error); - } - }, 4096, 8192); - parser.setMaxFrameLength(maxFrameLength); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure the parser is properly reset. - for (int i = 0; i < 2; ++i) { - byte[] bytes = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}; - ByteBuffer buffer = ByteBuffer.wrap(bytes); - buffer.putInt(0, maxFrameLength + 1); - buffer.position(1); - while (buffer.hasRemaining()) - parser.parse(buffer); - } - - assertEquals(ErrorCode.FRAME_SIZE_ERROR.code, failure.get()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PingGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PingGenerateParseTest.java deleted file mode 100644 index 3ba077f7f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PingGenerateParseTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.PingGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.*; - -class PingGenerateParseTest { - - @Test - void testGenerateParse() { - PingGenerator generator = new PingGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPing(PingFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - byte[] payload = new byte[8]; - new Random().nextBytes(payload); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generatePing(payload, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - } - - assertEquals(1, frames.size()); - PingFrame frame = frames.get(0); - assertArrayEquals(payload, frame.getPayload()); - assertTrue(frame.isReply()); - } - - @Test - void testGenerateParseOneByteAtATime() { - PingGenerator generator = new PingGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPing(PingFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - byte[] payload = new byte[8]; - new Random().nextBytes(payload); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generatePing(payload, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - PingFrame frame = frames.get(0); - assertArrayEquals(payload, frame.getPayload()); - assertTrue(frame.isReply()); - } - } - - @Test - void testPayloadAsLong() { - PingGenerator generator = new PingGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPing(PingFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - PingFrame ping = new PingFrame(System.nanoTime(), true); - FrameBytes frameBytes = generator.generate(ping); - - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - - assertEquals(1, frames.size()); - PingFrame pong = frames.get(0); - assertEquals(ping.getPayloadAsLong(), pong.getPayloadAsLong()); - assertTrue(pong.isReply()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PriorityGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PriorityGenerateParseTest.java deleted file mode 100644 index 0acef9507..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PriorityGenerateParseTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.PriorityGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class PriorityGenerateParseTest { - - @Test - void testGenerateParse() { - PriorityGenerator generator = new PriorityGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPriority(PriorityFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int parentStreamId = 17; - int weight = 256; - boolean exclusive = true; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generatePriority(streamId, parentStreamId, weight, exclusive); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - } - - assertEquals(1, frames.size()); - PriorityFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(parentStreamId, frame.getParentStreamId()); - assertEquals(weight, frame.getWeight()); - assertEquals(exclusive, frame.isExclusive()); - } - - @Test - void testGenerateParseOneByteAtATime() { - PriorityGenerator generator = new PriorityGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPriority(PriorityFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int parentStreamId = 17; - int weight = 3; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generatePriority(streamId, parentStreamId, weight, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - PriorityFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(parentStreamId, frame.getParentStreamId()); - assertEquals(weight, frame.getWeight()); - assertTrue(frame.isExclusive()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PushPromiseGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PushPromiseGenerateParseTest.java deleted file mode 100644 index 48c1adbf2..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/PushPromiseGenerateParseTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.model.*; -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.PushPromiseGenerator; -import com.fireflysource.net.http.common.v2.hpack.HpackEncoder; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class PushPromiseGenerateParseTest { - - @Test - void testGenerateParse() { - PushPromiseGenerator generator = new PushPromiseGenerator(new HeaderGenerator(), new HpackEncoder()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPushPromise(PushPromiseFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int promisedStreamId = 17; - HttpFields fields = new HttpFields(); - fields.put("Accept", "text/html"); - fields.put("User-Agent", "Firefly"); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generatePushPromise(streamId, promisedStreamId, metaData); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - - assertEquals(1, frames.size()); - PushPromiseFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(promisedStreamId, frame.getPromisedStreamId()); - MetaData.Request request = (MetaData.Request) frame.getMetaData(); - assertEquals(metaData.getMethod(), request.getMethod()); - assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) { - HttpField field = fields.getField(j); - assertTrue(request.getFields().contains(field)); - } - } - } - - @Test - void testGenerateParseOneByteAtATime() { - PushPromiseGenerator generator = new PushPromiseGenerator(new HeaderGenerator(), new HpackEncoder()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onPushPromise(PushPromiseFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int promisedStreamId = 17; - HttpFields fields = new HttpFields(); - fields.put("Accept", "text/html"); - fields.put("User-Agent", "Firefly"); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generatePushPromise(streamId, promisedStreamId, metaData); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - PushPromiseFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(promisedStreamId, frame.getPromisedStreamId()); - MetaData.Request request = (MetaData.Request) frame.getMetaData(); - assertEquals(metaData.getMethod(), request.getMethod()); - assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) { - HttpField field = fields.getField(j); - assertTrue(request.getFields().contains(field)); - } - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/ResetGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/ResetGenerateParseTest.java deleted file mode 100644 index 80127f127..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/ResetGenerateParseTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.ResetGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class ResetGenerateParseTest { - - @Test - void testGenerateParse() { - ResetGenerator generator = new ResetGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onReset(ResetFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int error = 17; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateReset(streamId, error); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - } - - assertEquals(1, frames.size()); - ResetFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(error, frame.getError()); - } - - @Test - void testGenerateParseOneByteAtATime() { - ResetGenerator generator = new ResetGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onReset(ResetFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int error = 17; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateReset(streamId, error); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - ResetFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(error, frame.getError()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/SettingsGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/SettingsGenerateParseTest.java deleted file mode 100644 index 82dd640e6..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/SettingsGenerateParseTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.SettingsGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class SettingsGenerateParseTest { - - @Test - void testGenerateParseNoSettings() { - List frames = testGenerateParse(Collections.emptyMap()); - assertEquals(1, frames.size()); - SettingsFrame frame = frames.get(0); - assertEquals(0, frame.getSettings().size()); - assertTrue(frame.isReply()); - } - - @Test - void testGenerateParseSettings() { - Map settings1 = new HashMap<>(); - int key1 = 13; - Integer value1 = 17; - settings1.put(key1, value1); - int key2 = 19; - Integer value2 = 23; - settings1.put(key2, value2); - List frames = testGenerateParse(settings1); - assertEquals(1, frames.size()); - SettingsFrame frame = frames.get(0); - Map settings2 = frame.getSettings(); - assertEquals(2, settings2.size()); - assertEquals(value1, settings2.get(key1)); - assertEquals(value2, settings2.get(key2)); - } - - private List testGenerateParse(Map settings) { - SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); - - List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onSettings(SettingsFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateSettings(settings, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - } - - return frames; - } - - @Test - void testGenerateParseInvalidSettings() { - SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); - - AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onConnectionFailure(int error, String reason) { - errorRef.set(error); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - Map settings1 = new HashMap<>(); - settings1.put(13, 17); - FrameBytes frameBytes = generator.generateSettings(settings1, true); - // Modify the length of the frame to make it invalid - ByteBuffer bytes = frameBytes.getByteBuffers().get(0); - bytes.putShort(1, (short) (bytes.getShort(1) - 1)); - - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(ErrorCode.FRAME_SIZE_ERROR.code, errorRef.get()); - } - - @Test - void testGenerateParseOneByteAtATime() { - SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); - - List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onSettings(SettingsFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - Map settings1 = new HashMap<>(); - int key = 13; - Integer value = 17; - settings1.put(key, value); - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateSettings(settings1, true); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - SettingsFrame frame = frames.get(0); - Map settings2 = frame.getSettings(); - assertEquals(1, settings2.size()); - assertEquals(value, settings2.get(key)); - assertTrue(frame.isReply()); - } - } - - @Test - void testGenerateParseTooManyDifferentSettingsInOneFrame() { - SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); - - AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onConnectionFailure(int error, String reason) { - errorRef.set(error); - } - }, 4096, 8192); - int maxSettingsKeys = 32; - parser.setMaxSettingsKeys(maxSettingsKeys); - parser.init(UnaryOperator.identity()); - - Map settings = new HashMap<>(); - for (int i = 0; i < maxSettingsKeys + 1; ++i) - settings.put(i + 10, i); - - FrameBytes frameBytes = generator.generateSettings(settings, false); - - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) - parser.parse(buffer); - } - - assertEquals(ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, errorRef.get()); - } - - @Test - void testGenerateParseTooManySameSettingsInOneFrame() throws Exception { - int keyValueLength = 6; - int pairs = Frame.DEFAULT_MAX_LENGTH / keyValueLength; - int maxSettingsKeys = pairs / 2; - - AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(new Parser.Listener.Adapter(), 4096, 8192); - parser.setMaxSettingsKeys(maxSettingsKeys); - parser.setMaxFrameLength(Frame.DEFAULT_MAX_LENGTH); - parser.init(listener -> new Parser.Listener.Wrapper(listener) { - @Override - public void onConnectionFailure(int error, String reason) { - errorRef.set(error); - } - }); - - int length = pairs * keyValueLength; - ByteBuffer buffer = ByteBuffer.allocate(1 + 9 + length); - buffer.putInt(length); - buffer.put((byte) FrameType.SETTINGS.getType()); - buffer.put((byte) 0); // Flags. - buffer.putInt(0); // Stream ID. - // Add the same setting over and over again. - for (int i = 0; i < pairs; ++i) { - buffer.putShort((short) SettingsFrame.MAX_CONCURRENT_STREAMS); - buffer.putInt(i); - } - // Only 3 bytes for the length, skip the first. - buffer.flip().position(1); - - while (buffer.hasRemaining()) - parser.parse(buffer); - - assertEquals(ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, errorRef.get()); - } - - @Test - void testGenerateParseTooManySettingsInMultipleFrames() { - SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator()); - - AtomicInteger errorRef = new AtomicInteger(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onConnectionFailure(int error, String reason) { - errorRef.set(error); - } - }, 4096, 8192); - int maxSettingsKeys = 32; - parser.setMaxSettingsKeys(maxSettingsKeys); - parser.init(UnaryOperator.identity()); - - Map settings = new HashMap<>(); - settings.put(13, 17); - - List list = new LinkedList<>(); - for (int i = 0; i < maxSettingsKeys + 1; ++i) { - FrameBytes frameBytes = generator.generateSettings(settings, false); - list.addAll(frameBytes.getByteBuffers()); - } - - for (ByteBuffer buffer : list) { - while (buffer.hasRemaining()) - parser.parse(buffer); - } - - assertEquals(ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, errorRef.get()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/UnknownParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/UnknownParseTest.java deleted file mode 100644 index a7e4ef4a0..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/UnknownParseTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - -class UnknownParseTest { - - @Test - void testParse() { - testParse(Function.identity()); - } - - @Test - void testParseOneByteAtATime() { - testParse(buffer -> ByteBuffer.wrap(new byte[]{buffer.get()})); - } - - @Test - void testInvalidFrameSize() { - AtomicInteger failure = new AtomicInteger(); - Parser parser = new Parser(new Parser.Listener.Adapter(), 4096, 8192); - parser.init(listener -> new Parser.Listener.Wrapper(listener) { - @Override - public void onConnectionFailure(int error, String reason) { - failure.set(error); - } - }); - parser.setMaxFrameLength(Frame.DEFAULT_MAX_LENGTH); - - // 0x4001 == 16385 which is > Frame.DEFAULT_MAX_LENGTH. - byte[] bytes = new byte[]{0, 0x40, 0x01, 64, 0, 0, 0, 0, 0}; - ByteBuffer buffer = ByteBuffer.wrap(bytes); - while (buffer.hasRemaining()) - parser.parse(buffer); - - assertEquals(ErrorCode.FRAME_SIZE_ERROR.code, failure.get()); - } - - private void testParse(Function fn) { - AtomicBoolean failure = new AtomicBoolean(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onConnectionFailure(int error, String reason) { - failure.set(true); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - // Iterate a few times to be sure the parser is properly reset. - for (int i = 0; i < 2; ++i) { - byte[] bytes = new byte[]{0, 0, 4, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - ByteBuffer buffer = ByteBuffer.wrap(bytes); - while (buffer.hasRemaining()) - parser.parse(fn.apply(buffer)); - } - - assertFalse(failure.get()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/WindowUpdateGenerateParseTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/WindowUpdateGenerateParseTest.java deleted file mode 100644 index 607dd3bdf..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/frame/WindowUpdateGenerateParseTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.fireflysource.net.http.common.v2.frame; - -import com.fireflysource.net.http.common.v2.decoder.Parser; -import com.fireflysource.net.http.common.v2.encoder.FrameBytes; -import com.fireflysource.net.http.common.v2.encoder.HeaderGenerator; -import com.fireflysource.net.http.common.v2.encoder.WindowUpdateGenerator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.function.UnaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class WindowUpdateGenerateParseTest { - - @Test - void testGenerateParse() { - WindowUpdateGenerator generator = new WindowUpdateGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onWindowUpdate(WindowUpdateFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int windowUpdate = 17; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateWindowUpdate(streamId, windowUpdate); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(buffer); - } - } - } - - assertEquals(1, frames.size()); - WindowUpdateFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(windowUpdate, frame.getWindowDelta()); - } - - @Test - public void testGenerateParseOneByteAtATime() { - WindowUpdateGenerator generator = new WindowUpdateGenerator(new HeaderGenerator()); - - final List frames = new ArrayList<>(); - Parser parser = new Parser(new Parser.Listener.Adapter() { - @Override - public void onWindowUpdate(WindowUpdateFrame frame) { - frames.add(frame); - } - }, 4096, 8192); - parser.init(UnaryOperator.identity()); - - int streamId = 13; - int windowUpdate = 17; - - // Iterate a few times to be sure generator and parser are properly reset. - for (int i = 0; i < 2; ++i) { - FrameBytes frameBytes = generator.generateWindowUpdate(streamId, windowUpdate); - - frames.clear(); - for (ByteBuffer buffer : frameBytes.getByteBuffers()) { - while (buffer.hasRemaining()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } - - assertEquals(1, frames.size()); - WindowUpdateFrame frame = frames.get(0); - assertEquals(streamId, frame.getStreamId()); - assertEquals(windowUpdate, frame.getWindowDelta()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackContextTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackContextTest.java deleted file mode 100644 index 4e2b4d0c5..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackContextTest.java +++ /dev/null @@ -1,395 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - - -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.v2.hpack.HpackContext.Entry; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.*; - - -class HpackContextTest { - - @Test - void testStaticName() { - HpackContext ctx = new HpackContext(4096); - Entry entry = ctx.get(":method"); - assertEquals(":method", entry.getHttpField().getName()); - assertTrue(entry.isStatic()); - assertTrue(entry.toString().startsWith("{S,2,:method: ")); - } - - @Test - void testEmptyAdd() { - HpackContext ctx = new HpackContext(0); - HttpField field = new HttpField("foo", "bar"); - assertNull(ctx.add(field)); - } - - @Test - void testTooBigAdd() { - HpackContext ctx = new HpackContext(37); - HttpField field = new HttpField("foo", "bar"); - assertNull(ctx.add(field)); - } - - @Test - void testJustRight() { - HpackContext ctx = new HpackContext(38); - HttpField field = new HttpField("foo", "bar"); - Entry entry = ctx.add(field); - assertNotNull(entry); - assertTrue(entry.toString().startsWith("{D,0,foo: bar,")); - } - - @Test - void testEvictOne() { - HpackContext ctx = new HpackContext(38); - HttpField field0 = new HttpField("foo", "bar"); - - assertEquals(field0, ctx.add(field0).getHttpField()); - assertEquals(field0, ctx.get("foo").getHttpField()); - - HttpField field1 = new HttpField("xxx", "yyy"); - assertEquals(field1, ctx.add(field1).getHttpField()); - - assertNull(ctx.get(field0)); - assertNull(ctx.get("foo")); - assertEquals(field1, ctx.get(field1).getHttpField()); - assertEquals(field1, ctx.get("xxx").getHttpField()); - - } - - @Test - void testEvictNames() { - HpackContext ctx = new HpackContext(38 * 2); - HttpField[] field = - { - new HttpField("name", "v0"), - new HttpField("name", "v1"), - new HttpField("name", "v2"), - new HttpField("name", "v3"), - new HttpField("name", "v4"), - new HttpField("name", "v5"), - }; - - Entry[] entry = new Entry[field.length]; - - // Add 2 name entries to fill table - for (int i = 0; i <= 1; i++) - entry[i] = ctx.add(field[i]); - - // check there is a name reference and it is the most recent added - assertEquals(entry[1], ctx.get("name")); - - // Add 1 other entry to table and evict 1 - ctx.add(new HttpField("xxx", "yyy")); - - // check the name reference has been not been evicted - assertEquals(entry[1], ctx.get("name")); - - // Add 1 other entry to table and evict 1 - ctx.add(new HttpField("foo", "bar")); - - // name is evicted - assertNull(ctx.get("name")); - } - - @Test - void testGetAddStatic() { - HpackContext ctx = new HpackContext(4096); - - // Look for the field. Should find static version. - HttpField methodGet = new HttpField(":method", "GET"); - assertEquals(methodGet, ctx.get(methodGet).getHttpField()); - assertTrue(ctx.get(methodGet).isStatic()); - - // Add static version to dynamic table - Entry e0 = ctx.add(ctx.get(methodGet).getHttpField()); - - // Look again and should see dynamic version - assertEquals(methodGet, ctx.get(methodGet).getHttpField()); - assertNotSame(methodGet, ctx.get(methodGet).getHttpField()); - assertFalse(ctx.get(methodGet).isStatic()); - - // Duplicates allows - Entry e1 = ctx.add(ctx.get(methodGet).getHttpField()); - - // Look again and should see dynamic version - assertEquals(methodGet, ctx.get(methodGet).getHttpField()); - assertNotSame(methodGet, ctx.get(methodGet).getHttpField()); - assertFalse(ctx.get(methodGet).isStatic()); - assertNotSame(e0, e1); - } - - @Test - void testGetAddStaticName() { - HpackContext ctx = new HpackContext(4096); - HttpField methodOther = new HttpField(":method", "OTHER"); - - // Look for the field by name. Should find static version. - assertEquals(":method", ctx.get(":method").getHttpField().getName()); - assertTrue(ctx.get(":method").isStatic()); - - // Add dynamic entry with method - ctx.add(methodOther); - - // Look for the field by name. Should find static version. - assertEquals(":method", ctx.get(":method").getHttpField().getName()); - assertTrue(ctx.get(":method").isStatic()); - } - - @Test - void testIndexes() { - // Only enough space for 5 entries - HpackContext ctx = new HpackContext(38 * 5); - - HttpField methodPost = new HttpField(":method", "POST"); - HttpField[] field = { - new HttpField("fo0", "b0r"), - new HttpField("fo1", "b1r"), - new HttpField("fo2", "b2r"), - new HttpField("fo3", "b3r"), - new HttpField("fo4", "b4r"), - new HttpField("fo5", "b5r"), - new HttpField("fo6", "b6r"), - new HttpField("fo7", "b7r"), - new HttpField("fo8", "b8r"), - new HttpField("fo9", "b9r"), - new HttpField("foA", "bAr"), - }; - - Entry[] entry = new Entry[100]; - - // Lookup the index of a static field - assertEquals(0, ctx.size()); - assertEquals(":authority", ctx.get(1).getHttpField().getName()); - assertEquals(3, ctx.index(ctx.get(methodPost))); - assertEquals(methodPost, ctx.get(3).getHttpField()); - assertEquals("www-authenticate", ctx.get(61).getHttpField().getName()); - assertNull(ctx.get(62)); - - // Add a single entry - entry[0] = ctx.add(field[0]); - - // Check new entry is 62 - assertEquals(1, ctx.size()); - assertEquals(62, ctx.index(entry[0])); - assertEquals(entry[0], ctx.get(62)); - - // and statics still OK - assertEquals(":authority", ctx.get(1).getHttpField().getName()); - assertEquals(3, ctx.index(ctx.get(methodPost))); - assertEquals(methodPost, ctx.get(3).getHttpField()); - assertEquals("www-authenticate", ctx.get(61).getHttpField().getName()); - assertNull(ctx.get(62 + ctx.size())); - - - // Add 4 more entries - for (int i = 1; i <= 4; i++) - entry[i] = ctx.add(field[i]); - - // Check newest entry is at 62 oldest at 66 - assertEquals(5, ctx.size()); - int index = 66; - for (int i = 0; i <= 4; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - // and statics still OK - assertEquals(":authority", ctx.get(1).getHttpField().getName()); - assertEquals(3, ctx.index(ctx.get(methodPost))); - assertEquals(methodPost, ctx.get(3).getHttpField()); - assertEquals("www-authenticate", ctx.get(61).getHttpField().getName()); - assertNull(ctx.get(62 + ctx.size())); - - // add 1 more entry and this should cause an eviction! - entry[5] = ctx.add(field[5]); - - // Check newest entry is at 1 oldest at 5 - index = 66; - for (int i = 1; i <= 5; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - // check entry 0 evicted - assertNull(ctx.get(field[0])); - assertEquals(0, ctx.index(entry[0])); - - // and statics still OK - assertEquals(":authority", ctx.get(1).getHttpField().getName()); - assertEquals(3, ctx.index(ctx.get(methodPost))); - assertEquals(methodPost, ctx.get(3).getHttpField()); - assertEquals("www-authenticate", ctx.get(61).getHttpField().getName()); - assertNull(ctx.get(62 + ctx.size())); - - // Add 4 more entries - for (int i = 6; i <= 9; i++) - entry[i] = ctx.add(field[i]); - - // Check newest entry is at 1 oldest at 5 - index = 66; - for (int i = 5; i <= 9; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - // check entry 0-4 evicted - for (int i = 0; i <= 4; i++) { - assertNull(ctx.get(field[i])); - assertEquals(0, ctx.index(entry[i])); - } - - - // Add new entries enough so that array queue will wrap - for (int i = 10; i <= 52; i++) - entry[i] = ctx.add(new HttpField("n" + i, "v" + i)); - - index = 66; - for (int i = 48; i <= 52; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - } - - - @Test - void testResize() { - // Only enough space for 5 entries - HpackContext ctx = new HpackContext(38 * 5); - - HttpField[] field = { - new HttpField("fo0", "b0r"), - new HttpField("fo1", "b1r"), - new HttpField("fo2", "b2r"), - new HttpField("fo3", "b3r"), - new HttpField("fo4", "b4r"), - new HttpField("fo5", "b5r"), - new HttpField("fo6", "b6r"), - new HttpField("fo7", "b7r"), - new HttpField("fo8", "b8r"), - new HttpField("fo9", "b9r"), - new HttpField("foA", "bAr"), - }; - Entry[] entry = new Entry[field.length]; - - // Add 5 entries - for (int i = 0; i <= 4; i++) - entry[i] = ctx.add(field[i]); - - assertEquals(5, ctx.size()); - - // check indexes - int index = 66; - for (int i = 0; i <= 4; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - // resize so that only 2 entries may be held - ctx.resize(38 * 2); - assertEquals(2, ctx.size()); - - // check indexes - index = 63; - for (int i = 3; i <= 4; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - // resize so that 6.5 entries may be held - ctx.resize(38 * 6 + 19); - assertEquals(2, ctx.size()); - - // check indexes - index = 63; - for (int i = 3; i <= 4; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - - // Add 5 entries - for (int i = 5; i <= 9; i++) - entry[i] = ctx.add(field[i]); - - assertEquals(6, ctx.size()); - - // check indexes - index = 67; - for (int i = 4; i <= 9; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - - // resize so that only 100 entries may be held - ctx.resize(38 * 100); - assertEquals(6, ctx.size()); - // check indexes - index = 67; - for (int i = 4; i <= 9; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - // add 50 fields - for (int i = 0; i < 50; i++) - ctx.add(new HttpField("n" + i, "v" + i)); - - // check indexes - index = 67 + 50; - for (int i = 4; i <= 9; i++) { - assertEquals(index, ctx.index(entry[i])); - assertEquals(entry[i], ctx.get(index)); - index--; - } - - - } - - @Test - void testStaticHuffmanValues() throws Exception { - HpackContext ctx = new HpackContext(4096); - for (int i = 2; i <= 14; i++) { - Entry entry = ctx.get(i); - assertTrue(entry.isStatic()); - - ByteBuffer buffer = ByteBuffer.wrap(entry.getStaticHuffmanValue()); - int huff = 0xff & buffer.get(); - assertEquals(0x80, (0x80 & huff)); - - int len = NBitInteger.decode(buffer, 7); - - assertEquals(len, buffer.remaining()); - String value = Huffman.decode(buffer); - - assertEquals(entry.getHttpField().getValue(), value); - - } - } - - @Test - void testNameInsensitivity() { - HpackContext ctx = new HpackContext(4096); - assertEquals("content-length", ctx.get("content-length").getHttpField().getName()); - assertEquals("content-length", ctx.get("Content-Length").getHttpField().getName()); - assertTrue(ctx.get("Content-Length").isStatic()); - assertTrue(ctx.get("Content-Type").isStatic()); - - ctx.add(new HttpField("Wibble", "Wobble")); - assertEquals("Wibble", ctx.get("wibble").getHttpField().getName()); - assertEquals("Wibble", ctx.get("Wibble").getHttpField().getName()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackDecoderTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackDecoderTest.java deleted file mode 100644 index ccf89348b..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackDecoderTest.java +++ /dev/null @@ -1,490 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - - -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpHeader; -import com.fireflysource.net.http.common.model.HttpScheme; -import com.fireflysource.net.http.common.model.MetaData; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.Iterator; - -import static com.fireflysource.net.http.common.v2.hpack.HpackException.CompressionException; -import static com.fireflysource.net.http.common.v2.hpack.HpackException.StreamException; -import static org.junit.jupiter.api.Assertions.*; - - -class HpackDecoderTest { - - @Test - void testDecodeD_3() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - // First request - String encoded = "828684410f7777772e6578616d706c652e636f6d"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - MetaData.Request request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - assertFalse(request.iterator().hasNext()); - - // Second request - encoded = "828684be58086e6f2d6361636865"; - buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - Iterator iterator = request.iterator(); - assertTrue(iterator.hasNext()); - assertEquals(new HttpField("cache-control", "no-cache"), iterator.next()); - assertFalse(iterator.hasNext()); - - // Third request - encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565"; - buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTPS.getValue(), request.getURI().getScheme()); - assertEquals("/index.html", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - iterator = request.iterator(); - assertTrue(iterator.hasNext()); - assertEquals(new HttpField("custom-key", "custom-value"), iterator.next()); - assertFalse(iterator.hasNext()); - } - - @Test - void testDecodeD_4() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - // First request - String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - MetaData.Request request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - assertFalse(request.iterator().hasNext()); - - // Second request - encoded = "828684be5886a8eb10649cbf"; - buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - Iterator iterator = request.iterator(); - assertTrue(iterator.hasNext()); - assertEquals(new HttpField("cache-control", "no-cache"), iterator.next()); - assertFalse(iterator.hasNext()); - } - - @Test - void testDecodeWithArrayOffset() throws Exception { - String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; - - HpackDecoder decoder = new HpackDecoder(4096, 8192); - String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d"; - byte[] bytes = TypeUtils.fromHexString(encoded); - byte[] array = new byte[bytes.length + 1]; - System.arraycopy(bytes, 0, array, 1, bytes.length); - ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); - - MetaData.Request request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - assertEquals(1, request.getFields().size()); - HttpField field = request.iterator().next(); - assertEquals(HttpHeader.AUTHORIZATION, field.getHeader()); - assertEquals(value, field.getValue()); - } - - @Test - void testDecodeHuffmanWithArrayOffset() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84"; - byte[] bytes = TypeUtils.fromHexString(encoded); - byte[] array = new byte[bytes.length + 1]; - System.arraycopy(bytes, 0, array, 1, bytes.length); - ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); - - MetaData.Request request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("www.example.com", request.getURI().getHost()); - assertFalse(request.iterator().hasNext()); - } - - @Test - void testNghttpx() throws Exception { - // Response encoded by nghttpx - String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - HpackDecoder decoder = new HpackDecoder(4096, 8192); - MetaData.Response response = (MetaData.Response) decoder.decode(buffer); - - assertEquals(200, response.getStatus()); - assertEquals(6, response.getFields().size()); - assertTrue(response.getFields().contains(new HttpField(HttpHeader.DATE, "Fri, 15 Jul 2016 02:36:20 GMT"))); - assertTrue(response.getFields().contains(new HttpField(HttpHeader.CONTENT_TYPE, "text/html"))); - assertTrue(response.getFields().contains(new HttpField(HttpHeader.CONTENT_ENCODING, ""))); - assertTrue(response.getFields().contains(new HttpField(HttpHeader.CONTENT_LENGTH, "42"))); - assertTrue(response.getFields().contains(new HttpField(HttpHeader.SERVER, "nghttpx nghttp2/1.12.0"))); - assertTrue(response.getFields().contains(new HttpField(HttpHeader.VIA, "1.1 nghttpx"))); - } - - @Test - void testResize() throws Exception { - String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(4096, 8192); - MetaData metaData = decoder.decode(buffer); - assertEquals("localhost0", metaData.getFields().get(HttpHeader.HOST)); - assertEquals("abcdefghij", metaData.getFields().get(HttpHeader.COOKIE)); - assertEquals(50, decoder.getHpackContext().getMaxDynamicTableSize()); - assertEquals(1, decoder.getHpackContext().size()); - - - } - - @Test - void testBadResize() throws Exception { - /* - 4. Dynamic Table Management - 4.2. Maximum Table Size - × 1: Sends a dynamic table size update at the end of header block - -> The endpoint MUST treat this as a decoding error. - Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) - Connection closed - */ - - String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - HpackDecoder decoder = new HpackDecoder(4096, 8192); - try { - decoder.decode(buffer); - fail(); - } catch (CompressionException e) { - assertTrue(e.getMessage().contains("Dynamic table resize after fields")); - } - } - - @Test - void testTooBigToIndex() throws Exception { - String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - HpackDecoder decoder = new HpackDecoder(128, 8192); - MetaData metaData = decoder.decode(buffer); - - assertEquals(0, decoder.getHpackContext().getDynamicTableSize()); - assertTrue(metaData.getFields().get("host").startsWith("This is a very large field")); - } - - @Test - void testUnknownIndex() throws Exception { - String encoded = "BE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - HpackDecoder decoder = new HpackDecoder(128, 8192); - try { - decoder.decode(buffer); - fail(); - } catch (HpackException.SessionException e) { - assertTrue(e.getMessage().startsWith("Unknown index")); - } - - } - - /* 8.1.2.1. Pseudo-Header Fields */ - @Test - void test8_1_2_1_PsuedoHeaderFields() throws Exception { - // 1:Sends a HEADERS frame that contains a unknown pseudo-header field - MetaDataBuilder mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(":unknown", "value")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Unknown pseudo header")); - } - - // 2: Sends a HEADERS frame that contains the pseudo-header field defined for response - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/path")); - mdb.emit(new HttpField(HttpHeader.C_STATUS, "100")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Request and Response headers")); - } - - // 3: Sends a HEADERS frame that contains a pseudo-header field as trailers - - // 4: Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/path")); - mdb.emit(new HttpField("Accept", "No Compromise")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Pseudo header :authority after fields")); - } - } - - @Test - void test8_1_2_2_ConnectionSpecificHeaderFields() throws Exception { - MetaDataBuilder mdb; - - // 1: Sends a HEADERS frame that contains the connection-specific header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.CONNECTION, "value")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Connection specific field 'Connection'")); - } - - // 2: Sends a HEADERS frame that contains the TE header field with any value other than "trailers" - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.TE, "not_trailers")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Unsupported TE value 'not_trailers'")); - } - - - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.CONNECTION, "TE")); - mdb.emit(new HttpField(HttpHeader.TE, "trailers")); - assertNotNull(mdb.build()); - } - - - @Test - void test8_1_2_3_RequestPseudoHeaderFields() throws Exception { - MetaDataBuilder mdb; - - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); - assertTrue(mdb.build() instanceof MetaData.Request); - - - // 1: Sends a HEADERS frame with empty ":path" pseudo-header field - mdb = new MetaDataBuilder(4096); - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("No Path")); - } - - // 2: Sends a HEADERS frame that omits ":method" pseudo-header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("No Method")); - } - - - // 3: Sends a HEADERS frame that omits ":scheme" pseudo-header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("No Scheme")); - } - - // 4: Sends a HEADERS frame that omits ":path" pseudo-header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("No Path")); - } - - // 5: Sends a HEADERS frame with duplicated ":method" pseudo-header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Duplicate")); - } - - // 6: Sends a HEADERS frame with duplicated ":scheme" pseudo-header field - mdb = new MetaDataBuilder(4096); - mdb.emit(new HttpField(HttpHeader.C_METHOD, "GET")); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_SCHEME, "http")); - mdb.emit(new HttpField(HttpHeader.C_AUTHORITY, "localhost:8080")); - mdb.emit(new HttpField(HttpHeader.C_PATH, "/")); - try { - mdb.build(); - fail(); - } catch (StreamException ex) { - assertTrue(ex.getMessage().contains("Duplicate")); - } - } - - - @Test - void testHuffmanEncodedStandard() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "82868441" + "83" + "49509F"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - MetaData.Request request = (MetaData.Request) decoder.decode(buffer); - - assertEquals("GET", request.getMethod()); - assertEquals(HttpScheme.HTTP.getValue(), request.getURI().getScheme()); - assertEquals("/", request.getURI().getPath()); - assertEquals("test", request.getURI().getHost()); - assertFalse(request.iterator().hasNext()); - } - - - /* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */ - @Test - void testHuffmanEncodedExtraPadding() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "82868441" + "84" + "49509FFF"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - try { - decoder.decode(buffer); - fail(); - } catch (CompressionException ex) { - assertTrue(ex.getMessage().contains("Bad termination")); - } - } - - - /* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */ - @Test - void testHuffmanEncodedZeroPadding() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "82868441" + "83" + "495090"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - try { - decoder.decode(buffer); - fail(); - } catch (CompressionException ex) { - assertTrue(ex.getMessage().contains("Incorrect padding")); - } - } - - - /* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */ - @Test - void testHuffmanEncodedWithEOS() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "82868441" + "87" + "497FFFFFFF427F"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - try { - decoder.decode(buffer); - fail(); - } catch (CompressionException ex) { - assertTrue(ex.getMessage().contains("EOS in content")); - } - } - - - @Test - void testHuffmanEncodedOneIncompleteOctet() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "82868441" + "81" + "FE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - try { - decoder.decode(buffer); - fail(); - } catch (CompressionException ex) { - assertTrue(ex.getMessage().contains("Bad termination")); - } - } - - - @Test - void testHuffmanEncodedTwoIncompleteOctet() throws Exception { - HpackDecoder decoder = new HpackDecoder(4096, 8192); - - String encoded = "82868441" + "82" + "FFFE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - - try { - decoder.decode(buffer); - fail(); - } catch (CompressionException ex) { - assertTrue(ex.getMessage().contains("Bad termination")); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackEncoderTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackEncoderTest.java deleted file mode 100644 index 03f41aa55..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackEncoderTest.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.http.common.model.HttpField; -import com.fireflysource.net.http.common.model.HttpFields; -import com.fireflysource.net.http.common.model.HttpVersion; -import com.fireflysource.net.http.common.model.MetaData; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -class HpackEncoderTest { - - @Test - void testUnknownFieldsContextManagement() { - HpackEncoder encoder = new HpackEncoder(38 * 5); - HttpFields fields = new HttpFields(); - - - HttpField[] field = { - new HttpField("fo0", "b0r"), - new HttpField("fo1", "b1r"), - new HttpField("fo2", "b2r"), - new HttpField("fo3", "b3r"), - new HttpField("fo4", "b4r"), - new HttpField("fo5", "b5r"), - new HttpField("fo6", "b6r"), - new HttpField("fo7", "b7r"), - new HttpField("fo8", "b8r"), - new HttpField("fo9", "b9r"), - new HttpField("foA", "bAr"), - }; - - // Add 4 entries - for (int i = 0; i <= 3; i++) { - fields.add(field[i]); - } - - // encode them - ByteBuffer buffer = ByteBuffer.allocate(4096); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // something encoded! - assertTrue(buffer.remaining() > 0); - - // All are in the dynamic table - assertEquals(4, encoder.getHpackContext().size()); - - // encode exact same fields again! - buffer.clear(); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // All are in the dynamic table - assertEquals(4, encoder.getHpackContext().size()); - - // Add 4 more fields - for (int i = 4; i <= 7; i++) - fields.add(field[i]); - - // encode - buffer.clear(); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // something encoded! - assertTrue(buffer.remaining() > 0); - - // max dynamic table size reached - assertEquals(5, encoder.getHpackContext().size()); - - - // remove some fields - for (int i = 0; i <= 7; i += 2) - fields.remove(field[i].getName()); - - // encode - buffer.clear(); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // something encoded! - assertTrue(buffer.remaining() > 0); - - // max dynamic table size reached - assertEquals(5, encoder.getHpackContext().size()); - - - // remove another fields - fields.remove(field[1].getName()); - - // encode - buffer.clear(); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - // something encoded! - assertTrue(buffer.remaining() > 0); - - // max dynamic table size reached - assertEquals(5, encoder.getHpackContext().size()); - - - // re add the field - - fields.add(field[1]); - - // encode - buffer.clear(); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // something encoded! - assertTrue(buffer.remaining() > 0); - - // max dynamic table size reached - assertEquals(5, encoder.getHpackContext().size()); - - } - - - @Test - void testNeverIndexSetCookie() { - HpackEncoder encoder = new HpackEncoder(38 * 5); - ByteBuffer buffer = ByteBuffer.allocate(4096); - - HttpFields fields = new HttpFields(); - fields.put("set-cookie", "some cookie value"); - - // encode - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // something was encoded! - assertTrue(buffer.remaining() > 0); - - // empty dynamic table - assertEquals(0, encoder.getHpackContext().size()); - - - // encode again - buffer.clear(); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - // something encoded! - assertTrue(buffer.remaining() > 0); - - // empty dynamic table - assertEquals(0, encoder.getHpackContext().size()); - - } - - - @Test - public void testFieldLargerThanTable() { - HttpFields fields = new HttpFields(); - - HpackEncoder encoder = new HpackEncoder(128); - ByteBuffer buffer0 = BufferUtils.allocate(4096); - int pos = BufferUtils.flipToFill(buffer0); - encoder.encode(buffer0, new MetaData(HttpVersion.HTTP_2, fields)); - BufferUtils.flipToFlush(buffer0, pos); - - encoder = new HpackEncoder(128); - fields.add(new HttpField("user-agent", "firefly/test")); - ByteBuffer buffer1 = BufferUtils.allocate(4096); - pos = BufferUtils.flipToFill(buffer1); - encoder.encode(buffer1, new MetaData(HttpVersion.HTTP_2, fields)); - BufferUtils.flipToFlush(buffer1, pos); - - encoder = new HpackEncoder(128); - encoder.setValidateEncoding(false); - fields.add(new HttpField(":path", - "This is a very large field, whose size is larger than the dynamic table so it should not be indexed as it will not fit in the table ever!" + - "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX " + - "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY " + - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ")); - ByteBuffer buffer2 = BufferUtils.allocate(4096); - pos = BufferUtils.flipToFill(buffer2); - encoder.encode(buffer2, new MetaData(HttpVersion.HTTP_2, fields)); - BufferUtils.flipToFlush(buffer2, pos); - - encoder = new HpackEncoder(128); - encoder.setValidateEncoding(false); - fields.add(new HttpField("host", "somehost")); - ByteBuffer buffer = BufferUtils.allocate(4096); - pos = BufferUtils.flipToFill(buffer); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - BufferUtils.flipToFlush(buffer, pos); - - //System.err.println(BufferUtils.toHexString(buffer0)); - //System.err.println(BufferUtils.toHexString(buffer1)); - //System.err.println(BufferUtils.toHexString(buffer2)); - //System.err.println(BufferUtils.toHexString(buffer)); - - // something encoded! - assertTrue(buffer.remaining() > 0); - - // check first field is static index name and dynamic index body - assertEquals(1, (buffer.get(buffer0.remaining()) & 0xFF) >> 6); - - // check first field is static index name and literal body - assertEquals(0, (buffer.get(buffer1.remaining()) & 0xFF) >> 4); - - // check first field is static index name and dynamic index body - assertEquals(1, (buffer.get(buffer2.remaining()) & 0xFF) >> 6); - - // Only first and third fields are put in the table - HpackContext context = encoder.getHpackContext(); - assertEquals(2, context.size()); - assertEquals("host", context.get(HpackContext.STATIC_SIZE + 1).getHttpField().getName()); - assertEquals("user-agent", context.get(HpackContext.STATIC_SIZE + 2).getHttpField().getName()); - assertEquals(context.get(HpackContext.STATIC_SIZE + 1).getSize() + context.get(HpackContext.STATIC_SIZE + 2).getSize(), context.getDynamicTableSize()); - } - - @Test - void testResize() { - HttpFields fields = new HttpFields(); - fields.add("host", "localhost0"); - fields.add("cookie", "abcdefghij"); - - HpackEncoder encoder = new HpackEncoder(4096); - - ByteBuffer buffer = ByteBuffer.allocate(4096); - encoder.encodeMaxDynamicTableSize(buffer, 0); - encoder.setRemoteMaxDynamicTableSize(50); - encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, fields)); - buffer.flip(); - - HpackContext context = encoder.getHpackContext(); - - assertEquals(50, context.getMaxDynamicTableSize()); - assertEquals(1, context.size()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackTest.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackTest.java deleted file mode 100644 index 4354db2fc..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/HpackTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.net.http.common.codec.DateGenerator; -import com.fireflysource.net.http.common.codec.PreEncodedHttpField; -import com.fireflysource.net.http.common.model.*; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.concurrent.TimeUnit; - -import static com.fireflysource.net.http.common.model.MetaData.Response; -import static org.junit.jupiter.api.Assertions.*; - - -class HpackTest { - - final static HttpField ServerFirefly = new PreEncodedHttpField(HttpHeader.SERVER, "firefly"); - final static HttpField XPowerFirefly = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, "firefly"); - final static HttpField Date = new PreEncodedHttpField(HttpHeader.DATE, DateGenerator.formatDate(TimeUnit.NANOSECONDS.toMillis(System.nanoTime()))); - - @Test - void encodeDecodeResponseTest() { - HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 8192); - ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024); - - HttpFields fields0 = new HttpFields(); - fields0.add(HttpHeader.CONTENT_TYPE, "text/html"); - fields0.add(HttpHeader.CONTENT_LENGTH, "1024"); - fields0.add(new HttpField(HttpHeader.CONTENT_ENCODING, (String) null)); - fields0.add(ServerFirefly); - fields0.add(XPowerFirefly); - fields0.add(Date); - fields0.add(HttpHeader.SET_COOKIE, "abcdefghijklmnopqrstuvwxyz"); - fields0.add("custom-key", "custom-value"); - MetaData.Response original0 = new MetaData.Response(HttpVersion.HTTP_2, 200, fields0); - - buffer.clear(); - encoder.encode(buffer, original0); - buffer.flip(); - Response decoded0 = (Response) decoder.decode(buffer); - original0.getFields().put(new HttpField(HttpHeader.CONTENT_ENCODING, "")); - assertMetadataSame(original0, decoded0); - - // Same again? - buffer.clear(); - encoder.encode(buffer, original0); - buffer.flip(); - Response decoded0b = (Response) decoder.decode(buffer); - - assertMetadataSame(original0, decoded0b); - - HttpFields fields1 = new HttpFields(); - fields1.add(HttpHeader.CONTENT_TYPE, "text/plain"); - fields1.add(HttpHeader.CONTENT_LENGTH, "1234"); - fields1.add(HttpHeader.CONTENT_ENCODING, " "); - fields1.add(ServerFirefly); - fields1.add(XPowerFirefly); - fields1.add(Date); - fields1.add("Custom-Key", "Other-Value"); - Response original1 = new MetaData.Response(HttpVersion.HTTP_2, 200, fields1); - - // Same again? - buffer.clear(); - encoder.encode(buffer, original1); - buffer.flip(); - Response decoded1 = (Response) decoder.decode(buffer); - - assertMetadataSame(original1, decoded1); - assertEquals("custom-key", decoded1.getFields().getField("Custom-Key").getName()); - } - - @Test - void encodeDecodeTooLargeTest() { - HpackEncoder encoder = new HpackEncoder(); - HpackDecoder decoder = new HpackDecoder(4096, 164); - ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024); - - HttpFields fields0 = new HttpFields(); - fields0.add("1234567890", "1234567890123456789012345678901234567890"); - fields0.add("Cookie", "abcdeffhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); - MetaData original0 = new MetaData(HttpVersion.HTTP_2, fields0); - - buffer.clear(); - encoder.encode(buffer, original0); - buffer.flip(); - MetaData decoded0 = (MetaData) decoder.decode(buffer); - - assertMetadataSame(original0, decoded0); - - HttpFields fields1 = new HttpFields(); - fields1.add("1234567890", "1234567890123456789012345678901234567890"); - fields1.add("Cookie", "abcdeffhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); - fields1.add("x", "y"); - MetaData original1 = new MetaData(HttpVersion.HTTP_2, fields1); - - buffer.clear(); - encoder.encode(buffer, original1); - buffer.flip(); - try { - decoder.decode(buffer); - fail(); - } catch (HpackException.SessionException e) { - assertTrue(e.getMessage().contains("Header too large")); - } - } - - @Test - void evictReferencedFieldTest() { - HpackEncoder encoder = new HpackEncoder(200, 200); - HpackDecoder decoder = new HpackDecoder(200, 1024); - ByteBuffer buffer = ByteBuffer.allocate(16 * 1024); - - HttpFields fields0 = new HttpFields(); - fields0.add("123456789012345678901234567890123456788901234567890", "value"); - fields0.add("foo", "abcdeffhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"); - MetaData original0 = new MetaData(HttpVersion.HTTP_2, fields0); - - buffer.clear(); - encoder.encode(buffer, original0); - buffer.flip(); - MetaData decoded0 = (MetaData) decoder.decode(buffer); - - assertEquals(2, encoder.getHpackContext().size()); - assertEquals(2, decoder.getHpackContext().size()); - assertEquals("123456789012345678901234567890123456788901234567890", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 1).getHttpField().getName()); - assertEquals("foo", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 0).getHttpField().getName()); - - assertMetadataSame(original0, decoded0); - - HttpFields fields1 = new HttpFields(); - fields1.add("123456789012345678901234567890123456788901234567890", "other_value"); - fields1.add("x", "y"); - MetaData original1 = new MetaData(HttpVersion.HTTP_2, fields1); - - buffer.clear(); - encoder.encode(buffer, original1); - buffer.flip(); - MetaData decoded1 = (MetaData) decoder.decode(buffer); - assertMetadataSame(original1, decoded1); - - assertEquals(2, encoder.getHpackContext().size()); - assertEquals(2, decoder.getHpackContext().size()); - assertEquals("x", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length).getHttpField().getName()); - assertEquals("foo", encoder.getHpackContext().get(HpackContext.STATIC_TABLE.length + 1).getHttpField().getName()); - } - - private void assertMetadataSame(MetaData.Response expected, MetaData.Response actual) { - assertEquals(expected.getStatus(), actual.getStatus()); - assertEquals(expected.getReason(), actual.getReason()); - assertMetadataSame((MetaData) expected, (MetaData) actual); - } - - private void assertMetadataSame(MetaData expected, MetaData actual) { - assertEquals(expected.getContentLength(), actual.getContentLength()); - assertEquals(expected.getHttpVersion(), actual.getHttpVersion()); - assertHttpFieldsSame("Metadata.fields", expected.getFields(), actual.getFields()); - } - - private void assertHttpFieldsSame(String message, HttpFields expected, HttpFields actual) { - assertEquals(expected.size(), actual.size(), message); - - for (HttpField actualField : actual) { - if ("DATE".equalsIgnoreCase(actualField.getName())) { - // skip comparison on Date, as these values can often differ by 1 second - // during testing. - continue; - } - assertTrue(expected.contains(actualField), message + ".contains(" + actualField + ")"); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/TestHuffman.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/TestHuffman.java deleted file mode 100644 index dd719ceaf..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/TestHuffman.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.object.TypeUtils; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.util.Locale; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class TestHuffman { - - static Stream data() { - return Stream.of( - new String[][]{ - {"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"}, - {"D.4.2", "a8eb10649cbf", "no-cache"}, - {"D.6.1k", "6402", "302"}, - {"D.6.1v", "aec3771a4b", "private"}, - {"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"}, - {"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"}, - {"D.6.2te", "640cff", "303"}, - }).map(Arguments::of); - } - - @ParameterizedTest(name = "[{index}] spec={0}") - @MethodSource("data") - void testDecode(String specSection, String hex, String expected) throws Exception { - byte[] encoded = TypeUtils.fromHexString(hex); - String decoded = Huffman.decode(ByteBuffer.wrap(encoded)); - assertEquals(expected, decoded, specSection); - } - - @ParameterizedTest(name = "[{index}] spec={0}") - @MethodSource("data") - void testEncode(String specSection, String hex, String expected) { - ByteBuffer buf = ByteBuffer.allocate(1024); - Huffman.encode(buf, expected); - buf.flip(); - String encoded = TypeUtils.toHexString(BufferUtils.toArray(buf)).toLowerCase(Locale.ENGLISH); - assertEquals(hex, encoded, specSection); - assertEquals(hex.length() / 2, Huffman.octetsNeeded(expected)); - } - - @ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name - @ValueSource(chars = {(char) 128, (char) 0, (char) -1, ' ' - 1}) - void testEncode8859Only(char bad) { - String s = "bad '" + bad + "'"; - - assertEquals(-1, Huffman.octetsNeeded(s)); - assertThrows(BufferOverflowException.class, () -> Huffman.encode(BufferUtils.allocate(32), s)); - } - - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/TestNBitInteger.java b/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/TestNBitInteger.java deleted file mode 100644 index 6e4f29cf7..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/http/common/v2/hpack/TestNBitInteger.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.fireflysource.net.http.common.v2.hpack; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.object.TypeUtils; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -class TestNBitInteger { - - @Test - void testOctetsNeeded() { - assertEquals(0, NBitInteger.octectsNeeded(5, 10)); - assertEquals(2, NBitInteger.octectsNeeded(5, 1337)); - assertEquals(1, NBitInteger.octectsNeeded(8, 42)); - assertEquals(3, NBitInteger.octectsNeeded(8, 1337)); - - assertEquals(0, NBitInteger.octectsNeeded(6, 62)); - assertEquals(1, NBitInteger.octectsNeeded(6, 63)); - assertEquals(1, NBitInteger.octectsNeeded(6, 64)); - assertEquals(2, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x01)); - assertEquals(3, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80)); - assertEquals(4, NBitInteger.octectsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80)); - } - - @Test - void testEncode() { - testEncode(6, 0, "00"); - testEncode(6, 1, "01"); - testEncode(6, 62, "3e"); - testEncode(6, 63, "3f00"); - testEncode(6, 63 + 1, "3f01"); - testEncode(6, 63 + 0x7e, "3f7e"); - testEncode(6, 63 + 0x7f, "3f7f"); - testEncode(6, 63 + 0x00 + 0x80 * 0x01, "3f8001"); - testEncode(6, 63 + 0x01 + 0x80 * 0x01, "3f8101"); - testEncode(6, 63 + 0x7f + 0x80 * 0x01, "3fFf01"); - testEncode(6, 63 + 0x00 + 0x80 * 0x02, "3f8002"); - testEncode(6, 63 + 0x01 + 0x80 * 0x02, "3f8102"); - testEncode(6, 63 + 0x7f + 0x80 * 0x7f, "3fFf7f"); - testEncode(6, 63 + 0x00 + 0x80 * 0x80, "3f808001"); - testEncode(6, 63 + 0x7f + 0x80 * 0x80 * 0x7f, "3fFf807f"); - testEncode(6, 63 + 0x00 + 0x80 * 0x80 * 0x80, "3f80808001"); - - testEncode(8, 0, "00"); - testEncode(8, 1, "01"); - testEncode(8, 128, "80"); - testEncode(8, 254, "Fe"); - testEncode(8, 255, "Ff00"); - testEncode(8, 255 + 1, "Ff01"); - testEncode(8, 255 + 0x7e, "Ff7e"); - testEncode(8, 255 + 0x7f, "Ff7f"); - testEncode(8, 255 + 0x80, "Ff8001"); - testEncode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001"); - } - - void testEncode(int n, int i, String expected) { - ByteBuffer buf = ByteBuffer.allocate(16); - if (n < 8) - buf.put((byte) 0x00); - NBitInteger.encode(buf, n, i); - buf.flip(); - String r = TypeUtils.toHexString(BufferUtils.toArray(buf)); - assertEquals(expected, r); - - assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitInteger.octectsNeeded(n, i)); - } - - @Test - void testDecode() { - testDecode(6, 0, "00"); - testDecode(6, 1, "01"); - testDecode(6, 62, "3e"); - testDecode(6, 63, "3f00"); - testDecode(6, 63 + 1, "3f01"); - testDecode(6, 63 + 0x7e, "3f7e"); - testDecode(6, 63 + 0x7f, "3f7f"); - testDecode(6, 63 + 0x80, "3f8001"); - testDecode(6, 63 + 0x81, "3f8101"); - testDecode(6, 63 + 0x7f + 0x80 * 0x01, "3fFf01"); - testDecode(6, 63 + 0x00 + 0x80 * 0x02, "3f8002"); - testDecode(6, 63 + 0x01 + 0x80 * 0x02, "3f8102"); - testDecode(6, 63 + 0x7f + 0x80 * 0x7f, "3fFf7f"); - testDecode(6, 63 + 0x00 + 0x80 * 0x80, "3f808001"); - testDecode(6, 63 + 0x7f + 0x80 * 0x80 * 0x7f, "3fFf807f"); - testDecode(6, 63 + 0x00 + 0x80 * 0x80 * 0x80, "3f80808001"); - - testDecode(8, 0, "00"); - testDecode(8, 1, "01"); - testDecode(8, 128, "80"); - testDecode(8, 254, "Fe"); - testDecode(8, 255, "Ff00"); - testDecode(8, 255 + 1, "Ff01"); - testDecode(8, 255 + 0x7e, "Ff7e"); - testDecode(8, 255 + 0x7f, "Ff7f"); - testDecode(8, 255 + 0x80, "Ff8001"); - testDecode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001"); - } - - void testDecode(int n, int expected, String encoded) { - ByteBuffer buf = ByteBuffer.wrap(TypeUtils.fromHexString(encoded)); - buf.position(n == 8 ? 0 : 1); - assertEquals(expected, NBitInteger.decode(buf, n)); - } - - @Test - void testEncodeExampleD_1_1() { - ByteBuffer buf = ByteBuffer.allocate(16); - buf.put((byte) 0x77); - buf.put((byte) 0xFF); - NBitInteger.encode(buf, 5, 10); - buf.flip(); - - String r = TypeUtils.toHexString(BufferUtils.toArray(buf)); - - assertEquals("77Ea", r); - - } - - @Test - void testDecodeExampleD_1_1() { - ByteBuffer buf = ByteBuffer.wrap(TypeUtils.fromHexString("77EaFF")); - buf.position(2); - - assertEquals(10, NBitInteger.decode(buf, 5)); - } - - @Test - void testEncodeExampleD_1_2() { - ByteBuffer buf = ByteBuffer.allocate(16); - - buf.put((byte) 0x88); - buf.put((byte) 0x00); - NBitInteger.encode(buf, 5, 1337); - buf.flip(); - - String r = TypeUtils.toHexString(BufferUtils.toArray(buf)); - - assertEquals("881f9a0a", r); - - } - - @Test - void testDecodeExampleD_1_2() { - ByteBuffer buf = ByteBuffer.wrap(TypeUtils.fromHexString("881f9a0aff")); - buf.position(2); - - assertEquals(1337, NBitInteger.decode(buf, 5)); - } - - @Test - void testEncodeExampleD_1_3() { - ByteBuffer buf = ByteBuffer.allocate(16); - buf.put((byte) 0x88); - buf.put((byte) 0xFF); - NBitInteger.encode(buf, 8, 42); - buf.flip(); - - String r = TypeUtils.toHexString(BufferUtils.toArray(buf)); - - assertEquals("88Ff2a", r); - - } - - @Test - void testDecodeExampleD_1_3() { - ByteBuffer buf = ByteBuffer.wrap(TypeUtils.fromHexString("882aFf")); - buf.position(1); - - assertEquals(42, NBitInteger.decode(buf, 8)); - } - -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/ClosePayloadParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/ClosePayloadParserTest.java deleted file mode 100644 index aff1c973d..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/ClosePayloadParserTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - - -import com.fireflysource.net.websocket.common.model.*; -import com.fireflysource.net.websocket.common.utils.MaskedByteBuffer; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -public class ClosePayloadParserTest { - @Test - public void testGameOver() { - String expectedReason = "Game Over"; - - byte[] utf = expectedReason.getBytes(StandardCharsets.UTF_8); - ByteBuffer payload = ByteBuffer.allocate(utf.length + 2); - payload.putChar((char) StatusCode.NORMAL); - payload.put(utf, 0, utf.length); - payload.flip(); - - ByteBuffer buf = ByteBuffer.allocate(24); - buf.put((byte) (0x80 | OpCode.CLOSE)); // fin + close - buf.put((byte) (0x80 | payload.remaining())); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, payload); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.CLOSE, 1); - CloseInfo close = new CloseInfo(capture.getFrames().poll()); - assertEquals(StatusCode.NORMAL, close.getStatusCode()); - assertEquals(expectedReason, close.getReason()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/ParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/ParserTest.java deleted file mode 100644 index 7947821f5..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/ParserTest.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.encoder.UnitGenerator; -import com.fireflysource.net.websocket.common.exception.ProtocolException; -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.model.*; -import com.fireflysource.net.websocket.common.utils.Hex; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -public class ParserTest { - /** - * Similar to the server side 5.15 testcase. A normal 2 fragment text text message, followed by another continuation. - */ - @Test - public void testParseCase515() { - List send = new ArrayList<>(); - send.add(new TextFrame().setPayload("fragment1").setFin(false)); - send.add(new ContinuationFrame().setPayload("fragment2").setFin(true)); - send.add(new ContinuationFrame().setPayload("fragment3").setFin(false)); // bad frame - send.add(new TextFrame().setPayload("fragment4").setFin(true)); - send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - - ByteBuffer completeBuf = UnitGenerator.generate(send); - UnitParser parser = new UnitParser(); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - ProtocolException x = assertThrows(ProtocolException.class, () -> parser.parseQuietly(completeBuf)); - assertTrue(x.getMessage().contains("CONTINUATION frame without prior !FIN")); - } - - /** - * Similar to the server side 5.18 testcase. Text message fragmented as 2 frames, both as opcode=TEXT - */ - @Test - public void testParseCase518() { - List send = new ArrayList<>(); - send.add(new TextFrame().setPayload("fragment1").setFin(false)); - send.add(new TextFrame().setPayload("fragment2").setFin(true)); // bad frame, must be continuation - send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - - ByteBuffer completeBuf = UnitGenerator.generate(send); - UnitParser parser = new UnitParser(); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - ProtocolException x = assertThrows(ProtocolException.class, () -> parser.parseQuietly(completeBuf)); - assertTrue(x.getMessage().contains("Unexpected TEXT frame")); - } - - /** - * Similar to the server side 5.19 testcase. text message, send in 5 frames/fragments, with 2 pings in the mix. - */ - @Test - public void testParseCase519() { - List send = new ArrayList<>(); - send.add(new TextFrame().setPayload("f1").setFin(false)); - send.add(new ContinuationFrame().setPayload(",f2").setFin(false)); - send.add(new PingFrame().setPayload("pong-1")); - send.add(new ContinuationFrame().setPayload(",f3").setFin(false)); - send.add(new ContinuationFrame().setPayload(",f4").setFin(false)); - send.add(new PingFrame().setPayload("pong-2")); - send.add(new ContinuationFrame().setPayload(",f5").setFin(true)); - send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - - ByteBuffer completeBuf = UnitGenerator.generate(send); - UnitParser parser = new UnitParser(); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parseQuietly(completeBuf); - - capture.assertHasFrame(OpCode.TEXT, 1); - capture.assertHasFrame(OpCode.CONTINUATION, 4); - capture.assertHasFrame(OpCode.CLOSE, 1); - capture.assertHasFrame(OpCode.PING, 2); - } - - /** - * Similar to the server side 5.6 testcase. pong, then text, then close frames. - */ - @Test - public void testParseCase56() { - List send = new ArrayList<>(); - send.add(new PongFrame().setPayload("ping")); - send.add(new TextFrame().setPayload("hello, world")); - send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - - ByteBuffer completeBuf = UnitGenerator.generate(send); - UnitParser parser = new UnitParser(); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(completeBuf); - - capture.assertHasFrame(OpCode.TEXT, 1); - capture.assertHasFrame(OpCode.CLOSE, 1); - capture.assertHasFrame(OpCode.PONG, 1); - } - - /** - * Similar to the server side 6.2.3 testcase. Lots of small 1 byte UTF8 Text frames, representing 1 overall text message. - */ - @Test - public void testParseCase623() { - String utf8 = "Hello-\uC2B5@\uC39F\uC3A4\uC3BC\uC3A0\uC3A1-UTF-8!!"; - byte[] msg = StringUtils.getUtf8Bytes(utf8); - - List send = new ArrayList<>(); - int textCount = 0; - int continuationCount = 0; - int len = msg.length; - boolean continuation = false; - byte[] mini; - for (int i = 0; i < len; i++) { - DataFrame frame = null; - if (continuation) { - frame = new ContinuationFrame(); - continuationCount++; - } else { - frame = new TextFrame(); - textCount++; - } - mini = new byte[1]; - mini[0] = msg[i]; - frame.setPayload(ByteBuffer.wrap(mini)); - boolean isLast = (i >= (len - 1)); - frame.setFin(isLast); - send.add(frame); - continuation = true; - } - send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - - ByteBuffer completeBuf = UnitGenerator.generate(send); - UnitParser parser = new UnitParser(); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(completeBuf); - - capture.assertHasFrame(OpCode.TEXT, textCount); - capture.assertHasFrame(OpCode.CONTINUATION, continuationCount); - capture.assertHasFrame(OpCode.CLOSE, 1); - } - - @Test - public void testParseNothing() { - ByteBuffer buf = ByteBuffer.allocate(16); - // Put nothing in the buffer. - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - assertEquals(0, capture.getFrames().size()); - } - - @Test - public void testWindowedParseLargeFrame() { - // Create frames - byte[] payload = new byte[65536]; - Arrays.fill(payload, (byte) '*'); - - List frames = new ArrayList<>(); - TextFrame text = new TextFrame(); - text.setPayload(ByteBuffer.wrap(payload)); - text.setMask(Hex.asByteArray("11223344")); - frames.add(text); - frames.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - - // Build up raw (network bytes) buffer - ByteBuffer networkBytes = UnitGenerator.generate(frames); - - // Parse, in 4096 sized windows - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - while (networkBytes.remaining() > 0) { - ByteBuffer window = networkBytes.slice(); - int windowSize = Math.min(window.remaining(), 4096); - window.limit(windowSize); - parser.parse(window); - networkBytes.position(networkBytes.position() + windowSize); - } - - assertEquals(2, capture.getFrames().size()); - WebSocketFrame frame = capture.getFrames().poll(); - assertEquals(OpCode.TEXT, frame.getOpCode()); - ByteBuffer actualPayload = frame.getPayload(); - assertEquals(payload.length, actualPayload.remaining()); - // Should be all '*' characters (if masking is correct) - for (int i = actualPayload.position(); i < actualPayload.remaining(); i++) { - assertEquals((byte) '*', actualPayload.get(i)); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/PingPayloadParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/PingPayloadParserTest.java deleted file mode 100644 index 19017e52d..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/PingPayloadParserTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.frame.PingFrame; -import com.fireflysource.net.websocket.common.model.IncomingFramesCapture; -import com.fireflysource.net.websocket.common.model.OpCode; -import com.fireflysource.net.websocket.common.model.WebSocketBehavior; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -public class PingPayloadParserTest { - - @Test - public void testBasicPingParsing() { - ByteBuffer buf = ByteBuffer.allocate(16); - BufferUtils.clearToFill(buf); - buf.put(new byte[] - {(byte) 0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}); - BufferUtils.flipToFlush(buf, 0); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.PING, 1); - PingFrame ping = (PingFrame) capture.getFrames().poll(); - - String actual = BufferUtils.toUTF8String(ping.getPayload()); - assertEquals("Hello", actual); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/RFC6455ExamplesParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/RFC6455ExamplesParserTest.java deleted file mode 100644 index 5057fbf79..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/RFC6455ExamplesParserTest.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; -import com.fireflysource.net.websocket.common.model.IncomingFramesCapture; -import com.fireflysource.net.websocket.common.model.OpCode; -import com.fireflysource.net.websocket.common.model.WebSocketBehavior; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -/** - * Collection of Example packets as found in RFC 6455 Examples section - */ -public class RFC6455ExamplesParserTest { - - @Test - public void testFragmentedUnmaskedTextMessage() { - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - ByteBuffer buf = ByteBuffer.allocate(16); - BufferUtils.clearToFill(buf); - - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // A fragmented unmasked text message (part 1 of 2 "Hel") - buf.put(new byte[] - {(byte) 0x01, (byte) 0x03, 0x48, (byte) 0x65, 0x6c}); - - // Parse #1 - BufferUtils.flipToFlush(buf, 0); - parser.parse(buf); - - // part 2 of 2 "lo" (A continuation frame of the prior text message) - BufferUtils.flipToFill(buf); - buf.put(new byte[] - {(byte) 0x80, 0x02, 0x6c, 0x6f}); - - // Parse #2 - BufferUtils.flipToFlush(buf, 0); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - capture.assertHasFrame(OpCode.CONTINUATION, 1); - - WebSocketFrame txt = capture.getFrames().poll(); - String actual = BufferUtils.toUTF8String(txt.getPayload()); - assertEquals("Hel", actual); - txt = capture.getFrames().poll(); - actual = BufferUtils.toUTF8String(txt.getPayload()); - assertEquals("lo", actual); - } - - @Test - public void testSingleMaskedPongRequest() { - ByteBuffer buf = ByteBuffer.allocate(16); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // Unmasked Pong request - buf.put(new byte[] - {(byte) 0x8a, (byte) 0x85, 0x37, (byte) 0xfa, 0x21, 0x3d, 0x7f, (byte) 0x9f, 0x4d, 0x51, 0x58}); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.PONG, 1); - - WebSocketFrame pong = capture.getFrames().poll(); - String actual = BufferUtils.toUTF8String(pong.getPayload()); - assertEquals("Hello", actual); - } - - @Test - public void testSingleMaskedTextMessage() { - ByteBuffer buf = ByteBuffer.allocate(16); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // A single-frame masked text message - buf.put(new byte[] - {(byte) 0x81, (byte) 0x85, 0x37, (byte) 0xfa, 0x21, 0x3d, 0x7f, (byte) 0x9f, 0x4d, 0x51, 0x58}); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - - WebSocketFrame txt = capture.getFrames().poll(); - String actual = BufferUtils.toUTF8String(txt.getPayload()); - assertEquals("Hello", actual); - } - - @Test - public void testSingleUnmasked256ByteBinaryMessage() { - int dataSize = 256; - - ByteBuffer buf = ByteBuffer.allocate(dataSize + 10); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // 256 bytes binary message in a single unmasked frame - buf.put(new byte[] - {(byte) 0x82, 0x7E}); - buf.putShort((short) 0x01_00); // 16 bit size - for (int i = 0; i < dataSize; i++) { - buf.put((byte) 0x44); - } - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.BINARY, 1); - - Frame bin = capture.getFrames().poll(); - - assertEquals(dataSize, bin.getPayloadLength()); - - ByteBuffer data = bin.getPayload(); - assertEquals(dataSize, data.remaining()); - - for (int i = 0; i < dataSize; i++) { - assertEquals((byte) 0x44, data.get(i)); - } - } - - @Test - public void testSingleUnmasked64KByteBinaryMessage() { - int dataSize = 1024 * 64; - - ByteBuffer buf = ByteBuffer.allocate((dataSize + 10)); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // 64 Kbytes binary message in a single unmasked frame - buf.put(new byte[] - {(byte) 0x82, 0x7F}); - buf.putLong(dataSize); // 64bit size - for (int i = 0; i < dataSize; i++) { - buf.put((byte) 0x77); - } - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.BINARY, 1); - - Frame bin = capture.getFrames().poll(); - - assertEquals(dataSize, bin.getPayloadLength()); - ByteBuffer data = bin.getPayload(); - assertEquals(dataSize, data.remaining()); - - for (int i = 0; i < dataSize; i++) { - assertEquals((byte) 0x77, data.get(i)); - } - } - - @Test - public void testSingleUnmaskedPingRequest() { - ByteBuffer buf = ByteBuffer.allocate(16); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // Unmasked Ping request - buf.put(new byte[] - {(byte) 0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.PING, 1); - - WebSocketFrame ping = capture.getFrames().poll(); - String actual = BufferUtils.toUTF8String(ping.getPayload()); - assertEquals("Hello", actual); - } - - @Test - public void testSingleUnmaskedTextMessage() { - ByteBuffer buf = ByteBuffer.allocate(16); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // A single-frame unmasked text message - buf.put(new byte[] - {(byte) 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f}); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.CLIENT); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - - WebSocketFrame txt = capture.getFrames().poll(); - String actual = BufferUtils.toUTF8String(txt.getPayload()); - assertEquals("Hello", actual); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/TextPayloadParserTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/TextPayloadParserTest.java deleted file mode 100644 index 82357df65..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/TextPayloadParserTest.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - -import com.fireflysource.net.websocket.common.exception.MessageTooLargeException; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; -import com.fireflysource.net.websocket.common.model.IncomingFramesCapture; -import com.fireflysource.net.websocket.common.model.OpCode; -import com.fireflysource.net.websocket.common.model.WebSocketBehavior; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import com.fireflysource.net.websocket.common.utils.MaskedByteBuffer; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.*; - -public class TextPayloadParserTest { - - @Test - public void testFrameTooLargeDueToPolicy() throws Exception { - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - // Artificially small buffer/payload - policy.setInputBufferSize(1024); // read buffer - policy.setMaxTextMessageBufferSize(1024); // streaming buffer (not used in this test) - policy.setMaxTextMessageSize(1024); // actual maximum text message size policy - byte[] utf = new byte[2048]; - Arrays.fill(utf, (byte) 'a'); - - assertTrue(utf.length > 0x7E && utf.length < 0xFFFF); - - ByteBuffer buf = ByteBuffer.allocate(utf.length + 8); - buf.put((byte) 0x81); // text frame, fin = true - buf.put((byte) (0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length) - buf.putShort((short) utf.length); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, utf); - buf.flip(); - - UnitParser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - assertThrows(MessageTooLargeException.class, () -> parser.parseQuietly(buf)); - } - - @Test - public void testLongMaskedText() throws Exception { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < 3500; i++) { - sb.append("Hell\uFF4f Big W\uFF4Frld "); - } - sb.append(". The end."); - - String expectedText = sb.toString(); - byte[] utf = expectedText.getBytes(StandardCharsets.UTF_8); - - assertTrue(utf.length > 0xFFFF); - - ByteBuffer buf = ByteBuffer.allocate(utf.length + 32); - buf.put((byte) 0x81); // text frame, fin = true - buf.put((byte) (0x80 | 0x7F)); // 0x7F == 127 (a 8 byte payload length) - buf.putLong(utf.length); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, utf); - buf.flip(); - - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - policy.setMaxTextMessageSize(100000); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - WebSocketFrame txt = capture.getFrames().poll(); - assertEquals(expectedText, txt.getPayloadAsUTF8()); - } - - @Test - public void testMediumMaskedText() throws Exception { - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < 14; i++) { - sb.append("Hell\uFF4f Medium W\uFF4Frld "); - } - sb.append(". The end."); - - String expectedText = sb.toString(); - byte[] utf = expectedText.getBytes(StandardCharsets.UTF_8); - - assertTrue(utf.length > 0x7E && utf.length < 0xFFFF); - - ByteBuffer buf = ByteBuffer.allocate(utf.length + 10); - buf.put((byte) 0x81); - buf.put((byte) (0x80 | 0x7E)); // 0x7E == 126 (a 2 byte payload length) - buf.putShort((short) utf.length); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, utf); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - WebSocketFrame txt = capture.getFrames().poll(); - assertEquals(expectedText, txt.getPayloadAsUTF8()); - } - - @Test - public void testShortMaskedFragmentedText() throws Exception { - String part1 = "Hello "; - String part2 = "World"; - - byte[] b1 = part1.getBytes(StandardCharsets.UTF_8); - byte[] b2 = part2.getBytes(StandardCharsets.UTF_8); - - ByteBuffer buf = ByteBuffer.allocate(32); - - // part 1 - buf.put((byte) 0x01); // no fin + text - buf.put((byte) (0x80 | b1.length)); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, b1); - - // part 2 - buf.put((byte) 0x80); // fin + continuation - buf.put((byte) (0x80 | b2.length)); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, b2); - - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - capture.assertHasFrame(OpCode.CONTINUATION, 1); - WebSocketFrame txt = capture.getFrames().poll(); - assertEquals(part1, txt.getPayloadAsUTF8()); - txt = capture.getFrames().poll(); - assertEquals(part2, txt.getPayloadAsUTF8()); - } - - @Test - public void testShortMaskedText() throws Exception { - String expectedText = "Hello World"; - byte[] utf = expectedText.getBytes(StandardCharsets.UTF_8); - - ByteBuffer buf = ByteBuffer.allocate(24); - buf.put((byte) 0x81); - buf.put((byte) (0x80 | utf.length)); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, utf); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - WebSocketFrame txt = capture.getFrames().poll(); - assertEquals(expectedText, txt.getPayloadAsUTF8()); - } - - @Test - public void testShortMaskedUtf8Text() throws Exception { - String expectedText = "Hell\uFF4f W\uFF4Frld"; - - byte[] utf = expectedText.getBytes(StandardCharsets.UTF_8); - - ByteBuffer buf = ByteBuffer.allocate(24); - buf.put((byte) 0x81); - buf.put((byte) (0x80 | utf.length)); - MaskedByteBuffer.putMask(buf); - MaskedByteBuffer.putPayload(buf, utf); - buf.flip(); - - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - parser.parse(buf); - - capture.assertHasFrame(OpCode.TEXT, 1); - WebSocketFrame txt = capture.getFrames().poll(); - assertEquals(expectedText, txt.getPayloadAsUTF8()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/UnitParser.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/UnitParser.java deleted file mode 100644 index babf03459..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/decoder/UnitParser.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fireflysource.net.websocket.common.decoder; - - -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -import java.nio.ByteBuffer; - -public class UnitParser extends Parser { - public UnitParser() { - this(WebSocketPolicy.newServerPolicy()); - } - - public UnitParser(WebSocketPolicy policy) { - super(policy); - } - - private void parsePartial(ByteBuffer buf, int numBytes) { - int len = Math.min(numBytes, buf.remaining()); - byte[] arr = new byte[len]; - buf.get(arr, 0, len); - this.parse(ByteBuffer.wrap(arr)); - } - - /** - * Parse a buffer, but do so in a quiet fashion, squelching stacktraces if encountered. - *

    - * Use if you know the parse will cause an exception and just don't want to make the test console all noisy. - * - * @param buf the buffer to parse - */ - public void parseQuietly(ByteBuffer buf) { - parse(buf); - } - - public void parseSlowly(ByteBuffer buf, int segmentSize) { - while (buf.remaining() > 0) { - parsePartial(buf, segmentSize); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/GeneratorParserRoundtripTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/GeneratorParserRoundtripTest.java deleted file mode 100644 index f2186e9cc..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/GeneratorParserRoundtripTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.fireflysource.net.websocket.common.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.decoder.Parser; -import com.fireflysource.net.websocket.common.frame.TextFrame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; -import com.fireflysource.net.websocket.common.model.IncomingFramesCapture; -import com.fireflysource.net.websocket.common.model.OpCode; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class GeneratorParserRoundtripTest { - - @Test - public void testParserAndGenerator() { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - Generator gen = new Generator(policy); - Parser parser = new Parser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; - - ByteBuffer out = BufferUtils.allocate(8192); - // Generate Buffer - BufferUtils.flipToFill(out); - WebSocketFrame frame = new TextFrame().setPayload(message); - ByteBuffer header = gen.generateHeaderBytes(frame); - ByteBuffer payload = frame.getPayload(); - out.put(header); - out.put(payload); - - // Parse Buffer - BufferUtils.flipToFlush(out, 0); - parser.parse(out); - - // Validate - capture.assertHasFrame(OpCode.TEXT, 1); - - TextFrame txt = (TextFrame) capture.getFrames().poll(); - assertEquals(message, txt.getPayloadAsUTF8()); - } - - @Test - public void testParserAndGeneratorMasked() { - Generator gen = new Generator(WebSocketPolicy.newClientPolicy()); - Parser parser = new Parser(WebSocketPolicy.newServerPolicy()); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - String message = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; - - ByteBuffer out = BufferUtils.allocate(8192); - BufferUtils.flipToFill(out); - // Setup Frame - WebSocketFrame frame = new TextFrame().setPayload(message); - - // Add masking - byte[] mask = new byte[4]; - Arrays.fill(mask, (byte) 0xFF); - frame.setMask(mask); - - // Generate Buffer - ByteBuffer header = gen.generateHeaderBytes(frame); - ByteBuffer payload = frame.getPayload(); - out.put(header); - out.put(payload); - - // Parse Buffer - BufferUtils.flipToFlush(out, 0); - parser.parse(out); - - // Validate - capture.assertHasFrame(OpCode.TEXT, 1); - - TextFrame txt = (TextFrame) capture.getFrames().poll(); - assertTrue(txt.isMasked()); - assertEquals(message, txt.getPayloadAsUTF8()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/GeneratorTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/GeneratorTest.java deleted file mode 100644 index e45f9daa2..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/GeneratorTest.java +++ /dev/null @@ -1,270 +0,0 @@ -package com.fireflysource.net.websocket.common.encoder; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.decoder.Parser; -import com.fireflysource.net.websocket.common.decoder.UnitParser; -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.model.*; -import com.fireflysource.net.websocket.common.utils.Hex; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -public class GeneratorTest { - private static final LazyLogger LOG = SystemLogger.create(WindowHelper.class); - - public static class WindowHelper { - final int windowSize; - int totalParts; - int totalBytes; - - public WindowHelper(int windowSize) { - this.windowSize = windowSize; - this.totalParts = 0; - this.totalBytes = 0; - } - - public ByteBuffer generateWindowed(Frame... frames) { - // Create Buffer to hold all generated frames in a single buffer - int completeBufSize = 0; - for (Frame f : frames) { - completeBufSize += Generator.MAX_HEADER_LENGTH + f.getPayloadLength(); - } - - ByteBuffer completeBuf = ByteBuffer.allocate(completeBufSize); - BufferUtils.clearToFill(completeBuf); - - // Generate from all frames - Generator generator = new UnitGenerator(); - - for (Frame f : frames) { - ByteBuffer header = generator.generateHeaderBytes(f); - totalBytes += BufferUtils.put(header, completeBuf); - - if (f.hasPayload()) { - ByteBuffer payload = f.getPayload(); - totalBytes += payload.remaining(); - totalParts++; - completeBuf.put(payload.slice()); - } - } - - // Return results - BufferUtils.flipToFlush(completeBuf, 0); - return completeBuf; - } - - public void assertTotalParts(int expectedParts) { - assertEquals(expectedParts, totalParts); - } - - public void assertTotalBytes(int expectedBytes) { - assertEquals(expectedBytes, totalBytes); - } - } - - private void assertGeneratedBytes(CharSequence expectedBytes, Frame... frames) { - // collect up all frames as single ByteBuffer - ByteBuffer allframes = UnitGenerator.generate(frames); - // Get hex String form of all frames bytebuffer. - String actual = Hex.asHex(allframes); - // Validate - assertEquals(expectedBytes.toString(), actual); - } - - private String asMaskedHex(String str, byte[] maskingKey) { - byte[] utf = StringUtils.getUtf8Bytes(str); - mask(utf, maskingKey); - return Hex.asHex(utf); - } - - private void mask(byte[] buf, byte[] maskingKey) { - int size = buf.length; - for (int i = 0; i < size; i++) { - buf[i] ^= maskingKey[i % 4]; - } - } - - @Test - public void testCloseEmpty() { - // 0 byte payload (no status code) - assertGeneratedBytes("8800", new CloseFrame()); - } - - @Test - public void testCloseCodeNoReason() { - CloseInfo close = new CloseInfo(StatusCode.NORMAL); - // 2 byte payload (2 bytes for status code) - assertGeneratedBytes("880203E8", close.asFrame()); - } - - @Test - public void testCloseCodeOkReason() { - CloseInfo close = new CloseInfo(StatusCode.NORMAL, "OK"); - // 4 byte payload (2 bytes for status code, 2 more for "OK") - assertGeneratedBytes("880403E84F4B", close.asFrame()); - } - - @Test - public void testTextHello() { - WebSocketFrame frame = new TextFrame().setPayload("Hello"); - byte[] utf = StringUtils.getUtf8Bytes("Hello"); - assertGeneratedBytes("8105" + Hex.asHex(utf), frame); - } - - @Test - public void testTextMasked() { - WebSocketFrame frame = new TextFrame().setPayload("Hello"); - byte[] maskingKey = Hex.asByteArray("11223344"); - frame.setMask(maskingKey); - - // what is expected - StringBuilder expected = new StringBuilder(); - expected.append("8185").append("11223344"); - expected.append(asMaskedHex("Hello", maskingKey)); - - // validate - assertGeneratedBytes(expected, frame); - } - - @Test - public void testTextMaskedOffsetSourceByteBuffer() { - ByteBuffer payload = ByteBuffer.allocate(100); - payload.position(5); - payload.put(StringUtils.getUtf8Bytes("Hello")); - payload.flip(); - payload.position(5); - // at this point, we have a ByteBuffer of 100 bytes. - // but only a few bytes in the middle are made available for the payload. - // we are testing that masking works as intended, even if the provided - // payload does not start at position 0. - LOG.debug("Payload = {}", BufferUtils.toDetailString(payload)); - WebSocketFrame frame = new TextFrame().setPayload(payload); - byte[] maskingKey = Hex.asByteArray("11223344"); - frame.setMask(maskingKey); - - // what is expected - StringBuilder expected = new StringBuilder(); - expected.append("8185").append("11223344"); - expected.append(asMaskedHex("Hello", maskingKey)); - - // validate - assertGeneratedBytes(expected, frame); - } - - /** - * Prevent regression of masking of many packets. - */ - @Test - public void testManyMasked() { - int pingCount = 2; - - // Prepare frames - WebSocketFrame[] frames = new WebSocketFrame[pingCount + 1]; - for (int i = 0; i < pingCount; i++) { - frames[i] = new PingFrame().setPayload(String.format("ping-%d", i)); - } - frames[pingCount] = new CloseInfo(StatusCode.NORMAL).asFrame(); - - // Mask All Frames - byte[] maskingKey = Hex.asByteArray("11223344"); - for (WebSocketFrame f : frames) { - f.setMask(maskingKey); - } - - // Validate result of generation - StringBuilder expected = new StringBuilder(); - expected.append("8986").append("11223344"); - expected.append(asMaskedHex("ping-0", maskingKey)); // ping 0 - expected.append("8986").append("11223344"); - expected.append(asMaskedHex("ping-1", maskingKey)); // ping 1 - expected.append("8882").append("11223344"); - byte[] closure = Hex.asByteArray("03E8"); - mask(closure, maskingKey); - expected.append(Hex.asHex(closure)); // normal closure - - assertGeneratedBytes(expected, frames); - } - - /** - * Test the windowed generate of a frame that has no masking. - */ - @Test - public void testWindowedGenerate() { - // A decent sized frame, no masking - byte[] payload = new byte[10240]; - Arrays.fill(payload, (byte) 0x44); - - WebSocketFrame frame = new BinaryFrame().setPayload(payload); - - // Generate - int windowSize = 1024; - WindowHelper helper = new WindowHelper(windowSize); - ByteBuffer completeBuffer = helper.generateWindowed(frame); - - // Validate - int expectedHeaderSize = 4; - int expectedSize = payload.length + expectedHeaderSize; - int expectedParts = 1; - - helper.assertTotalParts(expectedParts); - helper.assertTotalBytes(payload.length + expectedHeaderSize); - - assertEquals(expectedSize, completeBuffer.remaining()); - } - - @Test - public void testWindowedGenerateWithMasking() { - // A decent sized frame, with masking - byte[] payload = new byte[10240]; - Arrays.fill(payload, (byte) 0x55); - - byte[] mask = new byte[] - {0x2A, (byte) 0xF0, 0x0F, 0x00}; - - WebSocketFrame frame = new BinaryFrame().setPayload(payload); - frame.setMask(mask); // masking! - - // Generate - int windowSize = 2929; - WindowHelper helper = new WindowHelper(windowSize); - ByteBuffer completeBuffer = helper.generateWindowed(frame); - - // Validate - int expectedHeaderSize = 8; - int expectedSize = payload.length + expectedHeaderSize; - int expectedParts = 1; - - helper.assertTotalParts(expectedParts); - helper.assertTotalBytes(payload.length + expectedHeaderSize); - - assertEquals(expectedSize, completeBuffer.remaining()); - - // Parse complete buffer. - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - Parser parser = new UnitParser(policy); - IncomingFramesCapture capture = new IncomingFramesCapture(); - parser.setIncomingFramesHandler(capture); - - parser.parse(completeBuffer); - - // Assert validity of frame - WebSocketFrame actual = capture.getFrames().poll(); - assertEquals(OpCode.BINARY, actual.getOpCode()); - assertEquals(payload.length, actual.getPayloadLength()); - - // Validate payload contents for proper masking - ByteBuffer actualData = actual.getPayload().slice(); - assertEquals(payload.length, actualData.remaining()); - while (actualData.remaining() > 0) { - assertEquals((byte) 0x55, actualData.get()); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/RFC6455ExamplesGeneratorTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/RFC6455ExamplesGeneratorTest.java deleted file mode 100644 index 5e1632a79..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/RFC6455ExamplesGeneratorTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.fireflysource.net.websocket.common.encoder; - -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.utils.ByteBufferAssert; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -public class RFC6455ExamplesGeneratorTest { - private static final int FUDGE = 32; - - @Test - public void testFragmentedUnmaskedTextMessage() { - WebSocketFrame text1 = new TextFrame().setPayload("Hel").setFin(false); - WebSocketFrame text2 = new ContinuationFrame().setPayload("lo"); - - ByteBuffer actual1 = UnitGenerator.generate(text1); - ByteBuffer actual2 = UnitGenerator.generate(text2); - - ByteBuffer expected1 = ByteBuffer.allocate(5); - - expected1.put(new byte[] - {(byte) 0x01, (byte) 0x03, (byte) 0x48, (byte) 0x65, (byte) 0x6c}); - - ByteBuffer expected2 = ByteBuffer.allocate(4); - - expected2.put(new byte[] - {(byte) 0x80, (byte) 0x02, (byte) 0x6c, (byte) 0x6f}); - - expected1.flip(); - expected2.flip(); - - ByteBufferAssert.assertEquals("t1 buffers are not equal", expected1, actual1); - ByteBufferAssert.assertEquals("t2 buffers are not equal", expected2, actual2); - } - - @Test - public void testSingleMaskedPongRequest() { - PongFrame pong = new PongFrame().setPayload("Hello"); - pong.setMask(new byte[] - {0x37, (byte) 0xfa, 0x21, 0x3d}); - - ByteBuffer actual = UnitGenerator.generate(pong); - - ByteBuffer expected = ByteBuffer.allocate(11); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // Unmasked Pong request - expected.put(new byte[] - {(byte) 0x8a, (byte) 0x85, 0x37, (byte) 0xfa, 0x21, 0x3d, 0x7f, (byte) 0x9f, 0x4d, 0x51, 0x58}); - expected.flip(); // make readable - - ByteBufferAssert.assertEquals("pong buffers are not equal", expected, actual); - } - - @Test - public void testSingleMaskedTextMessage() { - WebSocketFrame text = new TextFrame().setPayload("Hello"); - text.setMask(new byte[] - {0x37, (byte) 0xfa, 0x21, 0x3d}); - - ByteBuffer actual = UnitGenerator.generate(text); - - ByteBuffer expected = ByteBuffer.allocate(11); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // A single-frame masked text message - expected.put(new byte[] - {(byte) 0x81, (byte) 0x85, 0x37, (byte) 0xfa, 0x21, 0x3d, 0x7f, (byte) 0x9f, 0x4d, 0x51, 0x58}); - expected.flip(); // make readable - - ByteBufferAssert.assertEquals("masked text buffers are not equal", expected, actual); - } - - @Test - public void testSingleUnmasked256ByteBinaryMessage() { - int dataSize = 256; - - BinaryFrame binary = new BinaryFrame(); - byte[] payload = new byte[dataSize]; - Arrays.fill(payload, (byte) 0x44); - binary.setPayload(ByteBuffer.wrap(payload)); - - ByteBuffer actual = UnitGenerator.generate(binary); - - ByteBuffer expected = ByteBuffer.allocate(dataSize + FUDGE); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // 256 bytes binary message in a single unmasked frame - expected.put(new byte[] - {(byte) 0x82, (byte) 0x7E}); - expected.putShort((short) 0x01_00); - - for (int i = 0; i < dataSize; i++) { - expected.put((byte) 0x44); - } - - expected.flip(); - - ByteBufferAssert.assertEquals("binary buffers are not equal", expected, actual); - } - - @Test - public void testSingleUnmasked64KBinaryMessage() { - int dataSize = 1024 * 64; - - BinaryFrame binary = new BinaryFrame(); - byte[] payload = new byte[dataSize]; - Arrays.fill(payload, (byte) 0x44); - binary.setPayload(ByteBuffer.wrap(payload)); - - ByteBuffer actual = UnitGenerator.generate(binary); - - ByteBuffer expected = ByteBuffer.allocate(dataSize + 10); - // Raw bytes as found in RFC 6455, Section 5.7 - Examples - // 64k bytes binary message in a single unmasked frame - expected.put(new byte[] - {(byte) 0x82, (byte) 0x7F}); - expected.putInt(0x00_00_00_00); - expected.putInt(0x00_01_00_00); - - for (int i = 0; i < dataSize; i++) { - expected.put((byte) 0x44); - } - - expected.flip(); - - ByteBufferAssert.assertEquals("binary buffers are not equal", expected, actual); - } - - @Test - public void testSingleUnmaskedPingRequest() throws Exception { - PingFrame ping = new PingFrame().setPayload("Hello"); - - ByteBuffer actual = UnitGenerator.generate(ping); - - ByteBuffer expected = ByteBuffer.allocate(10); - expected.put(new byte[] - {(byte) 0x89, (byte) 0x05, (byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f}); - expected.flip(); // make readable - - ByteBufferAssert.assertEquals("Ping buffers", expected, actual); - } - - @Test - public void testSingleUnmaskedTextMessage() { - WebSocketFrame text = new TextFrame().setPayload("Hello"); - - ByteBuffer actual = UnitGenerator.generate(text); - - ByteBuffer expected = ByteBuffer.allocate(10); - - expected.put(new byte[] - {(byte) 0x81, (byte) 0x05, (byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f}); - - expected.flip(); - - ByteBufferAssert.assertEquals("t1 buffers are not equal", expected, actual); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/UnitGenerator.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/UnitGenerator.java deleted file mode 100644 index debf4d5ed..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/encoder/UnitGenerator.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.fireflysource.net.websocket.common.encoder; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; - -import java.nio.ByteBuffer; -import java.util.List; - -/** - * Convenience Generator. - */ -public class UnitGenerator extends Generator { - - private static final LazyLogger LOG = SystemLogger.create(UnitGenerator.class); - - public static ByteBuffer generate(Frame frame) { - return generate(new Frame[]{frame}); - } - - /** - * Generate All Frames into a single ByteBuffer. - *

    - * This is highly inefficient and is not used in production! (This exists to make testing of the Generator easier) - * - * @param frames the frames to generate from - * @return the ByteBuffer representing all of the generated frames provided. - */ - public static ByteBuffer generate(Frame[] frames) { - Generator generator = new UnitGenerator(); - - // Generate into single bytebuffer - int buflen = 0; - for (Frame f : frames) { - buflen += f.getPayloadLength() + MAX_HEADER_LENGTH; - } - ByteBuffer completeBuf = ByteBuffer.allocate(buflen); - BufferUtils.clearToFill(completeBuf); - - // Generate frames - for (Frame f : frames) { - generator.generateWholeFrame(f, completeBuf); - } - - BufferUtils.flipToFlush(completeBuf, 0); - if (LOG.isDebugEnabled()) { - LOG.debug("generate({} frames) - {}", frames.length, BufferUtils.toDetailString(completeBuf)); - } - return completeBuf; - } - - /** - * Generate a single giant buffer of all provided frames Not appropriate for production code, but useful for testing. - * - * @param frames the list of frames to generate from - * @return the bytebuffer representing all of the generated frames - */ - public static ByteBuffer generate(List frames) { - // Create non-symmetrical mask (helps show mask bytes order issues) - final byte[] MASK = - {0x11, 0x22, 0x33, 0x44}; - - // the generator - Generator generator = new UnitGenerator(); - - // Generate into single bytebuffer - int buflen = 0; - for (Frame f : frames) { - buflen += f.getPayloadLength() + MAX_HEADER_LENGTH; - } - ByteBuffer completeBuf = ByteBuffer.allocate(buflen); - BufferUtils.clearToFill(completeBuf); - - // Generate frames - for (WebSocketFrame f : frames) { - f.setMask(MASK); // make sure we have the test mask set - BufferUtils.put(generator.generateHeaderBytes(f), completeBuf); - ByteBuffer window = f.getPayload(); - if (BufferUtils.hasContent(window)) { - BufferUtils.put(window, completeBuf); - } - } - - BufferUtils.flipToFlush(completeBuf, 0); - if (LOG.isDebugEnabled()) { - LOG.debug("generate({} frames) - {}", frames.size(), BufferUtils.toDetailString(completeBuf)); - } - return completeBuf; - } - - public UnitGenerator() { - super(WebSocketPolicy.newServerPolicy()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/AbstractExtensionTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/AbstractExtensionTest.java deleted file mode 100644 index ebd423c79..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/AbstractExtensionTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.fireflysource.net.websocket.common.extension; - - -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import org.junit.jupiter.api.BeforeEach; - -public abstract class AbstractExtensionTest { - - protected ExtensionTool clientExtensions; - protected ExtensionTool serverExtensions; - - @BeforeEach - public void init() { - clientExtensions = new ExtensionTool(WebSocketPolicy.newClientPolicy()); - serverExtensions = new ExtensionTool(WebSocketPolicy.newServerPolicy()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/ExtensionTool.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/ExtensionTool.java deleted file mode 100644 index d057c7b24..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/ExtensionTool.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.fireflysource.net.websocket.common.extension; - -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.net.websocket.common.decoder.Parser; -import com.fireflysource.net.websocket.common.decoder.UnitParser; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.frame.TextFrame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; -import com.fireflysource.net.websocket.common.model.Extension; -import com.fireflysource.net.websocket.common.model.ExtensionConfig; -import com.fireflysource.net.websocket.common.model.IncomingFramesCapture; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import com.fireflysource.net.websocket.common.utils.ByteBufferAssert; - -import java.nio.ByteBuffer; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; - -public class ExtensionTool { - public class Tester { - private String requestedExtParams; - private ExtensionConfig extConfig; - private Extension ext; - private Parser parser; - private IncomingFramesCapture capture; - - private Tester(String parameterizedExtension) { - this.requestedExtParams = parameterizedExtension; - this.extConfig = ExtensionConfig.parse(parameterizedExtension); - Class extClass = factory.getExtension(extConfig.getName()); - assertNotNull(extClass); - - this.parser = new UnitParser(policy); - } - - public String getRequestedExtParams() { - return requestedExtParams; - } - - public void assertNegotiated(String expectedNegotiation) { - this.ext = factory.newInstance(extConfig); - if (ext instanceof AbstractExtension) { - ((AbstractExtension) ext).setPolicy(policy); - } - - this.capture = new IncomingFramesCapture(); - this.ext.setNextIncomingFrames(capture); - - this.parser.configureFromExtensions(Collections.singletonList(ext)); - this.parser.setIncomingFramesHandler(ext); - } - - public void parseIncomingHex(String... rawhex) { - int parts = rawhex.length; - byte[] net; - - for (int i = 0; i < parts; i++) { - String hex = rawhex[i].replaceAll("\\s*(0x)?", ""); - net = TypeUtils.fromHexString(hex); - parser.parse(ByteBuffer.wrap(net)); - } - } - - public void assertHasFrames(String... textFrames) { - Frame[] frames = new Frame[textFrames.length]; - for (int i = 0; i < frames.length; i++) { - frames[i] = new TextFrame().setPayload(textFrames[i]); - } - assertHasFrames(frames); - } - - public void assertHasFrames(Frame... expectedFrames) { - int expectedCount = expectedFrames.length; - capture.assertFrameCount(expectedCount); - - for (int i = 0; i < expectedCount; i++) { - WebSocketFrame actual = capture.getFrames().poll(); - - String prefix = String.format("frame[%d]", i); - assertEquals(expectedFrames[i].getOpCode(), actual.getOpCode()); - assertEquals(expectedFrames[i].isFin(), actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = expectedFrames[i].getPayload().slice(); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice()); - } - } - } - - private final WebSocketPolicy policy; - private final ExtensionFactory factory; - - public ExtensionTool(WebSocketPolicy policy) { - this.policy = policy; - this.factory = new WebSocketExtensionFactory(); - } - - public Tester newTester(String parameterizedExtension) { - return new Tester(parameterizedExtension); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/ByteAccumulatorTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/ByteAccumulatorTest.java deleted file mode 100644 index be0d076aa..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/ByteAccumulatorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.exception.MessageTooLargeException; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.jupiter.api.Assertions.*; - -public class ByteAccumulatorTest { - - @Test - public void testCopyNormal() { - ByteAccumulator accumulator = new ByteAccumulator(10_000); - - byte[] hello = "Hello".getBytes(UTF_8); - byte[] space = " ".getBytes(UTF_8); - byte[] world = "World".getBytes(UTF_8); - - accumulator.copyChunk(hello, 0, hello.length); - accumulator.copyChunk(space, 0, space.length); - accumulator.copyChunk(world, 0, world.length); - - assertEquals(hello.length + space.length + world.length, accumulator.getLength()); - - ByteBuffer out = ByteBuffer.allocate(200); - accumulator.transferTo(out); - String result = BufferUtils.toUTF8String(out); - assertEquals("Hello World", result); - } - - @Test - public void testTransferToNotEnoughSpace() { - ByteAccumulator accumulator = new ByteAccumulator(10_000); - - byte[] hello = "Hello".getBytes(UTF_8); - byte[] space = " ".getBytes(UTF_8); - byte[] world = "World".getBytes(UTF_8); - - accumulator.copyChunk(hello, 0, hello.length); - accumulator.copyChunk(space, 0, space.length); - accumulator.copyChunk(world, 0, world.length); - - int length = hello.length + space.length + world.length; - assertEquals(length, accumulator.getLength()); - - ByteBuffer out = ByteBuffer.allocate(length - 2); // intentionally too small ByteBuffer - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> accumulator.transferTo(out)); - assertTrue(e.getMessage().contains("Not enough space in ByteBuffer")); - } - - @Test - public void testCopyChunkNotEnoughSpace() { - byte[] hello = "Hello".getBytes(UTF_8); - byte[] space = " ".getBytes(UTF_8); - byte[] world = "World".getBytes(UTF_8); - - int length = hello.length + space.length + world.length; - ByteAccumulator accumulator = new ByteAccumulator(length - 2); // intentionally too small of a max - - accumulator.copyChunk(hello, 0, hello.length); - accumulator.copyChunk(space, 0, space.length); - - MessageTooLargeException e = assertThrows(MessageTooLargeException.class, () -> accumulator.copyChunk(world, 0, world.length)); - System.out.println(e.getMessage()); - assertTrue(e.getMessage().contains("too large for configured max")); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/CapturedHexPayloads.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/CapturedHexPayloads.java deleted file mode 100644 index ab41d1406..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/CapturedHexPayloads.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.model.OutgoingFrames; -import com.fireflysource.net.websocket.common.utils.Hex; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -public class CapturedHexPayloads implements OutgoingFrames { - private List captured = new ArrayList<>(); - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - String hexPayload = Hex.asHex(frame.getPayload()); - captured.add(hexPayload); - if (result != null) { - result.accept(Result.SUCCESS); - } - } - - public List getCaptured() { - return captured; - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/DeflateFrameExtensionTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/DeflateFrameExtensionTest.java deleted file mode 100644 index 0113a354f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/DeflateFrameExtensionTest.java +++ /dev/null @@ -1,383 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.common.slf4j.LazyLogger; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.common.sys.Result; -import com.fireflysource.common.sys.SystemLogger; -import com.fireflysource.net.websocket.common.decoder.Parser; -import com.fireflysource.net.websocket.common.decoder.UnitParser; -import com.fireflysource.net.websocket.common.encoder.Generator; -import com.fireflysource.net.websocket.common.extension.AbstractExtensionTest; -import com.fireflysource.net.websocket.common.extension.ExtensionTool.Tester; -import com.fireflysource.net.websocket.common.frame.BinaryFrame; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.frame.TextFrame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; -import com.fireflysource.net.websocket.common.model.*; -import com.fireflysource.net.websocket.common.utils.ByteBufferAssert; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.function.Consumer; -import java.util.zip.Deflater; -import java.util.zip.Inflater; - -import static org.junit.jupiter.api.Assertions.*; - -public class DeflateFrameExtensionTest extends AbstractExtensionTest { - private static final LazyLogger LOG = SystemLogger.create(DeflateFrameExtensionTest.class); - - private void assertIncoming(byte[] raw, String... expectedTextDatas) { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - - DeflateFrameExtension ext = new DeflateFrameExtension(); - ext.setPolicy(policy); - - ExtensionConfig config = ExtensionConfig.parse("deflate-frame"); - ext.setConfig(config); - - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); - - // Wire up stack - ext.setNextIncomingFrames(capture); - - Parser parser = new UnitParser(policy); - parser.configureFromExtensions(Collections.singletonList(ext)); - parser.setIncomingFramesHandler(ext); - - parser.parse(ByteBuffer.wrap(raw)); - - int len = expectedTextDatas.length; - capture.assertFrameCount(len); - capture.assertHasFrame(OpCode.TEXT, len); - - int i = 0; - for (WebSocketFrame actual : capture.getFrames()) { - String prefix = "Frame[" + i + "]"; - assertEquals(OpCode.TEXT, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(expectedTextDatas[i], StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice()); - i++; - } - } - - private void assertOutgoing(String text, String expectedHex) throws IOException { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - - DeflateFrameExtension ext = new DeflateFrameExtension(); - ext.setPolicy(policy); - - ExtensionConfig config = ExtensionConfig.parse("deflate-frame"); - ext.setConfig(config); - - Generator generator = new Generator(policy, true); - generator.configureFromExtensions(Collections.singletonList(ext)); - - OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator); - ext.setNextOutgoingFrames(capture); - - Frame frame = new TextFrame().setPayload(text); - ext.outgoingFrame(frame, null); - - capture.assertBytes(0, expectedHex); - } - - @Test - public void testBlockheadClientHelloThere() { - Tester tester = serverExtensions.newTester("deflate-frame"); - - tester.assertNegotiated("deflate-frame"); - - tester.parseIncomingHex(// Captured from Blockhead Client - "Hello" then "There" via unit test - "c18700000000f248cdc9c90700", // "Hello" - "c187000000000ac9482d4a0500" // "There" - ); - - tester.assertHasFrames("Hello", "There"); - } - - @Test - public void testChrome20Hello() { - Tester tester = serverExtensions.newTester("deflate-frame"); - - tester.assertNegotiated("deflate-frame"); - - tester.parseIncomingHex(// Captured from Chrome 20.x - "Hello" (sent from browser) - "c187832b5c11716391d84a2c5c" // "Hello" - ); - - tester.assertHasFrames("Hello"); - } - - @Test - public void testChrome20HelloThere() { - Tester tester = serverExtensions.newTester("deflate-frame"); - - tester.assertNegotiated("deflate-frame"); - - tester.parseIncomingHex(// Captured from Chrome 20.x - "Hello" then "There" (sent from browser) - "c1877b1971db8951bc12b21e71", // "Hello" - "c18759edc8f4532480d913e8c8" // There - ); - - tester.assertHasFrames("Hello", "There"); - } - - @Test - public void testChrome20Info() { - Tester tester = serverExtensions.newTester("deflate-frame"); - - tester.assertNegotiated("deflate-frame"); - - tester.parseIncomingHex(// Captured from Chrome 20.x - "info:" (sent from browser) - "c187ca4def7f0081a4b47d4fef" // example payload - ); - - tester.assertHasFrames("info:"); - } - - @Test - public void testChrome20TimeTime() { - Tester tester = serverExtensions.newTester("deflate-frame"); - - tester.assertNegotiated("deflate-frame"); - - tester.parseIncomingHex(// Captured from Chrome 20.x - "time:" then "time:" once more (sent from browser) - "c18782467424a88fb869374474", // "time:" - "c1853cfda17f16fcb07f3c" // "time:" - ); - - tester.assertHasFrames("time:", "time:"); - } - - @Test - public void testPyWebSocketTimeTimeTime() { - Tester tester = serverExtensions.newTester("deflate-frame"); - - tester.assertNegotiated("deflate-frame"); - - tester.parseIncomingHex(// Captured from Pywebsocket (r781) - "time:" sent 3 times. - "c1876b100104" + "41d9cd49de1201", // "time:" - "c1852ae3ff01" + "00e2ee012a", // "time:" - "c18435558caa" + "37468caa" // "time:" - ); - - tester.assertHasFrames("time:", "time:", "time:"); - } - - @Test - public void testCompressTimeTimeTime() { - // What pywebsocket produces for "time:", "time:", "time:" - String[] expected = new String[] - {"2AC9CC4DB50200", "2A01110000", "02130000"}; - - // Lets see what we produce - CapturedHexPayloads capture = new CapturedHexPayloads(); - DeflateFrameExtension ext = new DeflateFrameExtension(); - init(ext); - ext.setNextOutgoingFrames(capture); - - ext.outgoingFrame(new TextFrame().setPayload("time:"), null); - ext.outgoingFrame(new TextFrame().setPayload("time:"), null); - ext.outgoingFrame(new TextFrame().setPayload("time:"), null); - - List actual = capture.getCaptured(); - assertTrue(actual.containsAll(Arrays.asList(expected))); - } - - private void init(DeflateFrameExtension ext) { - ext.setConfig(new ExtensionConfig(ext.getName())); - } - - @Test - public void testDeflateBasics() { - // Setup deflater basics - Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION, true); - compressor.setStrategy(Deflater.DEFAULT_STRATEGY); - - // Text to compress - String text = "info:"; - byte[] uncompressed = StringUtils.getUtf8Bytes(text); - - // Prime the compressor - compressor.reset(); - compressor.setInput(uncompressed, 0, uncompressed.length); - compressor.finish(); - - // Perform compression - ByteBuffer outbuf = ByteBuffer.allocate(64); - BufferUtils.clearToFill(outbuf); - - while (!compressor.finished()) { - byte[] out = new byte[64]; - int len = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH); - if (len > 0) { - outbuf.put(out, 0, len); - } - } - compressor.end(); - - BufferUtils.flipToFlush(outbuf, 0); - byte[] compressed = BufferUtils.toArray(outbuf); - // Clear the BFINAL bit that has been set by the compressor.end() call. - // In the real implementation we never end() the compressor. - compressed[0] &= 0xFE; - - String actual = TypeUtils.toHexString(compressed); - String expected = "CaCc4bCbB70200"; // what pywebsocket produces - - assertEquals(expected, actual); - } - - @Test - public void testGeneratedTwoFrames() throws IOException { - WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); - - DeflateFrameExtension ext = new DeflateFrameExtension(); - ext.setPolicy(policy); - ext.setConfig(new ExtensionConfig(ext.getName())); - - Generator generator = new Generator(policy, true); - generator.configureFromExtensions(Collections.singletonList(ext)); - - OutgoingNetworkBytesCapture capture = new OutgoingNetworkBytesCapture(generator); - ext.setNextOutgoingFrames(capture); - - ext.outgoingFrame(new TextFrame().setPayload("Hello"), null); - ext.outgoingFrame(new TextFrame(), null); - ext.outgoingFrame(new TextFrame().setPayload("There"), null); - - capture.assertBytes(0, "c107f248cdc9c90700"); - } - - @Test - public void testInflateBasics() throws Exception { - // should result in "info:" text if properly inflated - byte[] rawbuf = TypeUtils.fromHexString("CaCc4bCbB70200"); // what pywebsocket produces - // byte[] rawbuf = TypeUtil.fromHexString("CbCc4bCbB70200"); // what java produces - - Inflater inflater = new Inflater(true); - inflater.reset(); - inflater.setInput(rawbuf, 0, rawbuf.length); - - byte[] outbuf = new byte[64]; - int len = inflater.inflate(outbuf); - inflater.end(); - assertTrue(len > 4); - - String actual = new String(outbuf, 0, len, StandardCharsets.UTF_8); - assertEquals("info:", actual); - } - - @Test - public void testPyWebSocketServerHello() { - // Captured from PyWebSocket - "Hello" (echo from server) - byte[] rawbuf = TypeUtils.fromHexString("c107f248cdc9c90700"); - assertIncoming(rawbuf, "Hello"); - } - - @Test - public void testPyWebSocketServerLong() { - // Captured from PyWebSocket - Long Text (echo from server) - byte[] rawbuf = TypeUtils.fromHexString("c1421cca410a80300c44d1abccce9df7" + - "f018298634d05631138ab7b7b8fdef1f" + - "dc0282e2061d575a45f6f2686bab25e1" + - "3fb7296fa02b5885eb3b0379c394f461" + - "98cafd03"); - assertIncoming(rawbuf, "It's a big enough umbrella but it's always me that ends up getting wet."); - } - - @Test - public void testPyWebSocketServerMedium() { - // Captured from PyWebSocket - "stackoverflow" (echo from server) - byte[] rawbuf = TypeUtils.fromHexString("c10f2a2e494ccece2f4b2d4acbc92f0700"); - assertIncoming(rawbuf, "stackoverflow"); - } - - /** - * Make sure that the server generated compressed form for "Hello" is consistent with what PyWebSocket creates. - * - * @throws IOException on test failure - */ - @Test - public void testServerGeneratedHello() throws IOException { - assertOutgoing("Hello", "c107f248cdc9c90700"); - } - - /** - * Make sure that the server generated compressed form for "There" is consistent with what PyWebSocket creates. - * - * @throws IOException on test failure - */ - @Test - public void testServerGeneratedThere() throws IOException { - assertOutgoing("There", "c1070ac9482d4a0500"); - } - - @Test - public void testCompressAndDecompressBigPayload() throws Exception { - byte[] input = new byte[1024 * 1024]; - // Make them not compressible. - new Random().nextBytes(input); - - int maxMessageSize = (1024 * 1024) + 8192; - - DeflateFrameExtension clientExtension = new DeflateFrameExtension(); - clientExtension.setPolicy(WebSocketPolicy.newClientPolicy()); - clientExtension.getPolicy().setMaxBinaryMessageSize(maxMessageSize); - clientExtension.getPolicy().setMaxBinaryMessageBufferSize(maxMessageSize); - clientExtension.setConfig(ExtensionConfig.parse("deflate-frame")); - - final DeflateFrameExtension serverExtension = new DeflateFrameExtension(); - serverExtension.setPolicy(WebSocketPolicy.newServerPolicy()); - serverExtension.getPolicy().setMaxBinaryMessageSize(maxMessageSize); - serverExtension.getPolicy().setMaxBinaryMessageBufferSize(maxMessageSize); - serverExtension.setConfig(ExtensionConfig.parse("deflate-frame")); - - // Chain the next element to decompress. - clientExtension.setNextOutgoingFrames(new OutgoingFrames() { - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - LOG.debug("outgoingFrame({})", frame); - serverExtension.incomingFrame(frame); - result.accept(Result.SUCCESS); - } - }); - - final ByteArrayOutputStream result = new ByteArrayOutputStream(input.length); - serverExtension.setNextIncomingFrames(new IncomingFrames() { - @Override - public void incomingFrame(Frame frame) { - LOG.debug("incomingFrame({})", frame); - try { - result.write(BufferUtils.toArray(frame.getPayload())); - } catch (IOException x) { - throw new RuntimeException(x); - } - } - }); - - BinaryFrame frame = new BinaryFrame(); - frame.setPayload(input); - frame.setFin(true); - clientExtension.outgoingFrame(frame, null); - - assertArrayEquals(input, result.toByteArray()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/PerMessageDeflateExtensionTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/PerMessageDeflateExtensionTest.java deleted file mode 100644 index 058b2e1e4..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/compress/PerMessageDeflateExtensionTest.java +++ /dev/null @@ -1,514 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.compress; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.net.websocket.common.exception.ProtocolException; -import com.fireflysource.net.websocket.common.extension.AbstractExtensionTest; -import com.fireflysource.net.websocket.common.extension.ExtensionTool.Tester; -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.model.*; -import com.fireflysource.net.websocket.common.utils.ByteBufferAssert; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Client side behavioral tests for permessage-deflate extension. - *

    - * See: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-15 - */ -public class PerMessageDeflateExtensionTest extends AbstractExtensionTest { - - private void assertEndsWithTail(String hexStr, boolean expectedResult) { - ByteBuffer buf = ByteBuffer.wrap(TypeUtils.fromHexString(hexStr)); - assertEquals(expectedResult, CompressExtension.endsWithTail(buf)); - } - - @Test - public void testEndsWithTailBytes() { - assertEndsWithTail("11223344", false); - assertEndsWithTail("00", false); - assertEndsWithTail("0000", false); - assertEndsWithTail("FFFF0000", false); - assertEndsWithTail("880000FFFF", true); - assertEndsWithTail("0000FFFF", true); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.1: A message compressed using 1 compressed DEFLATE block - */ - @Test - public void testDraft21HelloUnCompressedBlock() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex( - // basic, 1 block, compressed with 0 compression level (aka, uncompressed). - "0xc1 0x07", // (HEADER added for this test) - "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00" // example frame from RFC - ); - - tester.assertHasFrames("Hello"); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.1: A message compressed using 1 compressed DEFLATE block (with fragmentation) - */ - @Test - public void testDraft21HelloUnCompressedBlockFragmented() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// basic, 1 block, compressed with 0 compression level (aka, uncompressed). - // Fragment 1 - "0x41 0x03 0xf2 0x48 0xcd", - // Fragment 2 - "0x80 0x04 0xc9 0xc9 0x07 0x00"); - - tester.assertHasFrames( - new TextFrame().setPayload("He").setFin(false), - new ContinuationFrame().setPayload("llo").setFin(true)); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.2: Sharing LZ77 Sliding Window - */ - @Test - public void testDraft21SharingL77SlidingWindowContextTakeover() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// context takeover (2 messages) - // message 1 - "0xc1 0x07", // (HEADER added for this test) - "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00", - // message 2 - "0xc1 0x07", // (HEADER added for this test) - "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00"); - - tester.assertHasFrames("Hello", "Hello"); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.2: Sharing LZ77 Sliding Window - */ - @Test - public void testDraft21SharingL77SlidingWindowNoContextTakeover() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// 2 message, shared LZ77 window - // message 1 - "0xc1 0x07", // (HEADER added for this test) - "0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00", - // message 2 - "0xc1 0x05", // (HEADER added for this test) - "0xf2 0x00 0x11 0x00 0x00" - ); - - tester.assertHasFrames("Hello", "Hello"); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.3: Using a DEFLATE Block with No Compression - */ - @Test - public void testDraft21DeflateBlockWithNoCompression() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// 1 message / no compression - "0xc1 0x0b 0x00 0x05 0x00 0xfa 0xff 0x48 0x65 0x6c 0x6c 0x6f 0x00" // example frame - ); - - tester.assertHasFrames("Hello"); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.4: Using a DEFLATE Block with BFINAL Set to 1 - */ - @Test - public void testDraft21DeflateBlockWithBFinal1() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// 1 message - "0xc1 0x08", // header - "0xf3 0x48 0xcd 0xc9 0xc9 0x07 0x00 0x00" // example payload - ); - - tester.assertHasFrames("Hello"); - } - - /** - * Decode payload example as seen in draft-ietf-hybi-permessage-compression-21. - *

    - * Section 8.2.3.5: Two DEFLATE Blocks in 1 Message - */ - @Test - public void testDraft21TwoDeflateBlocksOneMessage() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// 1 message, 1 frame, 2 deflate blocks - "0xc1 0x0d", // (HEADER added for this test) - "0xf2 0x48 0x05 0x00 0x00 0x00 0xff 0xff 0xca 0xc9 0xc9 0x07 0x00" - ); - - tester.assertHasFrames("Hello"); - } - - /** - * Decode fragmented message (3 parts: TEXT, CONTINUATION, CONTINUATION) - */ - @Test - public void testParseFragmentedMessageGood() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - tester.parseIncomingHex(// 1 message, 3 frame - "410C", // HEADER TEXT / fin=false / rsv1=true - "F248CDC9C95700000000FFFF", - "000B", // HEADER CONTINUATION / fin=false / rsv1=false - "0ACF2FCA4901000000FFFF", - "8003", // HEADER CONTINUATION / fin=true / rsv1=false - "520400" - ); - - Frame txtFrame = new TextFrame().setPayload("Hello ").setFin(false); - Frame con1Frame = new ContinuationFrame().setPayload("World").setFin(false); - Frame con2Frame = new ContinuationFrame().setPayload("!").setFin(true); - - tester.assertHasFrames(txtFrame, con1Frame, con2Frame); - } - - /** - * Decode fragmented message (3 parts: TEXT, CONTINUATION, CONTINUATION) - *

    - * Continuation frames have RSV1 set, which MUST result in Failure - *

    - */ - @Test - public void testParseFragmentedMessageBadRsv1() { - Tester tester = clientExtensions.newTester("permessage-deflate"); - - tester.assertNegotiated("permessage-deflate"); - - assertThrows(ProtocolException.class, () -> - tester.parseIncomingHex(// 1 message, 3 frame - "410C", // Header TEXT / fin=false / rsv1=true - "F248CDC9C95700000000FFFF", // Payload - "400B", // Header CONTINUATION / fin=false / rsv1=true - "0ACF2FCA4901000000FFFF", // Payload - "C003", // Header CONTINUATION / fin=true / rsv1=true - "520400" // Payload - )); - } - - /** - * Incoming PING (Control Frame) should pass through extension unmodified - */ - @Test - public void testIncomingPing() { - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); - - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); - - // Wire up stack - ext.setNextIncomingFrames(capture); - - String payload = "Are you there?"; - Frame ping = new PingFrame().setPayload(payload); - ext.incomingFrame(ping); - - capture.assertFrameCount(1); - capture.assertHasFrame(OpCode.PING, 1); - WebSocketFrame actual = capture.getFrames().poll(); - - assertEquals(OpCode.PING, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(payload, StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice()); - } - - /** - * Incoming Text Message fragmented into 3 pieces. - */ - @Test - public void testIncomingFragmented() { - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); - - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); - - // Wire up stack - ext.setNextIncomingFrames(capture); - - String payload = "Are you there?"; - Frame ping = new PingFrame().setPayload(payload); - ext.incomingFrame(ping); - - capture.assertFrameCount(1); - capture.assertHasFrame(OpCode.PING, 1); - WebSocketFrame actual = capture.getFrames().poll(); - - assertEquals(OpCode.PING, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(payload, StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice()); - } - - /** - * Verify that incoming uncompressed frames are properly passed through - */ - @Test - public void testIncomingUncompressedFrames() { - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); - - // Setup capture of incoming frames - IncomingFramesCapture capture = new IncomingFramesCapture(); - - // Wire up stack - ext.setNextIncomingFrames(capture); - - // Quote - List quote = new ArrayList<>(); - quote.add("No amount of experimentation can ever prove me right;"); - quote.add("a single experiment can prove me wrong."); - quote.add("-- Albert Einstein"); - - // leave frames as-is, no compression, and pass into extension - for (String q : quote) { - TextFrame frame = new TextFrame().setPayload(q); - frame.setRsv1(false); // indication to extension that frame is not compressed (ie: a normal frame) - ext.incomingFrame(frame); - } - - int len = quote.size(); - capture.assertFrameCount(len); - capture.assertHasFrame(OpCode.TEXT, len); - - String prefix; - int i = 0; - for (WebSocketFrame actual : capture.getFrames()) { - prefix = "Frame[" + i + "]"; - - assertEquals(OpCode.TEXT, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(quote.get(i), StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice()); - i++; - } - } - - /** - * Outgoing PING (Control Frame) should pass through extension unmodified - * - * @throws IOException on test failure - */ - @Test - public void testOutgoingPing() throws IOException { - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); - - // Setup capture of outgoing frames - OutgoingFramesCapture capture = new OutgoingFramesCapture(); - - // Wire up stack - ext.setNextOutgoingFrames(capture); - - String payload = "Are you there?"; - Frame ping = new PingFrame().setPayload(payload); - - ext.outgoingFrame(ping, null); - - capture.assertFrameCount(1); - capture.assertHasFrame(OpCode.PING, 1); - - WebSocketFrame actual = capture.getFrames().getFirst(); - - assertEquals(OpCode.PING, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(payload, StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice()); - } - - /** - * Outgoing Fragmented Message - * - * @throws IOException on test failure - */ - @Test - public void testOutgoingFragmentedMessage() throws InterruptedException { - PerMessageDeflateExtension ext = new PerMessageDeflateExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("permessage-deflate"); - ext.setConfig(config); - - // Setup capture of outgoing frames - OutgoingFramesCapture capture = new OutgoingFramesCapture(); - - // Wire up stack - ext.setNextOutgoingFrames(capture); - - Frame txtFrame = new TextFrame().setPayload("Hello ").setFin(false); - Frame con1Frame = new ContinuationFrame().setPayload("World").setFin(false); - Frame con2Frame = new ContinuationFrame().setPayload("!").setFin(true); - ext.outgoingFrame(txtFrame, null); - ext.outgoingFrame(con1Frame, null); - ext.outgoingFrame(con2Frame, null); - - capture.assertFrameCount(3); - - WebSocketFrame capturedFrame; - - capturedFrame = capture.getFrames().poll(1, TimeUnit.SECONDS); - assertEquals(OpCode.TEXT, capturedFrame.getOpCode()); - assertFalse(capturedFrame.isFin()); - assertTrue(capturedFrame.isRsv1()); - assertFalse(capturedFrame.isRsv2()); - assertFalse(capturedFrame.isRsv3()); - - capturedFrame = capture.getFrames().poll(1, TimeUnit.SECONDS); - assertEquals(OpCode.CONTINUATION, capturedFrame.getOpCode()); - assertFalse(capturedFrame.isFin()); - assertFalse(capturedFrame.isRsv1()); - assertFalse(capturedFrame.isRsv2()); - assertFalse(capturedFrame.isRsv3()); - - capturedFrame = capture.getFrames().poll(1, TimeUnit.SECONDS); - assertEquals(OpCode.CONTINUATION, capturedFrame.getOpCode()); - assertTrue(capturedFrame.isFin()); - assertFalse(capturedFrame.isRsv1()); - assertFalse(capturedFrame.isRsv2()); - assertFalse(capturedFrame.isRsv3()); - } - - @Test - public void testPyWebSocketClientNoContextTakeoverThreeOra() { - Tester tester = clientExtensions.newTester("permessage-deflate; client_max_window_bits; client_no_context_takeover"); - - tester.assertNegotiated("permessage-deflate"); - - // Captured from Pywebsocket (r790) - 3 messages with similar parts. - - tester.parseIncomingHex(// context takeover (3 messages) - "c1 09 0a c9 2f 4a 0c 01 62 00 00", // ToraTora - "c1 0b 72 2c c9 2f 4a 74 cb 01 12 00 00", // AtoraFlora - "c1 0b 0a c8 c8 c9 2f 4a 0c 01 62 00 00" // PhloraTora - ); - - tester.assertHasFrames("ToraTora", "AtoraFlora", "PhloraTora"); - } - - @Test - public void testPyWebSocketClientToraToraTora() { - Tester tester = clientExtensions.newTester("permessage-deflate; client_max_window_bits"); - - tester.assertNegotiated("permessage-deflate"); - - // Captured from Pywebsocket (r790) - "tora" sent 3 times. - - tester.parseIncomingHex(// context takeover (3 messages) - "c1 06 2a c9 2f 4a 04 00", // tora 1 - "c1 05 2a 01 62 00 00", // tora 2 - "c1 04 02 61 00 00" // tora 3 - ); - - tester.assertHasFrames("tora", "tora", "tora"); - } - - @Test - public void testPyWebSocketServerNoContextTakeoverThreeOra() { - Tester tester = serverExtensions.newTester("permessage-deflate; client_max_window_bits; client_no_context_takeover"); - - tester.assertNegotiated("permessage-deflate"); - - // Captured from Pywebsocket (r790) - 3 messages with similar parts. - - tester.parseIncomingHex(// context takeover (3 messages) - "c1 89 88 bc 1b b1 82 75 34 fb 84 bd 79 b1 88", // ToraTora - "c1 8b 50 86 88 b2 22 aa 41 9d 1a f2 43 b3 42 86 88", // AtoraFlora - "c1 8b e2 3e 05 53 e8 f6 cd 9a cd 74 09 52 80 3e 05" // PhloraTora - ); - - tester.assertHasFrames("ToraTora", "AtoraFlora", "PhloraTora"); - } - - @Test - public void testPyWebSocketServerToraToraTora() { - Tester tester = serverExtensions.newTester("permessage-deflate; client_max_window_bits"); - - tester.assertNegotiated("permessage-deflate"); - - // Captured from Pywebsocket (r790) - "tora" sent 3 times. - - tester.parseIncomingHex(// context takeover (3 messages) - "c1 86 69 39 fe 91 43 f0 d1 db 6d 39", // tora 1 - "c1 85 2d f3 eb 96 07 f2 89 96 2d", // tora 2 - "c1 84 53 ad a5 34 51 cc a5 34" // tora 3 - ); - - tester.assertHasFrames("tora", "tora", "tora"); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/fragment/FragmentExtensionTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/fragment/FragmentExtensionTest.java deleted file mode 100644 index fecb523ca..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/extension/fragment/FragmentExtensionTest.java +++ /dev/null @@ -1,335 +0,0 @@ -package com.fireflysource.net.websocket.common.extension.fragment; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.frame.*; -import com.fireflysource.net.websocket.common.model.*; -import com.fireflysource.net.websocket.common.utils.ByteBufferAssert; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.function.Consumer; - -import static com.fireflysource.common.sys.Result.futureToConsumer; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.*; - - -public class FragmentExtensionTest { - - /** - * Verify that incoming frames are passed thru without modification - */ - @Test - public void testIncomingFrames() { - IncomingFramesCapture capture = new IncomingFramesCapture(); - - FragmentExtension ext = new FragmentExtension(); - ext.setPolicy(WebSocketPolicy.newClientPolicy()); - ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=4"); - ext.setConfig(config); - - ext.setNextIncomingFrames(capture); - - // Quote - List quote = new ArrayList<>(); - quote.add("No amount of experimentation can ever prove me right;"); - quote.add("a single experiment can prove me wrong."); - quote.add("-- Albert Einstein"); - - // Manually create frame and pass into extension - for (String q : quote) { - Frame frame = new TextFrame().setPayload(q); - ext.incomingFrame(frame); - } - - int len = quote.size(); - capture.assertFrameCount(len); - capture.assertHasFrame(OpCode.TEXT, len); - - String prefix; - int i = 0; - for (WebSocketFrame actual : capture.getFrames()) { - prefix = "Frame[" + i + "]"; - - assertEquals(OpCode.TEXT, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(quote.get(i), StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals(prefix + ".payload", expected, actual.getPayload().slice()); - i++; - } - } - - /** - * Incoming PING (Control Frame) should pass through extension unmodified - */ - @Test - public void testIncomingPing() { - IncomingFramesCapture capture = new IncomingFramesCapture(); - - FragmentExtension ext = new FragmentExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=4"); - ext.setConfig(config); - - ext.setNextIncomingFrames(capture); - - String payload = "Are you there?"; - Frame ping = new PingFrame().setPayload(payload); - ext.incomingFrame(ping); - - capture.assertFrameCount(1); - capture.assertHasFrame(OpCode.PING, 1); - WebSocketFrame actual = capture.getFrames().poll(); - - assertEquals(OpCode.PING, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(payload, StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice()); - } - - /** - * Verify that outgoing text frames are fragmented by the maxLength configuration. - * - * @throws IOException on test failure - */ - @Test - public void testOutgoingFramesByMaxLength() throws IOException, InterruptedException { - OutgoingFramesCapture capture = new OutgoingFramesCapture(); - - FragmentExtension ext = new FragmentExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=20"); - ext.setConfig(config); - - ext.setNextOutgoingFrames(capture); - - // Quote - List quote = new ArrayList<>(); - quote.add("No amount of experimentation can ever prove me right;"); - quote.add("a single experiment can prove me wrong."); - quote.add("-- Albert Einstein"); - - // Write quote as separate frames - for (String section : quote) { - Frame frame = new TextFrame().setPayload(section); - ext.outgoingFrame(frame, null); - } - - // Expected Frames - List expectedFrames = new ArrayList<>(); - expectedFrames.add(new TextFrame().setPayload("No amount of experim").setFin(false)); - expectedFrames.add(new ContinuationFrame().setPayload("entation can ever pr").setFin(false)); - expectedFrames.add(new ContinuationFrame().setPayload("ove me right;").setFin(true)); - - expectedFrames.add(new TextFrame().setPayload("a single experiment ").setFin(false)); - expectedFrames.add(new ContinuationFrame().setPayload("can prove me wrong.").setFin(true)); - - expectedFrames.add(new TextFrame().setPayload("-- Albert Einstein").setFin(true)); - - // capture.dump(); - - int len = expectedFrames.size(); - capture.assertFrameCount(len); - - String prefix; - LinkedBlockingDeque frames = capture.getFrames(); - for (int i = 0; i < len; i++) { - prefix = "Frame[" + i + "]"; - WebSocketFrame actualFrame = frames.poll(1, SECONDS); - WebSocketFrame expectedFrame = expectedFrames.get(i); - - // System.out.printf("actual: %s%n",actualFrame); - // System.out.printf("expect: %s%n",expectedFrame); - - // Validate Frame - assertEquals(expectedFrame.getOpCode(), actualFrame.getOpCode()); - assertEquals(expectedFrame.isFin(), actualFrame.isFin()); - assertEquals(expectedFrame.isRsv1(), actualFrame.isRsv1()); - assertEquals(expectedFrame.isRsv2(), actualFrame.isRsv2()); - assertEquals(expectedFrame.isRsv3(), actualFrame.isRsv3()); - - // Validate Payload - ByteBuffer expectedData = expectedFrame.getPayload().slice(); - ByteBuffer actualData = actualFrame.getPayload().slice(); - - assertEquals(expectedData.remaining(), actualData.remaining()); - ByteBufferAssert.assertEquals(prefix + ".payload", expectedData, actualData); - } - } - - /** - * Verify that outgoing text frames are not fragmented by default configuration (which has no maxLength specified) - * - * @throws IOException on test failure - */ - @Test - public void testOutgoingFramesDefaultConfig() throws Exception { - OutgoingFramesCapture capture = new OutgoingFramesCapture(); - - FragmentExtension ext = new FragmentExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("fragment"); - ext.setConfig(config); - - ext.setNextOutgoingFrames(capture); - - // Quote - List quote = new ArrayList<>(); - quote.add("No amount of experimentation can ever prove me right;"); - quote.add("a single experiment can prove me wrong."); - quote.add("-- Albert Einstein"); - - // Write quote as separate frames - for (String section : quote) { - Frame frame = new TextFrame().setPayload(section); - ext.outgoingFrame(frame, null); - } - - // Expected Frames - List expectedFrames = new ArrayList<>(); - expectedFrames.add(new TextFrame().setPayload("No amount of experimentation can ever prove me right;")); - expectedFrames.add(new TextFrame().setPayload("a single experiment can prove me wrong.")); - expectedFrames.add(new TextFrame().setPayload("-- Albert Einstein")); - - // capture.dump(); - - int len = expectedFrames.size(); - capture.assertFrameCount(len); - - String prefix; - LinkedBlockingDeque frames = capture.getFrames(); - for (int i = 0; i < len; i++) { - prefix = "Frame[" + i + "]"; - WebSocketFrame actualFrame = frames.poll(1, SECONDS); - WebSocketFrame expectedFrame = expectedFrames.get(i); - - // Validate Frame - assertEquals(expectedFrame.getOpCode(), actualFrame.getOpCode()); - assertEquals(expectedFrame.isFin(), actualFrame.isFin()); - assertEquals(expectedFrame.isRsv1(), actualFrame.isRsv1()); - assertEquals(expectedFrame.isRsv2(), actualFrame.isRsv2()); - assertEquals(expectedFrame.isRsv3(), actualFrame.isRsv3()); - - // Validate Payload - ByteBuffer expectedData = expectedFrame.getPayload().slice(); - ByteBuffer actualData = actualFrame.getPayload().slice(); - - assertEquals(expectedData.remaining(), actualData.remaining()); - ByteBufferAssert.assertEquals(prefix + ".payload", expectedData, actualData); - } - } - - /** - * Outgoing PING (Control Frame) should pass through extension unmodified - * - * @throws IOException on test failure - */ - @Test - public void testOutgoingPing() throws IOException { - OutgoingFramesCapture capture = new OutgoingFramesCapture(); - - FragmentExtension ext = new FragmentExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=4"); - ext.setConfig(config); - - ext.setNextOutgoingFrames(capture); - - String payload = "Are you there?"; - Frame ping = new PingFrame().setPayload(payload); - - ext.outgoingFrame(ping, null); - - capture.assertFrameCount(1); - capture.assertHasFrame(OpCode.PING, 1); - - WebSocketFrame actual = capture.getFrames().getFirst(); - - assertEquals(OpCode.PING, actual.getOpCode()); - assertTrue(actual.isFin()); - assertFalse(actual.isRsv1()); - assertFalse(actual.isRsv2()); - assertFalse(actual.isRsv3()); - - ByteBuffer expected = BufferUtils.toBuffer(payload, StandardCharsets.UTF_8); - assertEquals(expected.remaining(), actual.getPayloadLength()); - ByteBufferAssert.assertEquals("Frame.payload", expected, actual.getPayload().slice()); - } - - /** - * Ensure that FragmentExtension honors the correct order of websocket frames. - */ - @Test - public void testLargeSmallTextAlternating() throws Exception { - final int largeMessageSize = 60000; - byte[] buf = new byte[largeMessageSize]; - Arrays.fill(buf, (byte) 'x'); - String largeMessage = new String(buf, UTF_8); - - final int fragmentCount = 10; - final int fragmentLength = largeMessageSize / fragmentCount; - final int messageCount = 10000; - - FragmentExtension ext = new FragmentExtension(); - ext.setPolicy(WebSocketPolicy.newServerPolicy()); - ExtensionConfig config = ExtensionConfig.parse("fragment;maxLength=" + fragmentLength); - ext.setConfig(config); - SaneFrameOrderingAssertion saneFrameOrderingAssertion = new SaneFrameOrderingAssertion(); - ext.setNextOutgoingFrames(saneFrameOrderingAssertion); - - CompletableFuture enqueuedFrameCountFut = new CompletableFuture<>(); - - CompletableFuture.runAsync(() -> - { - // Run Server Task - int frameCount = 0; - try { - for (int i = 0; i < messageCount; i++) { - int messageId = i; - CompletableFuture future = new CompletableFuture<>(); - Consumer> result = futureToConsumer(future); - WebSocketFrame frame; - if (i % 2 == 0) { - frame = new TextFrame().setPayload(largeMessage); - frameCount += fragmentCount; - } else { - frame = new TextFrame().setPayload("Short Message: " + i); - frameCount++; - } - ext.outgoingFrame(frame, result); - future.get(); - } - enqueuedFrameCountFut.complete(frameCount); - } catch (Throwable t) { - enqueuedFrameCountFut.completeExceptionally(t); - } - }); - - int enqueuedFrameCount = enqueuedFrameCountFut.get(5, SECONDS); - - int expectedFrameCount = (messageCount / 2) * fragmentCount; // large messages - expectedFrameCount += (messageCount / 2); // + short messages - - assertEquals(expectedFrameCount, saneFrameOrderingAssertion.frameCount); - assertEquals(expectedFrameCount, enqueuedFrameCount); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/frame/WebSocketFrameTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/frame/WebSocketFrameTest.java deleted file mode 100644 index d3a27c329..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/frame/WebSocketFrameTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.fireflysource.net.websocket.common.frame; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.encoder.Generator; -import com.fireflysource.net.websocket.common.model.CloseInfo; -import com.fireflysource.net.websocket.common.model.StatusCode; -import com.fireflysource.net.websocket.common.model.WebSocketPolicy; -import com.fireflysource.net.websocket.common.utils.Hex; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -public class WebSocketFrameTest { - - private Generator strictGenerator; - private Generator laxGenerator; - - private ByteBuffer generateWholeFrame(Generator generator, Frame frame) { - ByteBuffer buf = ByteBuffer.allocate(frame.getPayloadLength() + Generator.MAX_HEADER_LENGTH); - generator.generateWholeFrame(frame, buf); - BufferUtils.flipToFlush(buf, 0); - return buf; - } - - @BeforeEach - public void initGenerator() { - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - strictGenerator = new Generator(policy); - laxGenerator = new Generator(policy, false); - } - - private void assertFrameHex(String message, String expectedHex, ByteBuffer actual) { - String actualHex = Hex.asHex(actual); - assertEquals(expectedHex, actualHex, message); - } - - @Test - public void testLaxInvalidClose() { - WebSocketFrame frame = new CloseFrame().setFin(false); - ByteBuffer actual = generateWholeFrame(laxGenerator, frame); - String expected = "0800"; - assertFrameHex("Lax Invalid Close Frame", expected, actual); - } - - @Test - public void testLaxInvalidPing() { - WebSocketFrame frame = new PingFrame().setFin(false); - ByteBuffer actual = generateWholeFrame(laxGenerator, frame); - String expected = "0900"; - assertFrameHex("Lax Invalid Ping Frame", expected, actual); - } - - @Test - public void testStrictValidClose() { - CloseInfo close = new CloseInfo(StatusCode.NORMAL); - ByteBuffer actual = generateWholeFrame(strictGenerator, close.asFrame()); - String expected = "880203E8"; - assertFrameHex("Strict Valid Close Frame", expected, actual); - } - - @Test - public void testStrictValidPing() { - WebSocketFrame frame = new PingFrame(); - ByteBuffer actual = generateWholeFrame(strictGenerator, frame); - String expected = "8900"; - assertFrameHex("Strict Valid Ping Frame", expected, actual); - } - - @Test - public void testRsv1() { - TextFrame frame = new TextFrame(); - frame.setPayload("Hi"); - frame.setRsv1(true); - laxGenerator.setRsv1InUse(true); - ByteBuffer actual = generateWholeFrame(laxGenerator, frame); - String expected = "C1024869"; - assertFrameHex("Lax Text Frame with RSV1", expected, actual); - } - - @Test - public void testRsv2() { - TextFrame frame = new TextFrame(); - frame.setPayload("Hi"); - frame.setRsv2(true); - laxGenerator.setRsv2InUse(true); - ByteBuffer actual = generateWholeFrame(laxGenerator, frame); - String expected = "A1024869"; - assertFrameHex("Lax Text Frame with RSV2", expected, actual); - } - - @Test - public void testRsv3() { - TextFrame frame = new TextFrame(); - frame.setPayload("Hi"); - frame.setRsv3(true); - laxGenerator.setRsv3InUse(true); - ByteBuffer actual = generateWholeFrame(laxGenerator, frame); - String expected = "91024869"; - assertFrameHex("Lax Text Frame with RSV3", expected, actual); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/AcceptHashTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/AcceptHashTest.java deleted file mode 100644 index 88b6f427e..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/AcceptHashTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.object.TypeUtils; -import org.junit.jupiter.api.Test; - -import java.util.Base64; - -import static org.junit.jupiter.api.Assertions.assertEquals; - - -public class AcceptHashTest { - - @Test - public void testHash() { - byte[] key = TypeUtils.fromHexString("00112233445566778899AABBCCDDEEFF"); - assertEquals(16, key.length); - - // what the client sends - String clientKey = Base64.getEncoder().encodeToString(key); - // what the server responds with - String serverHash = AcceptHash.hashKey(clientKey); - - // how the client validates - assertEquals("mVL6JKtNRC4tluIaFAW2hhMffgE=", serverHash); - } - - /** - * Test of values present in RFC-6455. - *

    - * Note: client key bytes are "7468652073616d706c65206e6f6e6365" - */ - @Test - public void testRfcHashExample() { - // What the client sends in the RFC - String clientKey = "dGhlIHNhbXBsZSBub25jZQ=="; - - // What the server responds with - String serverAccept = AcceptHash.hashKey(clientKey); - String expectedHash = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="; - - assertEquals(expectedHash, serverAccept); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/CloseInfoTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/CloseInfoTest.java deleted file mode 100644 index 11ad7f9b2..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/CloseInfoTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.string.StringUtils; -import com.fireflysource.net.websocket.common.frame.CloseFrame; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.*; - -public class CloseInfoTest { - - /** - * A test where no close is provided - */ - @Test - public void testAnonymousClose() { - CloseInfo close = new CloseInfo(); - Assertions.assertEquals(StatusCode.NO_CODE, close.getStatusCode()); - assertNull(close.getReason()); - - CloseFrame frame = close.asFrame(); - assertEquals(OpCode.CLOSE, frame.getOpCode()); - // should result in no payload - assertFalse(frame.hasPayload()); - assertEquals(0, frame.getPayloadLength()); - } - - /** - * A test where NO_CODE (1005) is provided - */ - @Test - public void testNoCode() { - CloseInfo close = new CloseInfo(StatusCode.NO_CODE); - Assertions.assertEquals(StatusCode.NO_CODE, close.getStatusCode()); - assertNull(close.getReason()); - - CloseFrame frame = close.asFrame(); - assertEquals(OpCode.CLOSE, frame.getOpCode()); - // should result in no payload - assertFalse(frame.hasPayload()); - assertEquals(0, frame.getPayloadLength()); - } - - /** - * A test where NO_CLOSE (1006) is provided - */ - @Test - public void testNoClose() { - CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE); - Assertions.assertEquals(StatusCode.NO_CLOSE, close.getStatusCode()); - assertNull(close.getReason()); - - CloseFrame frame = close.asFrame(); - assertEquals(OpCode.CLOSE, frame.getOpCode()); - // should result in no payload - assertFalse(frame.hasPayload()); - assertEquals(0, frame.getPayloadLength()); - } - - /** - * A test of FAILED_TLS_HANDSHAKE (1007) - */ - @Test - public void testFailedTlsHandshake() { - CloseInfo close = new CloseInfo(StatusCode.FAILED_TLS_HANDSHAKE); - Assertions.assertEquals(StatusCode.FAILED_TLS_HANDSHAKE, close.getStatusCode()); - assertNull(close.getReason()); - - CloseFrame frame = close.asFrame(); - assertEquals(OpCode.CLOSE, frame.getOpCode()); - // should result in no payload - assertFalse(frame.hasPayload()); - assertEquals(0, frame.getPayloadLength()); - } - - /** - * A test of NORMAL (1000) - */ - @Test - public void testNormal() { - CloseInfo close = new CloseInfo(StatusCode.NORMAL); - Assertions.assertEquals(StatusCode.NORMAL, close.getStatusCode()); - assertNull(close.getReason()); - - CloseFrame frame = close.asFrame(); - assertEquals(OpCode.CLOSE, frame.getOpCode()); - assertEquals(2, frame.getPayloadLength()); - } - - private ByteBuffer asByteBuffer(int statusCode, String reason) { - int len = 2; // status code length - byte[] utf = null; - if (StringUtils.hasText(reason)) { - utf = StringUtils.getUtf8Bytes(reason); - len += utf.length; - } - - ByteBuffer buf = BufferUtils.allocate(len); - BufferUtils.flipToFill(buf); - buf.put((byte) ((statusCode >>> 8) & 0xFF)); - buf.put((byte) ((statusCode >>> 0) & 0xFF)); - - if (utf != null) { - buf.put(utf, 0, utf.length); - } - BufferUtils.flipToFlush(buf, 0); - - return buf; - } - - @Test - public void testFromFrame() { - ByteBuffer payload = asByteBuffer(StatusCode.NORMAL, null); - assertEquals(2, payload.remaining()); - CloseFrame frame = new CloseFrame(); - frame.setPayload(payload); - - // create from frame - CloseInfo close = new CloseInfo(frame); - Assertions.assertEquals(StatusCode.NORMAL, close.getStatusCode()); - assertNull(close.getReason()); - - // and back again - frame = close.asFrame(); - assertEquals(OpCode.CLOSE, frame.getOpCode()); - assertEquals(2, frame.getPayloadLength()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/ExtensionConfigTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/ExtensionConfigTest.java deleted file mode 100644 index 24e58361e..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/ExtensionConfigTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -public class ExtensionConfigTest { - - private void assertConfig(ExtensionConfig cfg, String expectedName, Map expectedParams) { - assertEquals(expectedName, cfg.getName()); - - Map actualParams = cfg.getParameters(); - assertNotNull(actualParams); - assertEquals(expectedParams.size(), actualParams.size()); - - for (String expectedKey : expectedParams.keySet()) { - assertTrue(actualParams.containsKey(expectedKey)); - - String expectedValue = expectedParams.get(expectedKey); - String actualValue = actualParams.get(expectedKey); - - assertEquals(expectedValue, actualValue); - } - } - - @Test - public void testParseMuxExample() { - ExtensionConfig cfg = ExtensionConfig.parse("mux; max-channels=4; flow-control"); - Map expectedParams = new HashMap<>(); - expectedParams.put("max-channels", "4"); - expectedParams.put("flow-control", null); - assertConfig(cfg, "mux", expectedParams); - } - - @Test - public void testParsePerMessageCompressExample1() { - ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=foo"); - Map expectedParams = new HashMap<>(); - expectedParams.put("method", "foo"); - assertConfig(cfg, "permessage-compress", expectedParams); - } - - @Test - public void testParsePerMessageCompressExample2() { - ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; x=10\""); - Map expectedParams = new HashMap<>(); - expectedParams.put("method", "foo; x=10"); - assertConfig(cfg, "permessage-compress", expectedParams); - } - - @Test - public void testParsePerMessageCompressExample3() { - ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo, bar\""); - Map expectedParams = new HashMap<>(); - expectedParams.put("method", "foo, bar"); - assertConfig(cfg, "permessage-compress", expectedParams); - } - - @Test - public void testParsePerMessageCompressExample4() { - ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; use_x, foo\""); - Map expectedParams = new HashMap<>(); - expectedParams.put("method", "foo; use_x, foo"); - assertConfig(cfg, "permessage-compress", expectedParams); - } - - @Test - public void testParsePerMessageCompressExample5() { - ExtensionConfig cfg = ExtensionConfig.parse("permessage-compress; method=\"foo; x=\\\"Hello World\\\", bar\""); - Map expectedParams = new HashMap<>(); - expectedParams.put("method", "foo; x=\"Hello World\", bar"); - assertConfig(cfg, "permessage-compress", expectedParams); - } - - @Test - public void testParseSimpleBasicParameters() { - ExtensionConfig cfg = ExtensionConfig.parse("bar; baz=2"); - Map expectedParams = new HashMap<>(); - expectedParams.put("baz", "2"); - assertConfig(cfg, "bar", expectedParams); - } - - @Test - public void testParseSimpleNoParameters() { - ExtensionConfig cfg = ExtensionConfig.parse("foo"); - Map expectedParams = new HashMap<>(); - assertConfig(cfg, "foo", expectedParams); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/IncomingFramesCapture.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/IncomingFramesCapture.java deleted file mode 100644 index b084f374e..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/IncomingFramesCapture.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; - -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -public class IncomingFramesCapture implements IncomingFrames { - private LinkedBlockingQueue frames = new LinkedBlockingQueue<>(); - - public void assertFrameCount(int expectedCount) { - if (frames.size() != expectedCount) { - // dump details - System.err.printf("Expected %d frame(s)%n", expectedCount); - System.err.printf("But actually captured %d frame(s)%n", frames.size()); - int i = 0; - for (Frame frame : frames) { - System.err.printf(" [%d] Frame[%s] - %s%n", i++, - OpCode.name(frame.getOpCode()), - BufferUtils.toDetailString(frame.getPayload())); - } - } - assertEquals(expectedCount, frames.size()); - } - - public void assertHasFrame(byte op) { - assertTrue(op >= 1); - } - - public void assertHasFrame(byte op, int expectedCount) { - String msg = String.format("%s frame count", OpCode.name(op)); - assertEquals(expectedCount, getFrameCount(op)); - } - - public void assertHasNoFrames() { - assertEquals(0, frames.size()); - } - - public void clear() { - frames.clear(); - } - - public void dump() { - System.err.printf("Captured %d incoming frames%n", frames.size()); - int i = 0; - for (Frame frame : frames) { - System.err.printf("[%3d] %s%n", i++, frame); - System.err.printf(" payload: %s%n", BufferUtils.toDetailString(frame.getPayload())); - } - } - - public int getFrameCount(byte op) { - int count = 0; - for (WebSocketFrame frame : frames) { - if (frame.getOpCode() == op) { - count++; - } - } - return count; - } - - public Queue getFrames() { - return frames; - } - - @Override - public void incomingFrame(Frame frame) { - WebSocketFrame copy = WebSocketFrame.copy(frame); - // TODO: might need to make this optional (depending on use by client vs server tests) - // assertThat("frame.masking must be set",frame.isMasked(),is(true)); - frames.add(copy); - } - - public int size() { - return frames.size(); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/OutgoingFramesCapture.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/OutgoingFramesCapture.java deleted file mode 100644 index de9e3fc1f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/OutgoingFramesCapture.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.frame.Frame; -import com.fireflysource.net.websocket.common.frame.WebSocketFrame; - -import java.util.concurrent.LinkedBlockingDeque; -import java.util.function.Consumer; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class OutgoingFramesCapture implements OutgoingFrames { - private LinkedBlockingDeque frames = new LinkedBlockingDeque<>(); - - public void assertFrameCount(int expectedCount) { - assertEquals(expectedCount, frames.size()); - } - - public void assertHasFrame(byte op) { - assertTrue(getFrameCount(op) >= 1); - } - - public void assertHasFrame(byte op, int expectedCount) { - assertEquals(expectedCount, getFrameCount(op)); - } - - public void assertHasNoFrames() { - assertEquals(0, frames.size()); - } - - public void dump() { - System.out.printf("Captured %d outgoing writes%n", frames.size()); - int i = 0; - for (WebSocketFrame frame : frames) { - System.out.printf("[%3d] %s%n", i, frame); - System.out.printf(" %s%n", BufferUtils.toDetailString(frame.getPayload())); - i++; - } - } - - public int getFrameCount(byte op) { - int count = 0; - for (WebSocketFrame frame : frames) { - if (frame.getOpCode() == op) { - count++; - } - } - return count; - } - - public LinkedBlockingDeque getFrames() { - return frames; - } - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - frames.add(WebSocketFrame.copy(frame)); - if (result != null) { - result.accept(Result.SUCCESS); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/OutgoingNetworkBytesCapture.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/OutgoingNetworkBytesCapture.java deleted file mode 100644 index 56e02f67f..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/OutgoingNetworkBytesCapture.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.io.BufferUtils; -import com.fireflysource.common.object.TypeUtils; -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.encoder.Generator; -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.function.Consumer; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - - -/** - * Capture outgoing network bytes. - */ -public class OutgoingNetworkBytesCapture implements OutgoingFrames { - private final Generator generator; - private List captured; - - public OutgoingNetworkBytesCapture(Generator generator) { - this.generator = generator; - this.captured = new ArrayList<>(); - } - - public void assertBytes(int idx, String expectedHex) { - assertTrue(idx < captured.size()); - ByteBuffer buf = captured.get(idx); - String actualHex = TypeUtils.toHexString(BufferUtils.toArray(buf)).toUpperCase(Locale.ENGLISH); - assertEquals(expectedHex.toUpperCase(Locale.ENGLISH), actualHex); - } - - public List getCaptured() { - return captured; - } - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - ByteBuffer buf = ByteBuffer.allocate(Generator.MAX_HEADER_LENGTH + frame.getPayloadLength()); - generator.generateWholeFrame(frame, buf); - BufferUtils.flipToFlush(buf, 0); - captured.add(buf); - if (result != null) { - result.accept(Result.SUCCESS); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/SaneFrameOrderingAssertion.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/SaneFrameOrderingAssertion.java deleted file mode 100644 index c4d21c801..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/model/SaneFrameOrderingAssertion.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.websocket.common.model; - -import com.fireflysource.common.sys.Result; -import com.fireflysource.net.websocket.common.frame.Frame; - -import java.util.function.Consumer; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Useful for testing the production of sane frame ordering from various components. - */ -public class SaneFrameOrderingAssertion implements OutgoingFrames { - boolean priorDataFrame = false; - public int frameCount = 0; - - @Override - public void outgoingFrame(Frame frame, Consumer> result) { - byte opcode = frame.getOpCode(); - assertTrue(OpCode.isKnown(opcode)); - - switch (opcode) { - case OpCode.TEXT: - assertFalse(priorDataFrame, "Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION"); - break; - case OpCode.BINARY: - assertFalse(priorDataFrame, "Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION"); - break; - case OpCode.CONTINUATION: - assertTrue(priorDataFrame, "CONTINUATION frame without prior !FIN"); - break; - case OpCode.CLOSE: - assertFalse(frame.isFin(), "Fragmented Close Frame [" + OpCode.name(opcode) + "]"); - break; - case OpCode.PING: - assertFalse(frame.isFin(), "Fragmented Close Frame [" + OpCode.name(opcode) + "]"); - break; - case OpCode.PONG: - assertFalse(frame.isFin(), "Fragmented Close Frame [" + OpCode.name(opcode) + "]"); - break; - } - - if (OpCode.isDataFrame(opcode)) { - priorDataFrame = !frame.isFin(); - } - - frameCount++; - - if (result != null) - result.accept(Result.SUCCESS); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/ByteBufferAssert.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/ByteBufferAssert.java deleted file mode 100644 index e869056b6..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/ByteBufferAssert.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - - -import com.fireflysource.common.io.BufferUtils; -import org.junit.jupiter.api.Assertions; - -import java.nio.ByteBuffer; - - -public class ByteBufferAssert { - - public static void assertEquals(String message, byte[] expected, byte[] actual) { - Assertions.assertEquals(expected.length, actual.length); - int len = expected.length; - for (int i = 0; i < len; i++) { - Assertions.assertEquals(expected[i], actual[i]); - } - } - - public static void assertEquals(ByteBuffer expectedBuffer, ByteBuffer actualBuffer, String message) { - assertEquals(message, expectedBuffer, actualBuffer); - } - - public static void assertEquals(String message, ByteBuffer expectedBuffer, ByteBuffer actualBuffer) { - if (expectedBuffer == null) { - Assertions.assertNull(actualBuffer); - } else { - byte[] expectedBytes = BufferUtils.toArray(expectedBuffer); - byte[] actualBytes = BufferUtils.toArray(actualBuffer); - assertEquals(message, expectedBytes, actualBytes); - } - } - - public static void assertEquals(String message, String expectedString, ByteBuffer actualBuffer) { - String actualString = BufferUtils.toString(actualBuffer); - Assertions.assertEquals(expectedString, actualString); - } - - public static void assertSize(String message, int expectedSize, ByteBuffer buffer) { - if ((expectedSize == 0) && (buffer == null)) { - return; - } - Assertions.assertEquals(expectedSize, buffer.remaining()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/Hex.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/Hex.java deleted file mode 100644 index 3136c0b54..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/Hex.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - - -import com.fireflysource.common.io.BufferUtils; - -import java.nio.ByteBuffer; - -public final class Hex { - private static final char[] hexcodes = "0123456789ABCDEF".toCharArray(); - - public static byte[] asByteArray(String hstr) { - if ((hstr.length() < 0) || ((hstr.length() % 2) != 0)) { - throw new IllegalArgumentException(String.format("Invalid string length of <%d>", hstr.length())); - } - - int size = hstr.length() / 2; - byte[] buf = new byte[size]; - byte hex; - int len = hstr.length(); - - int idx = (int) Math.floor(((size * 2) - (double) len) / 2); - for (int i = 0; i < len; i++) { - hex = 0; - if (i >= 0) { - hex = (byte) (Character.digit(hstr.charAt(i), 16) << 4); - } - i++; - hex += (byte) (Character.digit(hstr.charAt(i), 16)); - - buf[idx] = hex; - idx++; - } - - return buf; - } - - public static ByteBuffer asByteBuffer(String hstr) { - return ByteBuffer.wrap(asByteArray(hstr)); - } - - public static String asHex(byte[] buf) { - int len = buf.length; - char[] out = new char[len * 2]; - for (int i = 0; i < len; i++) { - out[i * 2] = hexcodes[(buf[i] & 0xF0) >> 4]; - out[(i * 2) + 1] = hexcodes[(buf[i] & 0x0F)]; - } - return String.valueOf(out); - } - - public static String asHex(ByteBuffer buffer) { - return asHex(BufferUtils.toArray(buffer)); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/MaskedByteBuffer.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/MaskedByteBuffer.java deleted file mode 100644 index 5c7df20f8..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/MaskedByteBuffer.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - -import java.nio.ByteBuffer; - -public class MaskedByteBuffer { - private static byte[] mask = new byte[] - {0x00, (byte) 0xF0, 0x0F, (byte) 0xFF}; - - public static void putMask(ByteBuffer buffer) { - buffer.put(mask, 0, mask.length); - } - - public static void putPayload(ByteBuffer buffer, byte[] payload) { - int len = payload.length; - for (int i = 0; i < len; i++) { - buffer.put((byte) (payload[i] ^ mask[i % 4])); - } - } - - public static void putPayload(ByteBuffer buffer, ByteBuffer payload) { - int len = payload.remaining(); - for (int i = 0; i < len; i++) { - buffer.put((byte) (payload.get() ^ mask[i % 4])); - } - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/QuoteUtilQuoteTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/QuoteUtilQuoteTest.java deleted file mode 100644 index 63457528b..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/QuoteUtilQuoteTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Test QuoteUtil.quote(), and QuoteUtil.dequote() - */ -public class QuoteUtilQuoteTest { - public static Stream data() { - // The various quoting of a String - List data = new ArrayList<>(); - - data.add(new Object[]{"Hi", "\"Hi\""}); - data.add(new Object[]{"Hello World", "\"Hello World\""}); - data.add(new Object[]{"9.0.0", "\"9.0.0\""}); - data.add(new Object[]{ - "Something \"Special\"", - "\"Something \\\"Special\\\"\"" - }); - data.add(new Object[]{ - "A Few\n\"Good\"\tMen", - "\"A Few\\n\\\"Good\\\"\\tMen\"" - }); - - return data.stream().map(Arguments::of); - } - - @ParameterizedTest - @MethodSource("data") - public void testDequoting(final String unquoted, final String quoted) { - String actual = QuoteUtil.dequote(quoted); - actual = QuoteUtil.unescape(actual); - assertEquals(unquoted, actual); - } - - @ParameterizedTest - @MethodSource("data") - public void testQuoting(final String unquoted, final String quoted) { - StringBuilder buf = new StringBuilder(); - QuoteUtil.quote(buf, unquoted); - - String actual = buf.toString(); - assertEquals(quoted, actual); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/QuoteUtilTest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/QuoteUtilTest.java deleted file mode 100644 index bc6aead2c..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/QuoteUtilTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - -import org.junit.jupiter.api.Test; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test QuoteUtil - */ -public class QuoteUtilTest { - private void assertSplitAt(Iterator iter, String... expectedParts) { - int len = expectedParts.length; - for (int i = 0; i < len; i++) { - String expected = expectedParts[i]; - assertTrue(iter.hasNext()); - assertEquals(expected, iter.next()); - } - } - - @Test - public void testSplitAtPreserveQuoting() { - Iterator iter = QuoteUtil.splitAt("permessage-compress; method=\"foo, bar\"", ";"); - assertSplitAt(iter, "permessage-compress", "method=\"foo, bar\""); - } - - @Test - public void testSplitAtPreserveQuotingWithNestedDelim() { - Iterator iter = QuoteUtil.splitAt("permessage-compress; method=\"foo; x=10\"", ";"); - assertSplitAt(iter, "permessage-compress", "method=\"foo; x=10\""); - } - - @Test - public void testSplitAtAllWhitespace() { - Iterator iter = QuoteUtil.splitAt(" ", "="); - assertFalse(iter.hasNext()); - assertThrows(NoSuchElementException.class, () -> iter.next()); - } - - @Test - public void testSplitAtEmpty() { - Iterator iter = QuoteUtil.splitAt("", "="); - assertFalse(iter.hasNext()); - assertThrows(NoSuchElementException.class, () -> iter.next()); - } - - @Test - public void testSplitAtHelloWorld() { - Iterator iter = QuoteUtil.splitAt("Hello World", " ="); - assertSplitAt(iter, "Hello", "World"); - } - - @Test - public void testSplitAtKeyValueMessage() { - Iterator iter = QuoteUtil.splitAt("method=\"foo, bar\"", "="); - assertSplitAt(iter, "method", "foo, bar"); - } - - @Test - public void testSplitAtQuotedDelim() { - // test that split ignores delimiters that occur within a quoted - // part of the sequence. - Iterator iter = QuoteUtil.splitAt("A,\"B,C\",D", ","); - assertSplitAt(iter, "A", "B,C", "D"); - } - - @Test - public void testSplitAtSimple() { - Iterator iter = QuoteUtil.splitAt("Hi", "="); - assertSplitAt(iter, "Hi"); - } - - @Test - public void testSplitKeyValueQuoted() { - Iterator iter = QuoteUtil.splitAt("Key = \"Value\"", "="); - assertSplitAt(iter, "Key", "Value"); - } - - @Test - public void testSplitKeyValueQuotedValueList() { - Iterator iter = QuoteUtil.splitAt("Fruit = \"Apple, Banana, Cherry\"", "="); - assertSplitAt(iter, "Fruit", "Apple, Banana, Cherry"); - } - - @Test - public void testSplitKeyValueQuotedWithDelim() { - Iterator iter = QuoteUtil.splitAt("Key = \"Option=Value\"", "="); - assertSplitAt(iter, "Key", "Option=Value"); - } - - @Test - public void testSplitKeyValueSimple() { - Iterator iter = QuoteUtil.splitAt("Key=Value", "="); - assertSplitAt(iter, "Key", "Value"); - } - - @Test - public void testSplitKeyValueWithWhitespace() { - Iterator iter = QuoteUtil.splitAt("Key = Value", "="); - assertSplitAt(iter, "Key", "Value"); - } - - @Test - public void testQuoteIfNeeded() { - StringBuilder buf = new StringBuilder(); - QuoteUtil.quoteIfNeeded(buf, "key", ","); - assertEquals("key", buf.toString()); - } - - @Test - public void testQuoteIfNeedednull() { - StringBuilder buf = new StringBuilder(); - QuoteUtil.quoteIfNeeded(buf, null, ";="); - assertEquals("", buf.toString()); - } -} diff --git a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/WSURITest.java b/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/WSURITest.java deleted file mode 100644 index fdb0d95e8..000000000 --- a/firefly-net/src/test/java/com/fireflysource/net/websocket/common/utils/WSURITest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.fireflysource.net.websocket.common.utils; - -import org.junit.jupiter.api.Test; - -import java.net.URI; -import java.net.URISyntaxException; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class WSURITest { - private void assertURI(URI actual, URI expected) { - assertEquals(expected.toASCIIString(), actual.toASCIIString()); - } - - @Test - public void testHttpsToHttps() throws URISyntaxException { - assertURI(WSURI.toHttp(URI.create("https://localhost/")), URI.create("https://localhost/")); - } - - @Test - public void testHttpsToWss() throws URISyntaxException { - assertURI(WSURI.toWebsocket(URI.create("https://localhost/")), URI.create("wss://localhost/")); - } - - @Test - public void testHttpToHttp() throws URISyntaxException { - assertURI(WSURI.toHttp(URI.create("http://localhost/")), URI.create("http://localhost/")); - } - - @Test - public void testHttpToWs() throws URISyntaxException { - assertURI(WSURI.toWebsocket(URI.create("http://localhost/")), URI.create("ws://localhost/")); - assertURI(WSURI.toWebsocket(URI.create("http://localhost:8080/deeper/")), URI.create("ws://localhost:8080/deeper/")); - assertURI(WSURI.toWebsocket("http://localhost/"), URI.create("ws://localhost/")); - assertURI(WSURI.toWebsocket("http://localhost/", null), URI.create("ws://localhost/")); - assertURI(WSURI.toWebsocket("http://localhost/", "a=b"), URI.create("ws://localhost/?a=b")); - } - - @Test - public void testWssToHttps() throws URISyntaxException { - assertURI(WSURI.toHttp(URI.create("wss://localhost/")), URI.create("https://localhost/")); - } - - @Test - public void testWssToWss() throws URISyntaxException { - assertURI(WSURI.toWebsocket(URI.create("wss://localhost/")), URI.create("wss://localhost/")); - } - - @Test - public void testWsToHttp() throws URISyntaxException { - assertURI(WSURI.toHttp(URI.create("ws://localhost/")), URI.create("http://localhost/")); - } - - @Test - public void testWsToWs() throws URISyntaxException { - assertURI(WSURI.toWebsocket(URI.create("ws://localhost/")), URI.create("ws://localhost/")); - } -} diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/common/v2/stream/TestAsyncHttp2Connection.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/common/v2/stream/TestAsyncHttp2Connection.kt deleted file mode 100644 index 5a4ba7720..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/common/v2/stream/TestAsyncHttp2Connection.kt +++ /dev/null @@ -1,894 +0,0 @@ -package com.fireflysource.net.common.v2.stream - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.common.sys.Result.futureToConsumer -import com.fireflysource.net.http.client.impl.Http2ClientConnection -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.common.v2.frame.* -import com.fireflysource.net.http.common.v2.frame.SettingsFrame.DEFAULT_SETTINGS_FRAME -import com.fireflysource.net.http.common.v2.stream.AsyncHttp2Stream -import com.fireflysource.net.http.common.v2.stream.Http2Connection -import com.fireflysource.net.http.common.v2.stream.SimpleFlowControlStrategy -import com.fireflysource.net.http.common.v2.stream.Stream -import com.fireflysource.net.http.server.impl.Http2ServerConnection -import com.fireflysource.net.tcp.aio.AioTcpClient -import com.fireflysource.net.tcp.aio.AioTcpServer -import com.fireflysource.net.tcp.aio.TcpConfig -import com.fireflysource.net.tcp.onAcceptAsync -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.net.URL -import java.util.concurrent.CompletableFuture -import java.util.function.Consumer -import kotlin.system.measureTimeMillis - -class TestAsyncHttp2Connection { - - @Test - @DisplayName("should send priority frame successfully") - fun testPriority() = runBlocking { - val host = "localhost" - val port = 4026 - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream: $stream . the headers: $frame .") - - val fields = HttpFields() - fields.put("Test-New-Stream-Response", "R1") - if (frame.priority != null) { - fields.put("Stream-Priority", "${frame.priority.weight}") - fields.put("Dependency-Stream", "${frame.priority.parentStreamId}") - } - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val headersFrame = HeadersFrame(stream.id, response, null, false) - stream.headers(headersFrame) { println("Server response success.") } - - return Stream.Listener.Adapter() - } - } - ).begin() - }.listen(host, port) - - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val responseHeadersChannel = Channel(UNLIMITED) - val newStreamChannel = Channel(UNLIMITED) - val headersFrame = createRequestHeadersFrame() - http2Connection.newStream(headersFrame, - { - if (it.isSuccess) { - val success = newStreamChannel.trySend(it.value) - println("offer new stream success: $success .") - } else { - println("new a stream failed") - it.throwable?.printStackTrace() - } - }, - object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - responseHeadersChannel.trySend(frame) - } - }) - - val stream = newStreamChannel.receive() - val responseHeadersFrame = responseHeadersChannel.receive() - assertEquals(1, responseHeadersFrame.streamId) - assertTrue(responseHeadersFrame.metaData.isResponse) - assertEquals("R1", responseHeadersFrame.metaData.fields["Test-New-Stream-Response"]) - assertNull(responseHeadersFrame.metaData.fields["Dependency-Stream"]) - - - val priorityFrame = PriorityFrame(stream.id + 2, stream.id, 10, false) - val headersFrameWithPriority = createRequestHeadersFrame(priorityFrame) - http2Connection.newStream(headersFrameWithPriority, - { - if (it.isSuccess) { - val success = newStreamChannel.trySend(it.value) - println("offer new stream success: $success .") - } else { - println("new a stream failed") - it.throwable?.printStackTrace() - } - }, - object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - responseHeadersChannel.trySend(frame) - } - }) - - val future = CompletableFuture() - http2Connection.priority( - PriorityFrame(stream.id + 2, stream.id, 15, false), - futureToConsumer(future) - ) - - val stream2 = newStreamChannel.receive() - assertEquals(3, stream2.id) - assertFalse(stream2.isReset) - val responseHeadersFrame2 = responseHeadersChannel.receive() - assertEquals(3, responseHeadersFrame2.streamId) - assertTrue(responseHeadersFrame2.metaData.isResponse) - assertEquals("1", responseHeadersFrame2.metaData.fields["Dependency-Stream"]) - assertEquals("10", responseHeadersFrame2.metaData.fields["Stream-Priority"]) - - future.await() - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should reset stream successfully after the stream sends reset frame") - fun testResetFrame() = runBlocking { - val host = "localhost" - val port = 4025 - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - - val resetFrameChannel = Channel(UNLIMITED) - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onReset(http2Connection: Http2Connection, frame: ResetFrame) { - println("Server receives reset frame for an unknown stream. frame: $frame") - resetFrameChannel.trySend(frame) - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream: $stream . the headers: $frame .") - - val fields = HttpFields() - fields.put("Test-New-Stream-Response", "R1") - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val headersFrame = HeadersFrame(stream.id, response, null, false) - stream.headers(headersFrame) { println("Server response success.") } - - return object : Stream.Listener.Adapter() { - override fun onReset(stream: Stream, frame: ResetFrame) { - println("Server receives the reset frame: $frame .") - resetFrameChannel.trySend(frame) - } - } - } - } - ).begin() - }.listen(host, port) - - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val newStreamChannel = Channel(UNLIMITED) - val responseHeadersChannel = Channel(UNLIMITED) - val headersFrame = createRequestHeadersFrame() - http2Connection.newStream(headersFrame, - { - if (it.isSuccess) { - val success = newStreamChannel.trySend(it.value) - println("offer new stream success: $success .") - } else { - println("new a stream failed") - it.throwable?.printStackTrace() - } - }, - object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client receives headers: $frame") - responseHeadersChannel.trySend(frame) - } - - override fun onReset(stream: Stream, frame: ResetFrame) { - println("Client receives reset frame: $frame") - } - }) - - val time = measureTimeMillis { - val newStream = newStreamChannel.receive() - assertEquals(1, newStream.id) - assertFalse(newStream.isReset) - - val responseHeadersFrame = responseHeadersChannel.receive() - assertEquals(1, responseHeadersFrame.streamId) - assertTrue(responseHeadersFrame.metaData.isResponse) - assertEquals("R1", responseHeadersFrame.metaData.fields["Test-New-Stream-Response"]) - - val resetFrame = ResetFrame(newStream.id, ErrorCode.INTERNAL_ERROR.code) - newStream.reset(resetFrame) { - println("reset frame success. $it") - } - val serverReceivedResetFrame = resetFrameChannel.receive() - assertTrue(newStream.isReset) - assertEquals(1, serverReceivedResetFrame.streamId) - assertEquals(ErrorCode.INTERNAL_ERROR.code, serverReceivedResetFrame.error) - } - - println("reset stream time: $time ms") - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should create server push stream successfully") - fun testPushPromise() = runBlocking { - val host = "localhost" - val port = 4024 - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onPreface(http2Connection: Http2Connection): MutableMap { - println("Server receives the preface frame.") - return DEFAULT_SETTINGS_FRAME.settings - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream. stream: ${stream}, headers: $frame") - - val fields = HttpFields() - fields.put("Test-Push-Promise-Stream", "P1") - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val pushPromiseFrame = PushPromiseFrame(stream.id, 0, response) - stream.push(pushPromiseFrame, { - if (it.isSuccess) { - println("Server creates new push stream success. stream: ${it.value}") - } else { - println("new a push stream failed") - it.throwable?.printStackTrace() - } - }, Stream.Listener.Adapter()) - - return Stream.Listener.Adapter() - } - } - ).begin() - }.listen(host, port) - - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val newPushStreamChannel = Channel(UNLIMITED) - val pushPromiseChannel = Channel(UNLIMITED) - val headersFrame = createRequestHeadersFrame() - http2Connection.newStream(headersFrame, - { - if (it.isSuccess) { - println("Client creates new stream success. stream: ${it.value}") - } else { - println("new a stream failed") - it.throwable?.printStackTrace() - } - }, - object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - } - - override fun onPush(stream: Stream, frame: PushPromiseFrame): Stream.Listener { - val success = newPushStreamChannel.trySend(stream) - println("Client received push stream: $stream . $success , $frame") - - pushPromiseChannel.trySend(frame) - return Stream.Listener.Adapter() - } - }) - - val time = measureTimeMillis { - val newStream = newPushStreamChannel.receive() - assertEquals(2, newStream.id) - assertFalse(newStream.isReset) - - val frame = pushPromiseChannel.receive() - assertEquals(1, frame.streamId) - assertEquals(2, frame.promisedStreamId) - assertTrue(frame.metaData.isResponse) - assertEquals("P1", frame.metaData.fields["Test-Push-Promise-Stream"]) - } - println("push promise stream time: $time ms") - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should send data frame successfully after the stream creates.") - fun testData(): Unit = runTest { - val host = "localhost" - val port = 4027 - val tcpConfig = TcpConfig(30, true) - val httpConfig = HttpConfig() - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream: ${stream}. the headers: ${frame}.") - - val fields = HttpFields() - fields.put("Test-New-Stream-Response", "R1") - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val headersFrame = HeadersFrame(stream.id, response, null, false) - stream.headers(headersFrame) { println("Server response header success.") } - - val data = BufferUtils.toBuffer("test data frame.") - val dataFrame = DataFrame(stream.id, data, true) - stream.data(dataFrame) { println("Server response data success.") } - - return Stream.Listener.Adapter() - } - } - ).begin() - }.listen(host, port) - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val dataFrameChannel = Channel(UNLIMITED) - val headersFrame = createRequestHeadersFrame() - val future = http2Connection.newStream(headersFrame, object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - } - - override fun onData(stream: Stream, frame: DataFrame, result: Consumer>) { - println("Client received data frame: $frame") - dataFrameChannel.trySend(frame) - result.accept(Result.SUCCESS) - } - }) - val time = measureTimeMillis { - val newStream = future.await() - val dataFrame = dataFrameChannel.receive() - assertEquals(1, newStream.id) - assertFalse(newStream.isReset) - assertEquals("test data frame.", BufferUtils.toString(dataFrame.data)) - } - - println("receive data time: $time") - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should create a new stream successfully") - fun testNewStream() = runTest { - val host = "localhost" - val port = 4023 - val tcpConfig = TcpConfig(30, true) - val httpConfig = HttpConfig() - - val requestHeadersChannel = Channel(UNLIMITED) - val responseHeadersChannel = Channel(UNLIMITED) - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream: $stream . the headers: $frame .") - requestHeadersChannel.trySend(frame) - - val fields = HttpFields() - fields.put("Test-New-Stream-Response", "R1") - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val headersFrame = HeadersFrame(stream.id, response, null, true) - stream.headers(headersFrame) { println("Server response success.") } - - return Stream.Listener.Adapter() - } - } - ).begin() - }.listen(host, port) - - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val headersFrame = createRequestHeadersFrame() - val future = http2Connection.newStream(headersFrame, object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - responseHeadersChannel.trySend(frame) - } - }) - - val time = measureTimeMillis { - val newStream = future.await() - assertEquals(1, newStream.id) - assertFalse(newStream.isReset) - if (newStream is AsyncHttp2Stream) { - assertTrue(newStream.getSendWindow() > 0) - assertEquals(httpConfig.initialStreamRecvWindow, newStream.getRecvWindow()) - } - val http2ClientConnection = http2Connection as Http2ClientConnection - assertTrue(http2ClientConnection.getSendWindow() > 0) - assertEquals(httpConfig.initialSessionRecvWindow, http2ClientConnection.getRecvWindow()) - - val requestHeadersFrame = requestHeadersChannel.receive() - assertEquals(1, requestHeadersFrame.streamId) - assertTrue(requestHeadersFrame.metaData.isRequest) - assertEquals("V1", requestHeadersFrame.metaData.fields["Test-New-Stream"]) - - val responseHeadersFrame = responseHeadersChannel.receive() - assertEquals(1, responseHeadersFrame.streamId) - assertTrue(responseHeadersFrame.metaData.isResponse) - assertEquals("R1", responseHeadersFrame.metaData.fields["Test-New-Stream-Response"]) - } - println("new stream time: $time ms") - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - private fun createClientHttp2ConnectionListener(): Http2Connection.Listener.Adapter { - return object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Client receives go away frame: $frame") - } - } - } - - private fun createRequestHeadersFrame(priorityFrame: PriorityFrame? = null): HeadersFrame { - val httpFields = HttpFields() - httpFields.put("Test-New-Stream", "V1") - @Suppress("BlockingMethodInNonBlockingContext") - val request = MetaData.Request( - HttpMethod.GET.value, - HttpURI(URL("http://localhost:8888/test").toURI()), - HttpVersion.HTTP_2, - httpFields - ) - return HeadersFrame(request, priorityFrame, true) - } - - @Test - @DisplayName("should send go away frame successfully") - fun testGoAway() = runTest { - val host = "localhost" - val port = 4022 - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - } - ).begin() - }.listen(host, port) - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Client receives go away frame: $frame") - } - } - ) - - val success = http2Connection.close(ErrorCode.INTERNAL_ERROR.code, "test error message") - assertTrue(success) - - client.stop() - server.stop() - } - - @Test - @DisplayName("should send settings frame successfully") - fun testSettings() = runTest { - val host = "localhost" - val port = 4021 - val channel = Channel(UNLIMITED) - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - - val settingsFrame = SettingsFrame( - mutableMapOf( - SettingsFrame.HEADER_TABLE_SIZE to 8192, - SettingsFrame.ENABLE_PUSH to 1, - SettingsFrame.MAX_CONCURRENT_STREAMS to 300, - SettingsFrame.INITIAL_WINDOW_SIZE to 128 * 1024, - SettingsFrame.MAX_FRAME_SIZE to 1024 * 1024, - SettingsFrame.MAX_HEADER_LIST_SIZE to 64 - ), false - ) - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onSettings(http2Connection: Http2Connection, frame: SettingsFrame) { - println("server receives settings: $frame") - - if (frame.settings == settingsFrame.settings) { - val success = channel.trySend(frame) - println("put result settings frame: $success") - } - } - } - ).begin() - }.listen(host, port) - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onSettings(http2Connection: Http2Connection, frame: SettingsFrame) { - println("client receives settings: $frame") - } - } - ) - - http2Connection.settings(settingsFrame) { println("send settings success. $it") } - - val receivedSettings = channel.receive()//withTimeout(2000) { channel.receive() } - assertEquals(settingsFrame.settings, receivedSettings.settings) - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should send ping frame successfully") - fun testPing() = runTest { - val host = "localhost" - val port = 4020 - val count = 10L - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - - val channel = Channel(UNLIMITED) - - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - } - ).begin() - }.listen(host, port) - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onPing(http2Connection: Http2Connection, frame: PingFrame) { - println("Client receives the ping frame. ${frame.payloadAsLong}: ${frame.isReply}") - if (frame.payloadAsLong == count) { - val success = channel.trySend(frame.payloadAsLong) - println("put result ping frame: $success") - } - } - } - ) - - (1..count).forEach { index -> - val pingFrame = PingFrame(index, false) - http2Connection.ping(pingFrame) { println("send ping success. $it") } - } - - val pingCount = channel.receive()//withTimeout(20000) { channel.receive() } - assertTrue(pingCount > 0) - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should receive reset frame when the stream idle timeout") - fun testStreamIdleTimeout(): Unit = runTest { - val host = "localhost" - val port = 4100 - val tcpConfig = TcpConfig(30, false) - val serverHttpConfig = HttpConfig() - serverHttpConfig.streamIdleTimeout = 1 - - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - serverHttpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream: $stream . the headers: $frame .") - - val fields = HttpFields() - fields.put("Test-Idle-Timeout", "R1") - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val headersFrame = HeadersFrame(stream.id, response, null, false) - stream.headers(headersFrame) { println("Server response success.") } - return object : Stream.Listener.Adapter() { - override fun onReset(stream: Stream, frame: ResetFrame) { - println("Server received reset: $frame") - } - - override fun onIdleTimeout(stream: Stream, x: Throwable): Boolean { - println("${x.message}: $stream") - return true - } - } - } - } - ).begin() - }.listen(host, port) - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - HttpConfig(), connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val headersFrame = createRequestHeadersFrame() - val channel: Channel = Channel(UNLIMITED) - val future = http2Connection.newStream(headersFrame, object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - } - - override fun onReset(stream: Stream, frame: ResetFrame) { - println("Client received reset: $frame") - channel.trySend(frame) - } - }) - - val time = measureTimeMillis { - val newStream = future.await() - assertEquals(1, newStream.id) - val resetFrame = channel.receive() - assertEquals(1, resetFrame.streamId) - } - println("new stream time: $time ms") - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - - @Test - @DisplayName("should set the stream idle timeout successfully") - fun testSetStreamIdleTimeout(): Unit = runTest { - val host = "localhost" - val port = 4101 - val tcpConfig = TcpConfig(30, false) - val httpConfig = HttpConfig() - httpConfig.streamIdleTimeout = 30 - - val channel: Channel = Channel(UNLIMITED) - val server = AioTcpServer(tcpConfig).onAcceptAsync { connection -> - connection.beginHandshake().await() - Http2ServerConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - object : Http2Connection.Listener.Adapter() { - - override fun onFailure(http2Connection: Http2Connection, failure: Throwable) { - failure.printStackTrace() - } - - override fun onClose(http2Connection: Http2Connection, frame: GoAwayFrame) { - println("Server receives go away frame: $frame") - } - - override fun onNewStream(stream: Stream, frame: HeadersFrame): Stream.Listener { - println("Server creates the remote stream: $stream . the headers: $frame .") - - val fields = HttpFields() - fields.put("Test-Idle-Timeout", "R1") - val response = MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields) - val headersFrame = HeadersFrame(stream.id, response, null, false) - stream.headers(headersFrame) { println("Server response success.") } - return object : Stream.Listener.Adapter() { - override fun onReset(stream: Stream, frame: ResetFrame) { - println("Server received reset: $frame") - channel.trySend(frame) - } - - override fun onIdleTimeout(stream: Stream, x: Throwable): Boolean { - println("${x.message}: $stream") - return true - } - } - } - } - ).begin() - }.listen(host, port) - - val client = AioTcpClient(tcpConfig) - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val http2Connection: Http2Connection = Http2ClientConnection( - httpConfig, connection, SimpleFlowControlStrategy(), - createClientHttp2ConnectionListener() - ) - - val headersFrame = createRequestHeadersFrame() - - val future = http2Connection.newStream(headersFrame, object : Stream.Listener.Adapter() { - override fun onHeaders(stream: Stream, frame: HeadersFrame) { - println("Client received headers: $frame") - } - - override fun onReset(stream: Stream, frame: ResetFrame) { - println("Client received reset: $frame") - } - }) - - val time = measureTimeMillis { - val newStream = future.await() - assertEquals(1, newStream.id) - newStream.idleTimeout = 1 - val resetFrame = channel.receive() - assertEquals(1, resetFrame.streamId) - } - println("new stream time: $time ms") - - http2Connection.close(ErrorCode.NO_ERROR.code, "exit test") {} - - client.stop() - server.stop() - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/common/v2/stream/TestAsyncHttp2Stream.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/common/v2/stream/TestAsyncHttp2Stream.kt deleted file mode 100644 index 13976f56c..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/common/v2/stream/TestAsyncHttp2Stream.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.fireflysource.net.common.v2.stream - -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.net.http.common.v2.frame.CloseState -import com.fireflysource.net.http.common.v2.frame.ErrorCode -import com.fireflysource.net.http.common.v2.frame.ResetFrame -import com.fireflysource.net.http.common.v2.stream.AsyncHttp2Connection -import com.fireflysource.net.http.common.v2.stream.AsyncHttp2Stream -import com.fireflysource.net.http.common.v2.stream.Stream -import com.fireflysource.net.http.common.v2.stream.getAndIncreaseStreamId -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito -import org.mockito.Mockito.`when` -import org.mockito.Mockito.verify -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicInteger - -class TestAsyncHttp2Stream { - - private val asyncHttp2Connection = Mockito.mock(AsyncHttp2Connection::class.java) - - @Test - @DisplayName("should update stream window successfully") - fun testWindowUpdate() { - val stream = AsyncHttp2Stream(asyncHttp2Connection, 1, true, Stream.Listener.Adapter()) - val initRecvWindow = stream.updateRecvWindow(25) - assertEquals(0, initRecvWindow) - assertEquals(25, stream.getRecvWindow()) - - val initSendWindow = stream.updateSendWindow(13) - assertEquals(0, initSendWindow) - assertEquals(13, stream.getSendWindow()) - } - - @Test - @DisplayName("should close stream after the stream remote close and local close") - fun testCloseStream() { - val stream = AsyncHttp2Stream(asyncHttp2Connection, 1, true, Stream.Listener.Adapter()) - assertFalse(stream.isClosed) - - val result = stream.updateClose(true, CloseState.Event.RECEIVED) - assertFalse(result) - - val result1 = stream.updateClose(true, CloseState.Event.BEFORE_SEND) - assertFalse(result1) - - val result2 = stream.updateClose(true, CloseState.Event.AFTER_SEND) - assertTrue(result2) - assertTrue(stream.isClosed) - } - - @Test - @DisplayName("should stream reset when the reset frame sent") - fun testReset() { - val stream = AsyncHttp2Stream(asyncHttp2Connection, 1, true, Stream.Listener.Adapter()) - assertFalse(stream.isReset) - - val frame = ResetFrame(1, ErrorCode.INTERNAL_ERROR.code) - val future = CompletableFuture() - future.complete(0) - `when`(asyncHttp2Connection.sendControlFrame(stream, frame)).thenReturn(future) - stream.reset(frame, discard()) - verify(asyncHttp2Connection).sendControlFrame(stream, frame) - assertTrue(stream.isReset) - } - - @Test - @DisplayName("should get the initial stream id when the id exceeds max integer") - fun testStreamIdIncrease() { - val id1 = AtomicInteger(Integer.MAX_VALUE) - assertEquals(Integer.MAX_VALUE, getAndIncreaseStreamId(id1, 3)) - assertEquals(3, getAndIncreaseStreamId(id1, 3)) - - val id2 = AtomicInteger(Integer.MAX_VALUE - 1) - assertEquals(Integer.MAX_VALUE - 1, getAndIncreaseStreamId(id2, 2)) - assertEquals(2, getAndIncreaseStreamId(id2, 2)) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestAsyncHttpClientConnectionManager.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestAsyncHttpClientConnectionManager.kt deleted file mode 100644 index 50f1ff3a3..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestAsyncHttpClientConnectionManager.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.Http1ServerConnection -import com.fireflysource.net.tcp.TcpClientConnectionFactory -import com.fireflysource.net.tcp.TcpServer -import com.fireflysource.net.tcp.TcpServerFactory -import com.fireflysource.net.tcp.aio.AioTcpChannelGroup -import com.fireflysource.net.tcp.onAcceptAsync -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.net.InetSocketAddress -import java.net.URL -import java.util.concurrent.CompletableFuture -import kotlin.random.Random - -class TestAsyncHttpClientConnectionManager { - - private lateinit var address: InetSocketAddress - private lateinit var httpServer: TcpServer - - @BeforeEach - fun init() { - address = InetSocketAddress("localhost", Random.nextInt(2000, 5000)) - val listener = object : HttpServerConnection.Listener.Adapter() { - override fun onHttpRequestComplete(ctx: RoutingContext): CompletableFuture { - return ctx.put(HttpHeader.CONTENT_LENGTH, "7").end("test ok") - } - - override fun onException(context: RoutingContext, e: Throwable): CompletableFuture { - e.printStackTrace() - return Result.DONE - } - } - httpServer = TcpServerFactory.create().timeout(120 * 1000L).enableOutputBuffer() - .onAcceptAsync { connection -> - println("accept connection. ${connection.id}") - connection.beginHandshake().await() - val http1Connection = Http1ServerConnection(HttpConfig(), connection) - http1Connection.setListener(listener).begin() - }.listen(address) - } - - @AfterEach - fun destroy() { - httpServer.stop() - } - - @Test - fun test() = runTest { - val config = HttpConfig() - val connectionFactory = TcpClientConnectionFactory( - AioTcpChannelGroup("async-http-client"), - config.isStopTcpChannelGroup, - config.timeout, - config.secureEngineFactory - ) - val manager = AsyncHttpClientConnectionManager(config, connectionFactory) - - repeat(5) { - val request = AsyncHttpClientRequest() - request.method = HttpMethod.GET.value - @Suppress("BlockingMethodInNonBlockingContext") - request.uri = HttpURI(URL("http://${address.hostName}:${address.port}/test1").toURI()) - - val response = manager.send(request).await() - println("${response.status} ${response.reason}") - println(response.httpFields) - println(response.stringBody) - println() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals(7L, response.contentLength) - assertEquals("test ok", response.stringBody) - } - - manager.stop() - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestAsyncHttpClientRequestBuilder.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestAsyncHttpClientRequestBuilder.kt deleted file mode 100644 index 049c55c70..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestAsyncHttpClientRequestBuilder.kt +++ /dev/null @@ -1,175 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.client.HttpClientConnectionManager -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.stringBody -import com.fireflysource.net.http.client.impl.content.provider.ByteBufferContentProvider -import com.fireflysource.net.http.client.impl.content.provider.StringContentProvider -import com.fireflysource.net.http.common.model.* -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.mockito.Mockito.mock -import java.net.URL -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets - -class TestAsyncHttpClientRequestBuilder { - - private val connectionManager = mock(HttpClientConnectionManager::class.java) - - @Test - fun testPostStringBodyData() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.POST.value, uri, HttpVersion.HTTP_1_1) - builder.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_PLAIN.value).body("body 123") - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - println(metadata.fields) - - assertTrue(metadata.isRequest) - - assertNotNull(builder.httpRequest.contentProvider) - assertTrue(builder.httpRequest.contentProvider is StringContentProvider) - } - - @Test - fun testPostByteBufferBodyData() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.POST.value, uri, HttpVersion.HTTP_1_1) - val buffer = ByteBuffer.allocate(20) - builder.put(HttpHeader.CONTENT_TYPE, "application/octet-stream").body(buffer) - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - println(metadata.fields) - - assertTrue(metadata.isRequest) - - assertNotNull(builder.httpRequest.contentProvider) - assertTrue(builder.httpRequest.contentProvider is ByteBufferContentProvider) - } - - @Test - fun testPostFormData() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.POST.value, uri, HttpVersion.HTTP_1_1) - builder.putFormInput("p1", "v1") - .putFormInput("p2", "v2") - .addFormInput("p3", "v3") - .addFormInputs("p3", listOf("v31", "v32")) - .putFormInputs("p4", listOf()) - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - println(metadata.fields) - - assertTrue(metadata.isRequest) - assertEquals(MimeTypes.Type.FORM_ENCODED.value, metadata.fields[HttpHeader.CONTENT_TYPE]) - assertTrue(metadata.fields[HttpHeader.CONTENT_LENGTH].toLong() > 0) - - assertNotNull(builder.httpRequest.contentProvider) - assertTrue(builder.httpRequest.contentProvider is StringContentProvider) - assertTrue(builder.httpRequest.contentProvider!!.length() > 0) - assertTrue((builder.httpRequest.contentProvider as StringContentProvider).content.contains("p1=v1&p2=v2&p3=v3&p3=v31&p3=v32&p4=")) - - println((builder.httpRequest.contentProvider as StringContentProvider).content) - } - - @Test - fun testQueryParam() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.GET.value, uri, HttpVersion.HTTP_1_1) - builder.putQueryString("q1", "v1") - .putQueryString("q2", "v2") - .addQueryString("q2", "v22") - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - - assertTrue(metadata.isRequest) - assertTrue(metadata.uri.query.contains("q1=v1&q2=v2&q2=v22")) - - assertEquals(0, metadata.fields.size()) - } - - @Test - fun testQueryParam2() { - val uri = HttpURI(URL("https://www.fireflysource.com?a1=c1&q1=v1").toURI()) - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.GET.value, uri, HttpVersion.HTTP_1_1) - builder.putQueryString("q1", "v1") - .putQueryString("q2", "v2") - .addQueryString("q2", "v22") - .putQueryStrings("q3", listOf("v31", "v32", "v33")) - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - - assertTrue(metadata.isRequest) - assertTrue(metadata.uri.query.contains("a1=c1&q1=v1&q1=v1&q2=v2&q2=v22&q3=v31&q3=v32&q3=v33")) - - assertEquals(0, metadata.fields.size()) - } - - @Test - fun testPostMultiPartData() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.POST.value, uri, HttpVersion.HTTP_1_1) - - val fields = HttpFields() - fields.add("t1", "x1") - builder.addPart("text1", stringBody("plain text1", StandardCharsets.UTF_8), fields) - - val fields2 = HttpFields() - fields2.add("t2", "x2") - builder.addPart("text2", stringBody("plain text2", StandardCharsets.UTF_8), fields2) - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - println(metadata.fields) - - assertTrue(metadata.isRequest) - assertTrue(metadata.fields[HttpHeader.CONTENT_TYPE].contains("multipart/form-data")) - assertEquals("327", metadata.fields[HttpHeader.CONTENT_LENGTH]) - } - - @Test - fun testPostFileMultiPartData() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.POST.value, uri, HttpVersion.HTTP_1_1) - - val fields = HttpFields() - fields.add("t1", "x1") - builder.addFilePart( - "text1", - "file1.txt", - stringBody("mock file text1", StandardCharsets.UTF_8), - fields - ) - - val metadata = toMetaDataRequest(builder.httpRequest) - println(metadata) - println(metadata.fields) - - assertTrue(metadata.isRequest) - assertTrue(metadata.fields[HttpHeader.CONTENT_TYPE].contains("multipart/form-data")) - } - - @Test - fun testCookie() { - val uri = HttpURI("https://www.fireflysource.com") - val builder = AsyncHttpClientRequestBuilder(connectionManager, HttpMethod.POST.value, uri, HttpVersion.HTTP_1_1) - - builder.cookies( - mutableListOf( - Cookie("c1", "v1"), - Cookie("c2", "v2"), - Cookie("c3", "v3") - ) - ) - - val metadata = toMetaDataRequest(builder.httpRequest) - assertTrue(metadata.isRequest) - assertTrue(metadata.fields[HttpHeader.COOKIE].contains("c1=v1;c2=v2;c3=v3")) - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttp1ClientConnection.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttp1ClientConnection.kt deleted file mode 100644 index 9273137ad..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttp1ClientConnection.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpHeaderValue -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpURI -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -class TestHttp1ClientConnection { - - @Test - @DisplayName("should add the HOST and KEEP_ALIVE headers") - fun testPrepareHttp1Headers() { - val request = AsyncHttpClientRequest() - request.method = HttpMethod.GET.value - request.uri = HttpURI("https://www.fireflysource.com/") - prepareHttp1Headers(request) { "defaultHost" } - - assertTrue(request.httpFields.getValuesList(HttpHeader.HOST.value).isNotEmpty()) - assertEquals("www.fireflysource.com", request.httpFields[HttpHeader.HOST.value]) - assertEquals(HttpHeaderValue.KEEP_ALIVE.value, request.httpFields[HttpHeader.CONNECTION.value]) - } - - @Test - @DisplayName("should set default host header") - fun testDefaultHost() { - val request = AsyncHttpClientRequest() - request.method = HttpMethod.GET.value - request.uri = HttpURI("/echo0") - prepareHttp1Headers(request) { "defaultHost" } - assertTrue(request.httpFields.getValuesList(HttpHeader.HOST.value).isNotEmpty()) - assertEquals("defaultHost", request.httpFields[HttpHeader.HOST.value]) - } - - @Test - @DisplayName("should not remove user setting headers") - fun testExistConnectionHeaders() { - val request = AsyncHttpClientRequest() - request.method = HttpMethod.GET.value - request.uri = HttpURI("https://www.fireflysource.com/") - request.httpFields.addCSV(HttpHeader.CONNECTION, HttpHeaderValue.UPGRADE.value, "HTTP2-Settings") - prepareHttp1Headers(request) { "" } - - assertEquals( - "${HttpHeaderValue.UPGRADE.value}, HTTP2-Settings, ${HttpHeaderValue.KEEP_ALIVE.value}", - request.httpFields[HttpHeader.CONNECTION.value] - ) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttp2ClientConnection.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttp2ClientConnection.kt deleted file mode 100644 index 65721b272..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttp2ClientConnection.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpHeaderValue -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.HttpServerConnection -import com.fireflysource.net.http.server.RoutingContext -import com.fireflysource.net.http.server.impl.Http2ServerConnection -import com.fireflysource.net.tcp.TcpServer -import com.fireflysource.net.tcp.TcpServerFactory -import com.fireflysource.net.tcp.onAcceptAsync -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.net.InetSocketAddress -import java.util.concurrent.CompletableFuture -import kotlin.math.roundToLong -import kotlin.random.Random -import kotlin.system.measureTimeMillis - -class TestHttp2ClientConnection { - - private lateinit var address: InetSocketAddress - - @BeforeEach - fun init() { - address = InetSocketAddress("localhost", Random.nextInt(20000, 40000)) - } - - private fun finish(count: Int, time: Long, httpClient: HttpClient, httpServer: TcpServer) { - val throughput = count / (time / 1000.00) - println("success. $time ms, ${throughput.roundToLong()} qps") - httpClient.stop() - httpServer.stop() - } - - private fun createHttpServer(listener: HttpServerConnection.Listener): TcpServer { - val server = TcpServerFactory.create().timeout(120 * 1000L).enableSecureConnection() - - return server.onAcceptAsync { connection -> - println("accept connection. ${connection.id}") - connection.beginHandshake().await() - val http2Connection = Http2ServerConnection(HttpConfig(), connection) - http2Connection.setListener(listener).begin() - }.listen(address) - } - - @Test - @DisplayName("should send request and receive response successfully.") - fun testSendRequest(): Unit = runBlocking { - val count = 10 - - val httpServer = createHttpServer(object : HttpServerConnection.Listener.Adapter() { - override fun onHttpRequestComplete(ctx: RoutingContext): CompletableFuture { - return ctx.put("Test-Http-Exchange", "R1") - .end("http exchange success.") - } - }) - - val httpClient = HttpClientFactory.create() - - val time = measureTimeMillis { - val futures = (1..count).map { i -> - httpClient.get("https://${address.hostName}:${address.port}/test/$i").submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("http exchange success.", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should receive 100 continue response.") - fun test100Continue(): Unit = runBlocking { - val count = 100 - - val httpServer = createHttpServer(object : HttpServerConnection.Listener.Adapter() { - override fun onHeaderComplete(ctx: RoutingContext): CompletableFuture { - return if (ctx.expect100Continue()) ctx.response100Continue() else Result.DONE - } - - override fun onHttpRequestComplete(ctx: RoutingContext): CompletableFuture { - return ctx.put("Test-100-Continue", "100") - .end("receive data success.") - } - }) - - val httpClient = HttpClientFactory.create() - - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("https://${address.hostName}:${address.port}/data") - .put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.value) - .body("Some test data!") - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("receive data success.", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should receive trailers successfully.") - fun testTrailer(): Unit = runBlocking { - val count = 100 - - val httpServer = createHttpServer(object : HttpServerConnection.Listener.Adapter() { - override fun onHeaderComplete(ctx: RoutingContext): CompletableFuture { - return if (ctx.expect100Continue()) ctx.response100Continue() else Result.DONE - } - - override fun onHttpRequestComplete(ctx: RoutingContext): CompletableFuture { - return ctx.put("Test-100-Continue", "100") - .addCSV(HttpHeader.TRAILER, "t1", "t2", "t3") - .setTrailerSupplier { - val trailers = HttpFields() - trailers.put("t1", "v1") - trailers.put("t2", "v2") - trailers.put("t3", "v3") - trailers - } - .end("receive data success.") - } - }) - - val httpClient = HttpClientFactory.create() - - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("https://${address.hostName}:${address.port}/data") - .put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.value) - .body("Some test data!") - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("receive data success.", response.stringBody) - assertTrue(response.trailerSupplier != null) - val trailers = response.trailerSupplier.get() - println(trailers) - assertEquals("v1", trailers["t1"]) - assertEquals("v2", trailers["t2"]) - assertEquals("v3", trailers["t3"]) - } - - finish(count, time, httpClient, httpServer) - } - - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttpClient.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttpClient.kt deleted file mode 100644 index df9f85cd5..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttpClient.kt +++ /dev/null @@ -1,242 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.useAwait -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.client.impl.content.provider.ByteBufferContentProvider -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.ProxyConfig -import com.fireflysource.net.http.common.exception.MissingRemoteHostException -import com.fireflysource.net.http.common.model.ContentEncoding -import com.fireflysource.net.http.common.model.Cookie -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.net.http.server.HttpServerFactory -import kotlinx.coroutines.delay -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletableFuture -import kotlin.math.roundToLong -import kotlin.random.Random -import kotlin.system.measureTimeMillis - -class TestHttpClient { - - private lateinit var address: InetSocketAddress - private lateinit var httpServer: HttpServer - private val content = (1..50000).joinToString("") { it.toString() } - - @BeforeEach - fun init() { - address = InetSocketAddress("localhost", Random.nextInt(2000, 5000)) - httpServer = HttpServerFactory.create() - httpServer - .router().path("/testHttpClient").handler { ctx -> - ctx.setCookies(listOf(Cookie("cookie1", "value1"), Cookie("cookie2", "value2"))) - .put(HttpHeader.CONTENT_LENGTH, "14") - .write("test client ok") - .end() - } - .router().path("/testChunkedEncoding").handler { ctx -> - ctx.end("test chunked encoding success") - } - .router().path("/testNoChunkedEncoding").handler { ctx -> - ctx.put(HttpHeader.CONTENT_LENGTH, "32").end("test no chunked encoding success") - } - .router().get("/testCompressedContent").handler { ctx -> - ctx.put(HttpHeader.CONTENT_ENCODING, ContentEncoding.GZIP.value) - .write("测试压缩内容:") - .write( - mutableListOf( - BufferUtils.toBuffer("跳过", StandardCharsets.UTF_8), - BufferUtils.toBuffer("跳过", StandardCharsets.UTF_8), - BufferUtils.toBuffer("今天,", StandardCharsets.UTF_8), - BufferUtils.toBuffer("非常愉快的", StandardCharsets.UTF_8), - BufferUtils.toBuffer("搞定了这个功能。", StandardCharsets.UTF_8) - ), 2, 3 - ) - .end() - } - .router().get("/echo0").handler { it.end("ok") } - .timeout(120 * 1000L) - .listen(address) - } - - @AfterEach - fun destroy() { - val time = measureTimeMillis { - httpServer.stop() - } - println("shutdown time: $time ms") - } - - @Test - @DisplayName("should send the HTTP request no content successfully") - fun testNoContent() = runBlocking { - val httpClient = HttpClientFactory.create() - val count = 100 - - val time = measureTimeMillis { - val futures = - (1..count).map { httpClient.get("http://${address.hostName}:${address.port}/testHttpClient").submit() } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals(14L, response.contentLength) - assertEquals("test client ok", response.stringBody) - - assertEquals(2, response.cookies.size) - assertEquals("value1", response.cookies.filter { it.name == "cookie1" }.map { it.value }[0]) - assertEquals("value2", response.cookies.filter { it.name == "cookie2" }.map { it.value }[0]) - - } - - val throughput = count / (time / 1000.00) - println("success. $time ms, ${throughput.roundToLong()} qps") - httpClient.stop() - } - - @Test - @DisplayName("should send the HTTP request with content using chucked encoding successfully") - fun testContentWithChunkedEncoding() = runBlocking { - val httpClient = HttpClientFactory.create() - - val data = ByteBuffer.wrap(content.toByteArray(StandardCharsets.UTF_8)) - println("data length: ${data.remaining()}") - val response = httpClient - .post("http://${address.hostName}:${address.port}/testChunkedEncoding") - .contentProvider(MockChunkByteBufferContentProvider(data)).submit().await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("test chunked encoding success", response.stringBody) - - httpClient.stop() - } - - @Test - @DisplayName("should send the HTTP request with content and content length successfully") - fun testContentWithoutChunkedEncoding() = runBlocking { - val httpClient = HttpClientFactory.create() - - (1..10).map { - val data = ByteBuffer.wrap(content.toByteArray(StandardCharsets.UTF_8)) - val length = data.remaining() - println("data length: $length") - httpClient.post("http://${address.hostName}:${address.port}/testNoChunkedEncoding") - .contentProvider(ByteBufferContentProvider(data)) - .submit() - }.forEach { - val response = it.await() - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("test no chunked encoding success", response.stringBody) - assertEquals(32, response.contentLength) - } - - httpClient.stop() - } - - @Test - @DisplayName("should get compressed content successfully") - fun testCompressedContent() = runBlocking { - val httpClient = HttpClientFactory.create() - val response = - httpClient.get("http://${address.hostName}:${address.port}/testCompressedContent").submit().await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("测试压缩内容:今天,非常愉快的搞定了这个功能。", response.stringBody) - println(response) - - httpClient.stop() - } - - @Test - @DisplayName("should retry to send request successfully") - fun testConnectionTimeoutRetry() = runBlocking { - val server2 = HttpServerFactory.create() - val addr = InetSocketAddress("localhost", Random.nextInt(12000, 15000)) - server2.router().get("/timeout/echo") - .handler { - val cmd = it.getQueryString("cmd") - it.end("test timeout $cmd") - } - .timeout(1) - .listen(addr) - - val config = HttpConfig() - config.connectionPoolSize = 1 - val httpClient = HttpClientFactory.create(config) - - suspend fun echo() { - val url = "http://${addr.hostName}:${addr.port}/timeout/echo" - val response = httpClient.get(url).addQueryString("cmd", "xx").submit().await() - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("test timeout xx", response.stringBody) - println(response) - } - - echo() - delay(2000) - echo() - - httpClient.stop() - server2.stop() - } - - @Test - @DisplayName("should create HTTP client connection and send request successfully") - fun testCreateHttpClientConnection() = runBlocking { - val httpClient = HttpClientFactory.create() - val uri = "http://${address.hostName}:${address.port}" - val connection = httpClient.createHttpClientConnection(uri).await() - - connection.useAwait { - repeat(3) { - val response = connection.get("/echo0").submit().await() - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("ok", response.stringBody) - println(response) - } - } - - httpClient.stop() - } - - @Test - @DisplayName("should send request failure when the host does not set") - fun testNoHostException() { - val httpClient = HttpClientFactory.create() - assertThrows(MissingRemoteHostException::class.java) { - httpClient.get("/echo0").submit() - } - } - -// @Test - fun testProxy() = runBlocking { - val proxyConfig = ProxyConfig() - proxyConfig.host = "127.0.0.1" - proxyConfig.port = 1091 - val httpConfig = HttpConfig() - httpConfig.proxyConfig = proxyConfig - val client = HttpClientFactory.create(httpConfig) - val response = client.get("https://www.google.com/").submit().await() - println(response) - println(response.stringBody) - } - - class MockChunkByteBufferContentProvider(content: ByteBuffer) : ByteBufferContentProvider(content) { - override fun length(): Long = -1 - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttpProtocolNegotiator.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttpProtocolNegotiator.kt deleted file mode 100644 index 5bff0c272..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/TestHttpProtocolNegotiator.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.fireflysource.net.http.client.impl - -import com.fireflysource.common.codec.base64.Base64Utils -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.addHttp2UpgradeHeader -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.defaultSettingsFrameBytes -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.expectUpgradeHttp2 -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.isUpgradeSuccess -import com.fireflysource.net.http.client.impl.HttpProtocolNegotiator.removeHttp2UpgradeHeader -import com.fireflysource.net.http.common.model.* -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -/** - * @author Pengtao Qiu - */ -class TestHttpProtocolNegotiator { - - @Test - @DisplayName("should remove h2c headers successfully") - fun testRemoveHttp2UpgradeHeader() { - val request = AsyncHttpClientRequest() - addHttp2UpgradeHeader(request) - assertTrue( - request.httpFields.getCSV(HttpHeader.CONNECTION, false).containsAll( - arrayListOf( - "Upgrade", - "HTTP2-Settings" - ) - ) - ) - assertEquals("h2c", request.httpFields[HttpHeader.UPGRADE]) - assertEquals( - Base64Utils.encodeToUrlSafeString(defaultSettingsFrameBytes), - request.httpFields[HttpHeader.HTTP2_SETTINGS] - ) - - removeHttp2UpgradeHeader(request) - assertFalse(expectUpgradeHttp2(request)) - assertFalse(request.httpFields.contains(HttpHeader.HTTP2_SETTINGS)) - assertFalse(request.httpFields.contains(HttpHeader.UPGRADE)) - assertEquals("keep-alive", request.httpFields[HttpHeader.CONNECTION]) - } - - @Test - @DisplayName("should add h2c headers successfully") - fun testAddHttp2UpgradeHeader() { - val request = AsyncHttpClientRequest() - request.httpFields.put(HttpHeader.CONNECTION, "keep-alive") - addHttp2UpgradeHeader(request) - assertTrue( - request.httpFields.getCSV(HttpHeader.CONNECTION, false).containsAll( - arrayListOf( - "keep-alive", - "Upgrade", - "HTTP2-Settings" - ) - ) - ) - assertTrue(expectUpgradeHttp2(request)) - } - - @Test - @DisplayName("should check response upgrade http2 successfully") - fun testUpgradeSuccess() { - val metadata = MetaData.Response(HttpVersion.HTTP_1_1, HttpStatus.SWITCHING_PROTOCOLS_101, HttpFields()) - metadata.fields.put(HttpHeader.CONNECTION, "Upgrade") - metadata.fields.put(HttpHeader.UPGRADE, "h2c") - val response = AsyncHttpClientResponse(metadata, null) - assertTrue(isUpgradeSuccess(response)) - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/handler/TestFileContentHandler.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/handler/TestFileContentHandler.kt deleted file mode 100644 index 420b30437..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/handler/TestFileContentHandler.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.handler - -import com.fireflysource.common.io.readFileBytesAsync -import com.fireflysource.net.http.client.HttpClientContentHandlerFactory.fileHandler -import com.fireflysource.net.http.client.HttpClientResponse -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito -import java.nio.ByteBuffer -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.StandardOpenOption.WRITE -import java.util.* - -class TestFileContentHandler { - - private val response = Mockito.mock(HttpClientResponse::class.java) - - private val tmpFile = Paths.get(System.getProperty("user.home"), "tmpFile${UUID.randomUUID()}.txt") - - @BeforeEach - fun init() { - Files.createFile(tmpFile) - println("create a file: $tmpFile") - } - - @AfterEach - fun destroy() { - Files.delete(tmpFile) - println("delete file: $tmpFile") - } - - @Test - @DisplayName("should write data to file successfully") - fun test() = runTest { - val handler = fileHandler(tmpFile, WRITE) - arrayOf( - ByteBuffer.wrap("hello".toByteArray()), - ByteBuffer.wrap(" file".toByteArray()), - ByteBuffer.wrap(" handler".toByteArray()) - ).forEach { handler.accept(it, response) } - - handler.closeAsync().await() - - val str = readFileBytesAsync(tmpFile).await() - assertEquals("hello file handler", String(str)) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/handler/TestStringContentHandler.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/handler/TestStringContentHandler.kt deleted file mode 100644 index 43cb7e3c3..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/handler/TestStringContentHandler.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.handler - -import com.fireflysource.net.http.client.HttpClientContentHandlerFactory.stringHandler -import com.fireflysource.net.http.client.HttpClientResponse -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.mockito.Mockito.mock -import java.nio.ByteBuffer - -class TestStringContentHandler { - - private val response = mock(HttpClientResponse::class.java) - - @Test - fun test() { - val handler = stringHandler() - arrayOf( - ByteBuffer.wrap("hello".toByteArray()), - ByteBuffer.wrap(" buffer".toByteArray()) - ).forEach { handler.accept(it, response) } - assertEquals("hello buffer", handler.toString()) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestByteBufferContentProvider.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestByteBufferContentProvider.kt deleted file mode 100644 index 6a9808042..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestByteBufferContentProvider.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.bytesBody -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -class TestByteBufferContentProvider { - - @Test - @DisplayName("should get byte buffer successfully") - fun testToByteBuffer() { - val content = BufferUtils.allocate(12) - val pos = BufferUtils.flipToFill(content) - content.putInt(333) - content.putLong(7777) - BufferUtils.flipToFlush(content, pos) - - val provider = bytesBody(content) - val buffer = provider.toByteBuffer() - assertEquals(333, buffer.int) - assertEquals(7777, buffer.long) - } - - @Test - @DisplayName("should read buffer successfully") - fun testRead() = runTest { - val content = BufferUtils.allocate(12) - val pos = BufferUtils.flipToFill(content) - content.putInt(333) - content.putLong(7777) - BufferUtils.flipToFlush(content, pos) - - val provider = bytesBody(content) - assertEquals(12, provider.length()) - - val buffer = BufferUtils.allocate(6) - val bufPos = BufferUtils.flipToFill(buffer) - val len1 = provider.read(buffer).await() - BufferUtils.flipToFlush(buffer, bufPos) - assertEquals(6, len1) - assertEquals(333, buffer.int) - - val buffer2 = BufferUtils.allocate(10) - val bufPos2 = BufferUtils.flipToFill(buffer2) - val len2 = provider.read(buffer2).await() - BufferUtils.flipToFlush(buffer2, bufPos2) - assertEquals(6, len2) - - val buffer3 = BufferUtils.allocate(8) - val bufPos3 = BufferUtils.flipToFill(buffer3) - buffer3.put(buffer).put(buffer2) - BufferUtils.flipToFlush(buffer3, bufPos3) - assertEquals(7777, buffer3.long) - - val buffer4 = BufferUtils.allocate(8) - val bufPos4 = BufferUtils.flipToFill(buffer4) - val len4 = provider.read(buffer2).await() - BufferUtils.flipToFlush(buffer4, bufPos4) - assertEquals(-1, len4) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestFileContentProvider.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestFileContentProvider.kt deleted file mode 100644 index a5bdcf476..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestFileContentProvider.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.openFileChannelAsync -import com.fireflysource.common.io.useAwait -import com.fireflysource.common.io.writeAwait -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.fileBody -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.StandardOpenOption.READ -import java.nio.file.StandardOpenOption.WRITE -import java.util.* - -class TestFileContentProvider { - - private val tmpFile = Paths.get(System.getProperty("user.home"), "tmpFile${UUID.randomUUID()}.txt") - - @BeforeEach - fun init() { - Files.createFile(tmpFile) - println("create file: $tmpFile") - } - - @AfterEach - fun destroy() { - Files.delete(tmpFile) - println("delete file: $tmpFile") - } - - @Test - @DisplayName("should read file successfully") - fun test(): Unit = runBlocking { - val capacity = 24 - val fileChannel = openFileChannelAsync(tmpFile, WRITE).await() - fileChannel.useAwait { - val writeBuffer = BufferUtils.allocate(capacity) - val writePos = BufferUtils.flipToFill(writeBuffer) - writeBuffer.putInt(1).putInt(2).putInt(3) - .putInt(4).putInt(5).putInt(6) - BufferUtils.flipToFlush(writeBuffer, writePos) - - val writeLen = fileChannel.writeAwait(writeBuffer, 0L) - assertEquals(capacity, writeLen) - } - - val provider = fileBody(tmpFile, READ) as FileContentProvider - val readBuffer = BufferUtils.allocate(capacity) - val readPos = BufferUtils.flipToFill(readBuffer) - val readLen = provider.read(readBuffer).await() - BufferUtils.flipToFlush(readBuffer, readPos) - assertEquals(capacity, readLen) - - (1..6).forEach { i -> - assertEquals(i, readBuffer.int) - } - provider.closeAsync().await() - Unit - } - - @Test - @DisplayName("should seek position and read file successfully") - fun testSeekPosition(): Unit = runBlocking { - val capacity = 24 - val fileChannel = openFileChannelAsync(tmpFile, WRITE).await() - fileChannel.useAwait { - val writeBuffer = BufferUtils.allocate(capacity) - val writePos = BufferUtils.flipToFill(writeBuffer) - writeBuffer.putInt(1).putInt(2).putInt(3) - .putInt(4).putInt(5).putInt(6) - BufferUtils.flipToFlush(writeBuffer, writePos) - - val writeLen = fileChannel.writeAwait(writeBuffer, 0L) - assertEquals(capacity, writeLen) - } - - val pos: Long = 2 * 4 - val length = capacity - pos - val provider = fileBody(tmpFile, setOf(READ), pos, length) as FileContentProvider - val readBuffer = BufferUtils.allocate(capacity) - val readPos = BufferUtils.flipToFill(readBuffer) - val readLen = provider.read(readBuffer).await() - BufferUtils.flipToFlush(readBuffer, readPos) - assertEquals(length, readLen.toLong()) - - (3..6).forEach { i -> - assertEquals(i, readBuffer.int) - } - provider.closeAsync().await() - Unit - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestMultiPartContentProvider.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestMultiPartContentProvider.kt deleted file mode 100644 index 3ae5a2179..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestMultiPartContentProvider.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.stringBody -import com.fireflysource.net.http.common.model.HttpFields -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.nio.charset.StandardCharsets - -class TestMultiPartContentProvider { - - @Test - @DisplayName("should generate multi-part format successfully") - fun testRead() = runBlocking { - val provider = MultiPartContentProvider() - - val str = "Hello string body" - val strProvider = stringBody(str, StandardCharsets.UTF_8) - provider.addPart("hello string", strProvider, null) - - val str2 = "string body 2" - val strProvider2 = stringBody(str2, StandardCharsets.UTF_8) - val httpFields = HttpFields() - httpFields.put("x1", "y1") - provider.addPart("string 2", strProvider2, httpFields) - - val buffer = BufferUtils.allocate(provider.length().toInt()) - val pos = BufferUtils.flipToFill(buffer) - while (buffer.hasRemaining()) { - val len = provider.read(buffer).await() - if (len < 0) { - break - } - } - BufferUtils.flipToFlush(buffer, pos) - println() - - println("Content-Type: ${provider.contentType}") - println() - provider.closeAsync().await() - - val content = BufferUtils.toUTF8String(buffer) - println(content) - assertTrue(content.contains("Hello string body")) - assertTrue(content.contains("string body 2")) - assertTrue(content.contains("x1: y1")) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestStringContentProvider.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestStringContentProvider.kt deleted file mode 100644 index 52f4cfe84..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/client/impl/content/provider/TestStringContentProvider.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.fireflysource.net.http.client.impl.content.provider - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.stringBody -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.nio.charset.StandardCharsets - -class TestStringContentProvider { - - @Test - @DisplayName("should get string successfully") - fun testToByteBuffer() { - val str = "Hello string body" - val provider = stringBody(str, StandardCharsets.UTF_8) - val byteBuffer = provider.toByteBuffer() - assertEquals(str, BufferUtils.toString(byteBuffer, StandardCharsets.UTF_8)) - } - - @Test - @DisplayName("should read string successfully") - fun testRead() = runBlocking { - val str = "Hello string body" - val provider = stringBody(str, StandardCharsets.UTF_8) - - val byteBuffer = BufferUtils.allocate(5) - val pos = BufferUtils.flipToFill(byteBuffer) - val len = provider.read(byteBuffer).await() - BufferUtils.flipToFlush(byteBuffer, pos) - - assertEquals(5, len) - assertEquals(5, byteBuffer.remaining()) - assertEquals("Hello", BufferUtils.toString(byteBuffer, StandardCharsets.UTF_8)) - - val byteBuffer2 = BufferUtils.allocate(20) - val pos2 = BufferUtils.flipToFill(byteBuffer2) - val len2 = provider.read(byteBuffer2).await() - BufferUtils.flipToFlush(byteBuffer2, pos2) - - assertEquals(str.length - 5, len2) - assertEquals(str.length - 5, byteBuffer2.remaining()) - assertEquals(" string body", BufferUtils.toString(byteBuffer2, StandardCharsets.UTF_8)) - - val byteBuffer3 = BufferUtils.allocate(10) - val pos3 = BufferUtils.flipToFill(byteBuffer3) - val len3 = provider.read(byteBuffer3).await() - BufferUtils.flipToFlush(byteBuffer3, pos3) - - assertEquals(-1, len3) - assertEquals(0, byteBuffer3.remaining()) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/AbstractHttpServerTestBase.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/AbstractHttpServerTestBase.kt deleted file mode 100644 index 7f2c2ac40..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/AbstractHttpServerTestBase.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.net.http.server.HttpServerFactory -import com.fireflysource.net.tcp.aio.ApplicationProtocol -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.params.provider.Arguments -import java.net.InetSocketAddress -import java.util.stream.Stream -import kotlin.math.roundToLong -import kotlin.random.Random - -abstract class AbstractHttpServerTestBase { - - companion object { - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - Arguments.arguments("http1", "http"), - Arguments.arguments("http1", "https"), - Arguments.arguments("http2", "https") - ) - } - } - - protected lateinit var address: InetSocketAddress - - @BeforeEach - fun init() { - address = InetSocketAddress("localhost", Random.nextInt(20000, 40000)) - } - - fun createHttpServer(protocol: String, schema: String, httpConfig: HttpConfig = HttpConfig()): HttpServer { - val server = HttpServerFactory.create(httpConfig) - when (protocol) { - "http1" -> server.supportedProtocols(listOf(ApplicationProtocol.HTTP1.value)) - "http2" -> server.supportedProtocols( - listOf( - ApplicationProtocol.HTTP2.value, - ApplicationProtocol.HTTP1.value - ) - ) - } - if (schema == "https") { - server.enableSecureConnection() - } - return server - } - - fun finish(count: Int, time: Long, httpClient: HttpClient, httpServer: HttpServer) { - try { - val throughput = count / (time / 1000.00) - println("success. $time ms, ${throughput.roundToLong()} qps") - httpClient.stop() - httpServer.stop() - } catch (e: Exception) { - e.printStackTrace() - } - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/TestHttpServer.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/TestHttpServer.kt deleted file mode 100644 index 534a096da..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/TestHttpServer.kt +++ /dev/null @@ -1,503 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.ProxyConfig -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.HttpServerContentProviderFactory -import com.fireflysource.net.http.server.HttpServerFactory -import com.fireflysource.net.http.server.impl.router.asyncBlockingHandler -import com.fireflysource.net.http.server.impl.router.asyncHandler -import com.fireflysource.net.http.server.impl.router.getCurrentRoutingContext -import com.fireflysource.net.tcp.TcpClientFactory -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource -import java.net.InetSocketAddress -import java.nio.ByteBuffer -import java.nio.channels.ClosedChannelException -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletableFuture -import kotlin.random.Random -import kotlin.system.measureTimeMillis - -@Suppress("HttpUrlsUsage") -class TestHttpServer : AbstractHttpServerTestBase() { - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should visit router chain successfully.") - fun testRouterChain(protocol: String, schema: String) = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer - .router().path("*").asyncHandler { ctx -> - assertTrue(getCurrentRoutingContext() != null) - ctx.write("into router -> ") - ctx.next().await() - ctx.end("end router.") - } - .router().get("/hello/:foo").handler { ctx -> - val p = ctx.getPathParameter("foo") - ctx.write("visit foo: $p |").next() - } - .router().get("/hello/*").handler { ctx -> - val p = ctx.getPathParameter(0) - ctx.write("visit pattern: $p |").next() - } - .router().method(HttpMethod.GET).pathRegex("/hello/([a-z]+)").handler { ctx -> - val p = ctx.getPathParameterByRegexGroup(1) - ctx.write("regex group: $p |").next() - } - .router().get("/hello/test").handler { ctx -> - ctx.write("visit test ").next() - } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/hello/bar").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals( - "into router -> visit foo: bar |visit pattern: bar |regex group: bar |end router.", - response.stringBody - ) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should enable and disable router successfully.") - fun testEnableAndDisableRouter(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer - .router().enable().get("/hello/:foo").handler { ctx -> - val p = ctx.getPathParameter("foo") - ctx.write("visit foo: $p ").next() - } - .router().disable().get("/hello/*").handler { ctx -> - val p = ctx.getPathParameter(0) - ctx.write("visit pattern: $p ").next() - } - .router().get("/hello/test").handler { ctx -> - ctx.write("visit test ").next() - } - .router().path("*").handler { ctx -> ctx.end() } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/hello/test").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals( - "visit foo: test visit test ", - response.stringBody - ) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should get and set attributes successfully.") - fun testContextAttributes(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer - .router().get("/hello/:foo").handler { ctx -> - ctx.attributes["A"] = "TestA" - ctx.next() - } - .router().get("/hello/:bar").handler { ctx -> - ctx.attributes["B"] = "TestB" - ctx.setAttribute("C", "TestC") - ctx.setAttribute("D", "TestD") - ctx.removeAttribute("D") - ctx.removeAttribute("E") - ctx.next() - } - .router().method("GET").path("/hello/*").handler { ctx -> - ctx.write("${ctx.attributes["A"]} ${ctx.attributes["B"]} ${ctx.getAttribute("C")}").next() - } - .router().method(HttpMethod.GET).handler { ctx -> ctx.end() } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/hello/test").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals( - "TestA TestB TestC", - response.stringBody - ) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response 500 when the router does not commit.") - fun testRouterNotCommit(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/hello").handler { - Result.DONE - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/hello").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - try { - val response = futures[0].await() - - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, response.status) - println(response) - } catch (e: Exception) { - e.printStackTrace() - } - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response 400 when the resource not found.") - fun testResourceNotFound(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/hello").handler { - Result.DONE - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/hellox").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.NOT_FOUND_404, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response 500 when the router happens exception.") - fun testRouterException(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer - .router().get("/exception").handler { - throw IllegalStateException("test exception") - } - .router().get("/exception").handler { ctx -> - println("bad end?") - ctx.end("bad end") - } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/exception").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should response 400 when the host header is missing.") - fun testNoHostHeader(): Unit = runTest { - val httpServer = createHttpServer("http1", "http") - httpServer - .router().get("/testNoHost").handler { it.end("ok") } - .listen(address) - - val config = HttpConfig() - config.isAutoGeneratedClientHttp1Headers = false - val httpClient = HttpClientFactory.create(config) - - val time = measureTimeMillis { - val response = httpClient.get("http://${address.hostName}:${address.port}/testNoHost").submit().await() - assertEquals(HttpStatus.BAD_REQUEST_400, response.status) - println(response) - } - - finish(1, time, httpClient, httpServer) - } - - @Test - @DisplayName("should establish HTTP tunnel successfully.") - fun testHttpTunnel(): Unit = runTest { - val httpServer = createHttpServer("http1", "http") - httpServer - .onAcceptHttpTunnel { request -> - println("Accept http tunnel request. $request") - CompletableFuture.completedFuture(true) - } - .onHttpTunnelHandshakeComplete { connection, address -> - println("target address: $address") - connection.write(BufferUtils.toBuffer("1234")) - .thenCompose { connection.closeAsync() } - } - .listen(address) - - val message = "CONNECT p54-caldav.icloud.com:443 HTTP/1.1\r\n" + - "Host: p54-caldav.icloud.com\r\n" + - "User-Agent: Mac+OS+X/10.15.7 (19H114) CalendarAgent/930.5.1\r\n" + - "Connection: keep-alive\r\n" + - "Proxy-Connection: keep-alive\r\n\r\n" - val tcpClient = TcpClientFactory.create() - val connection = tcpClient.connect(address).await() - connection.write(BufferUtils.toBuffer(message)).await() - val receivedData = mutableListOf() - while (true) { - try { - val data = connection.read().await() - receivedData.add(data) - } catch (e: ClosedChannelException) { - break - } - } - val receivedMessages = BufferUtils.toString(receivedData, StandardCharsets.UTF_8) - println(receivedMessages) - assertTrue(receivedMessages.isNotBlank()) - assertTrue(receivedMessages.contains("HTTP/1.1 200 Connection Established\r\n\r\n")) - assertTrue(receivedMessages.contains("1234")) - - tcpClient.stop() - httpServer.stop() - } - - @Test - @DisplayName("should establish HTTP tunnel failure.") - fun testHttpTunnelFailure(): Unit = runTest { - val httpServer = createHttpServer("http1", "http") - httpServer - .onAcceptHttpTunnel { request -> - println("Accept http tunnel request. $request") - CompletableFuture.completedFuture(false) - } - .onHttpTunnelHandshakeComplete { connection, address -> - println("target address: $address") - connection.write(BufferUtils.toBuffer("1234")) - .thenCompose { connection.closeAsync() } - } - .listen(address) - - val message = "CONNECT p54-caldav.icloud.com:443 HTTP/1.1\r\n" + - "Host: p54-caldav.icloud.com\r\n" + - "User-Agent: Mac+OS+X/10.15.7 (19H114) CalendarAgent/930.5.1\r\n" + - "Connection: keep-alive\r\n" + - "Proxy-Connection: keep-alive\r\n\r\n" - val tcpClient = TcpClientFactory.create() - val connection = tcpClient.connect(address).await() - connection.write(BufferUtils.toBuffer(message)).await() - val receivedData = mutableListOf() - while (true) { - try { - val data = connection.read().await() - receivedData.add(data) - } catch (e: ClosedChannelException) { - break - } - } - val receivedMessages = BufferUtils.toString(receivedData, StandardCharsets.UTF_8) - println(receivedMessages) - assertTrue(receivedMessages.isNotBlank()) - assertTrue(receivedMessages.contains("Proxy Authentication Required")) - assertTrue(receivedMessages.contains("The proxy authentication must be required")) - assertFalse(receivedMessages.contains("1234")) - - tcpClient.stop() - httpServer.stop() - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should send request via the http proxy successfully.") - fun testHttpProxy(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - httpServer - .router().path("/hello/*").asyncHandler { ctx -> - assertTrue(getCurrentRoutingContext() != null) - ctx.write("into router -> ") - ctx.next().await() - ctx.end("end router.") - } - .router().get("/hello/:foo").handler { ctx -> - val p = ctx.getPathParameter("foo") - ctx.write("visit foo: $p |").next() - } - .router().get("/hello/*").handler { ctx -> - val p = ctx.getPathParameter(0) - ctx.write("visit pattern: $p |").next() - } - .router().method(HttpMethod.GET).pathRegex("/hello/([a-z]+)").handler { ctx -> - val p = ctx.getPathParameterByRegexGroup(1) - ctx.write("regex group: $p |").next() - } - .router().get("/hello/test").handler { ctx -> - ctx.write("visit test ").next() - } - .router().get("/length/test").handler { ctx -> - ctx.contentProvider(HttpServerContentProviderFactory.stringBody("1234567", StandardCharsets.UTF_8)) - .end() - } - .listen(address) - - val proxyAddress = InetSocketAddress("localhost", Random.nextInt(10000, 20000)) - val proxy = HttpServerFactory.createHttpProxy() - proxy.listen(proxyAddress) - - val clientHttpConfig = HttpConfig() - val proxyConfig = ProxyConfig() - proxyConfig.host = proxyAddress.hostName - proxyConfig.port = proxyAddress.port - clientHttpConfig.proxyConfig = proxyConfig - val httpClient = HttpClientFactory.create(clientHttpConfig) - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/hello/bar").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals( - "into router -> visit foo: bar |visit pattern: bar |regex group: bar |end router.", - response.stringBody - ) - println(response) - - val lengthResponse = - httpClient.get("$schema://${address.hostName}:${address.port}/length/test").submit().await() - assertEquals(HttpStatus.OK_200, lengthResponse.status) - assertEquals("1234567", lengthResponse.stringBody) - println(lengthResponse) - } - - finish(count, time, httpClient, httpServer) - proxy.stop() - } - - @Test - fun testCopy() = runTest { - val httpServer = createHttpServer("http1", "http") - httpServer.router().get("/testCopy") - .asyncBlockingHandler { - println(Thread.currentThread().name) - it.end("Origin server") - } - .router().get("/blockingTask") - .blockingHandler { - Thread.sleep(100) - println(Thread.currentThread().name) - it.end("Blocking task").get() - } - .listen(address) - - var newAddress = InetSocketAddress("localhost", Random.nextInt(20000, 40000)) - while (newAddress == address) { - newAddress = InetSocketAddress("localhost", Random.nextInt(20000, 40000)) - } - val newHttpServer = httpServer.copy() - newHttpServer.enableSecureConnection().listen(newAddress) - - val httpClient = HttpClientFactory.create() - - var response = httpClient.get("http://${address.hostName}:${address.port}/testCopy").submit().await() - assertEquals("Origin server", response.stringBody) - println(response) - - response = httpClient.get("http://${address.hostName}:${address.port}/blockingTask").submit().await() - assertEquals("Blocking task", response.stringBody) - println(response) - - response = httpClient.get("https://${newAddress.hostName}:${newAddress.port}/testCopy").submit().await() - assertEquals("Origin server", response.stringBody) - println(response) - - response = httpClient.get("https://${newAddress.hostName}:${newAddress.port}/blockingTask").submit().await() - assertEquals("Blocking task", response.stringBody) - println(response) - - httpClient.stop() - httpServer.stop() - newHttpServer.stop() - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/TestHttpServerConnection.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/TestHttpServerConnection.kt deleted file mode 100644 index 96e4b3baf..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/TestHttpServerConnection.kt +++ /dev/null @@ -1,690 +0,0 @@ -package com.fireflysource.net.http.server.impl - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.client.HttpClientContentProviderFactory -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.client.impl.content.provider.ByteBufferContentProvider -import com.fireflysource.net.http.common.HttpConfig -import com.fireflysource.net.http.common.model.* -import com.fireflysource.net.http.server.HttpServerContentProviderFactory.stringBody -import com.fireflysource.net.http.server.impl.content.provider.DefaultContentProvider -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withTimeout -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets -import java.time.Duration -import java.util.concurrent.CompletableFuture -import kotlin.system.measureTimeMillis - -class TestHttpServerConnection : AbstractHttpServerTestBase() { - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should receive request and response texts successfully.") - fun test(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/test-*").handler { ctx -> - ctx.write(BufferUtils.toBuffer("response buffer.", StandardCharsets.UTF_8)) - val arr = arrayOf( - BufferUtils.toBuffer("array 1.", StandardCharsets.UTF_8), - BufferUtils.toBuffer("array 2.", StandardCharsets.UTF_8), - BufferUtils.toBuffer("array 3.", StandardCharsets.UTF_8) - ) - val list = listOf( - BufferUtils.toBuffer("list 1.", StandardCharsets.UTF_8), - BufferUtils.toBuffer("list 2.", StandardCharsets.UTF_8), - BufferUtils.toBuffer("list 3.", StandardCharsets.UTF_8) - ) - ctx.write(arr, 0, arr.size) - .write(list, 1, 2) - .end("hello http1 server!") - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/test-$it").submit() } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals( - "response buffer.array 1.array 2.array 3.list 2.list 3.hello http1 server!", - response.stringBody - ) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response texts with content length successfully.") - fun testResponseContentLength(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/length-*").handler { ctx -> - val buffer = BufferUtils.toBuffer("response text with content length.", StandardCharsets.UTF_8) - ctx.put(HttpHeader.CONTENT_LENGTH, buffer.remaining().toString()) - ctx.write(buffer).end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/length-$it").submit() } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("response text with content length.", response.stringBody) - assertEquals(34L, response.contentLength) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should receive query strings and form inputs successfully.") - fun testQueryStringsAndFormInputs(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().post("/query-form-*").handler { ctx -> - val query = ctx.getQueryString("key1") - val queryList = ctx.getQueryStrings("list1") - val querySize = ctx.queryStrings.size - val message = ctx.getFormInput("key1") - val formList = ctx.getFormInputs("list1") - val formSize = ctx.formInputs.size - val method = ctx.method - ctx.write(method).write(", ") - .write(query).write(queryList.toString()).write(", size: $querySize") - .write(", ") - .write(message).write(formList.toString()).write(", size: $formSize") - .end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("$schema://${address.hostName}:${address.port}/query-form-$it") - .addQueryString("key1", "query") - .addQueryStrings("list1", listOf("q1", "q2", "q3")) - .addFormInput("key1", "message") - .addFormInputs("list1", listOf("v1", "v2", "v3", "v4", "v5")) - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("POST, query[q1, q2, q3], size: 2, message[v1, v2, v3, v4, v5], size: 2", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response default html successfully.") - fun testContentProvider(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/not-found-*").handler { ctx -> - ctx.setStatus(HttpStatus.NOT_FOUND_404).setReason("Just so so") - .setHttpVersion(HttpVersion.HTTP_1_1) - .contentProvider(DefaultContentProvider(HttpStatus.NOT_FOUND_404, null, ctx)) - .end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/not-found-$it").submit() } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.NOT_FOUND_404, response.status) - if (protocol == "http1") { - assertEquals("Just so so", response.reason) - } - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response trailer successfully.") - fun testTrailer(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/trailer-*").handler { ctx -> - ctx.addCSV(HttpHeader.TRAILER, "t1", "t2", "t3") - .setTrailerSupplier { - val fields = HttpFields() - fields.put("t1", "trailer1") - fields.put("t2", "trailer2") - fields.put("t3", "trailer3") - fields - } - .write("response text success.") - .write("trailer.") - .end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/trailer-$it").submit() } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("t1, t2, t3", response.httpFields[HttpHeader.TRAILER]) - assertEquals("response text success.trailer.", response.stringBody) - assertEquals("trailer1", response.trailerSupplier.get()["t1"]) - assertEquals("trailer2", response.trailerSupplier.get()["t2"]) - assertEquals("trailer3", response.trailerSupplier.get()["t3"]) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should redirect successfully.") - fun testRedirect(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().get("/redirect-*").handler { ctx -> - ctx.redirect("http://${address.hostName}:${address.port}/r0") - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { httpClient.get("$schema://${address.hostName}:${address.port}/redirect-$it").submit() } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.FOUND_302, response.status) - assertEquals("http://${address.hostName}:${address.port}/r0", response.httpFields[HttpHeader.LOCATION]) - } - - finish(count, time, httpClient, httpServer) - } - - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should get cookies and set cookies successfully.") - fun testCookies(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().post("/cookies-*").handler { ctx -> - ctx.cookies = listOf( - Cookie("s1", "v1"), - Cookie("s2", "v2"), - Cookie("s3", ctx.cookies[0].value), - Cookie("s4", ctx.cookies[1].value) - ) - ctx.end("receive ${ctx.stringBody} ok.") - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("$schema://${address.hostName}:${address.port}/cookies-$it") - .body("cookies c1, c2.") - .cookies(listOf(Cookie("c1", "client1"), Cookie("c2", "client2"))) - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals(4, response.cookies.size) - assertEquals("receive cookies c1, c2. ok.", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should receive gbk content successfully.") - fun testGBK(protocol: String, schema: String): Unit = runTest { - val count = 100 - val charset = Charset.forName("GBK") - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().post("/gbk-*").handler { ctx -> - val content = ctx.getStringBody(charset) - ctx.contentProvider(stringBody("收到:${content}。长度:${ctx.contentLength}", charset)).end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("$schema://${address.hostName}:${address.port}/gbk-$it") - .body("发射!!Oooo", charset) - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response.getStringBody(charset)) - assertEquals(HttpStatus.OK_200, response.status) -// assertEquals("收到:发射!!Oooo。长度:12", response.getStringBody(charset)) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should accept 100 continue.") - fun testAccept100Continue(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().post("/100-continue-*").handler { ctx -> - ctx.end("receive ${ctx.stringBody} OK") - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("$schema://${address.hostName}:${address.port}/100-continue-$it") - .put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.value) - .body("100 continue content") - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("receive 100 continue content OK", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should not accept 100 continue and receive the error status successfully.") - fun testNotAccept100Continue(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer - .onHeaderComplete { ctx -> - ctx.setStatus(HttpStatus.PAYLOAD_TOO_LARGE_413).end("Content too large") - } - .router().post("/100-continue-*").handler { ctx -> - if (ctx.response.isCommitted) Result.DONE - else ctx.end("receive ${ctx.stringBody} OK") - } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("$schema://${address.hostName}:${address.port}/100-continue-$it") - .put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.value) - .body("100 continue content") - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.PAYLOAD_TOO_LARGE_413, response.status) - assertEquals("Content too large", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should upgrade http2 protocol successfully.") - fun testUpgradeHttp2(): Unit = runTest { - val count = 30 - - val httpServer = createHttpServer("http1", "http") - httpServer.router().get("/upgrade-http2-*").handler { ctx -> - ctx.end("Upgrade http2 success") - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.get("http://${address.hostName}:${address.port}/upgrade-http2-$it").upgradeHttp2().submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - val response = futures[0].await() - - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("Upgrade http2 success", response.stringBody) - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should trigger window update successfully.") - fun testBufferedWindowUpdate(): Unit = runTest { - val count = 1 - val content = (1..30_000_000).joinToString("") { "a" } - val httpConfig = HttpConfig() - httpConfig.initialSessionRecvWindow = HttpConfig.DEFAULT_WINDOW_SIZE - httpConfig.initialStreamRecvWindow = HttpConfig.DEFAULT_WINDOW_SIZE - - val httpServer = createHttpServer("http2", "https", httpConfig) - httpServer.router().post("/big-data-http2-*").handler { ctx -> - ctx.end("Received data success. length: ${ctx.stringBody.length}") - }.listen(address) - - val httpClient = HttpClientFactory.create(httpConfig) - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("https://${address.hostName}:${address.port}/big-data-http2-$it") - .contentProvider(ByteBufferContentProvider(ByteBuffer.wrap(content.toByteArray(StandardCharsets.UTF_8)))) - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - val response = futures[0].await() - - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertTrue(response.stringBody.contains("Received data success.")) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should receive multi part content successfully.") - fun testMultiPartContent(protocol: String, schema: String): Unit = runTest { - val count = 100 - - val httpServer = createHttpServer(protocol, schema) - httpServer.router().post("/multi-part-content-*").handler { ctx -> - val part1 = ctx.getPart("part1") - val part2 = ctx.getPart("part2") - val content = """ - |received part1: - |${part1.httpFields} - |${part1.stringBody} - | - |received part2: - |${part2.stringBody} - """.trimMargin() - ctx.end(content) - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - val part1 = HttpClientContentProviderFactory.stringBody("string content 1") - val fields1 = HttpFields() - fields1.put("hello", "hello part 1") - fields1.addCSV("part123", "1", "2", "3") - - val part2 = HttpClientContentProviderFactory.stringBody("file content 2") - httpClient.post("$schema://${address.hostName}:${address.port}/multi-part-content-$it") - .addPart("part1", part1, fields1) - .addFilePart("part2", "fileContent.txt", part2, HttpFields()) - .submit() - } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertTrue(response.stringBody.contains("hello")) - assertTrue(response.stringBody.contains("part123")) - assertTrue(response.stringBody.contains("string content 1")) - assertTrue(response.stringBody.contains("file content 2")) - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should send request by non persistence connection successfully.") - fun testClientShortConnection(): Unit = runTest { - val count = 30 - - val httpServer = createHttpServer("http1", "http") - httpServer.router().get("/close-*").handler { ctx -> - ctx.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.value) - .contentProvider(stringBody("Close connection success", StandardCharsets.UTF_8)) - .end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.get("http://${address.hostName}:${address.port}/close-$it") - .put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.value) - .submit() - } - - try { - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - val response = futures[0].await() - - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("Close connection success", response.stringBody) - } catch (e: Exception) { - println(e.message) - } - } - - finish(count, time, httpClient, httpServer) - } - - @Test - @DisplayName("should close connection by server successfully.") - fun testCloseConnectionByServer(): Unit = runTest { - val count = 30 - - val httpServer = createHttpServer("http1", "http") - httpServer.router().get("/close-*").handler { ctx -> - ctx.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.value) - .contentProvider(stringBody("Close connection success", StandardCharsets.UTF_8)) - .end() - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.get("http://${address.hostName}:${address.port}/close-$it").submit() - } - - try { - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - val response = futures[0].await() - val failureCount = futures.count { it.isCompletedExceptionally } - println("exception: $failureCount") - println(response) - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("Close connection success", response.stringBody) - } catch (e: Exception) { - println(e.message) - } - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response 413 when the multi-part too large.") - fun testMultiPartTooLarge(protocol: String, schema: String): Unit = runTest { - val count = 20 // TODO http1 client close connection exception - - val httpConfig = HttpConfig() - httpConfig.maxUploadFileSize = 10 - httpConfig.maxRequestBodySize = 10 - httpConfig.uploadFileSizeThreshold = 10 - val httpServer = createHttpServer(protocol, schema, httpConfig) - httpServer.router().post("/multi-part-content-*").handler { ctx -> - val part1 = ctx.getPart("part1") - val part2 = ctx.getPart("part2") - val content = """ - |received part1: - |${part1.httpFields} - |${part1.stringBody} - | - |received part2: - |${part2.stringBody} - """.trimMargin() - ctx.end(content) - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - val part1 = HttpClientContentProviderFactory.stringBody("string content 1") - val fields1 = HttpFields() - fields1.put("hello", "hello part 1") - fields1.addCSV("part123", "1", "2", "3") - - val part2 = HttpClientContentProviderFactory.stringBody("file content 2") - - httpClient.post("$schema://${address.hostName}:${address.port}/multi-part-content-$it") - .addPart("part1", part1, fields1) - .addFilePart("part2", "fileContent.txt", part2, HttpFields()) - .submit() - } - try { - withTimeout(Duration.ofSeconds(5).toMillis()) { - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - val failureCount = futures.count { it.isCompletedExceptionally } - println("failure count: $failureCount") - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.PAYLOAD_TOO_LARGE_413, response.status) - } - } catch (e: Exception) { - println(e.message) - } - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response 413 when the http body too large.") - fun testHttpBodyTooLarge(protocol: String, schema: String): Unit = runTest { - val count = 20 // TODO http1 client close connection exception - - val content = (1..30_000_000).joinToString("") { "a" } - val httpConfig = HttpConfig() - httpConfig.maxUploadFileSize = 10 - httpConfig.maxRequestBodySize = 10 - httpConfig.uploadFileSizeThreshold = 10 - val httpServer = createHttpServer(protocol, schema, httpConfig) - httpServer.router().post("/content-*").handler { ctx -> - ctx.end("ok") - }.listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count).map { - httpClient.post("$schema://${address.hostName}:${address.port}/content-$it") - .body(content) - .submit() - } - try { - withTimeout(Duration.ofSeconds(5).toMillis()) { - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - assertTrue(allDone) - val failureCount = futures.count { it.isCompletedExceptionally } - println("failure count: $failureCount") - - val response = futures[0].await() - println(response) - assertEquals(HttpStatus.PAYLOAD_TOO_LARGE_413, response.status) - } - } catch (e: Exception) { - println(e.message) - } - } - - finish(count, time, httpClient, httpServer) - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/content/handler/TestFormInputsContentHandler.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/content/handler/TestFormInputsContentHandler.kt deleted file mode 100644 index aa989ceb0..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/content/handler/TestFormInputsContentHandler.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.net.http.common.codec.UrlEncoded -import com.fireflysource.net.http.server.RoutingContext -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito.mock -import java.nio.charset.StandardCharsets - -class TestFormInputsContentHandler { - - private val context = mock(RoutingContext::class.java) - - @Test - @DisplayName("should decode web form inputs successfully") - fun test() { - val encoded = UrlEncoded() - encoded["key1"] = listOf("测试1", "$%^&====") - encoded["key2"] = listOf("v2") - encoded.add("key3", "v3") - encoded.add("key3", "v4") - val string = encoded.encode(StandardCharsets.UTF_8, true) - println(string) - val buffer = BufferUtils.toBuffer(string, StandardCharsets.UTF_8) - - val handler = FormInputsContentHandler() - handler.accept(buffer, context) - - assertEquals(listOf("测试1", "$%^&===="), handler.getFormInputs("key1")) - assertEquals("v2", handler.getFormInput("key2")) - assertEquals(listOf("v3", "v4"), handler.getFormInputs("key3")) - assertEquals(3, handler.getFormInputs().size) - println(handler.getFormInputs()) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/content/handler/TestMultiPartContentHandler.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/content/handler/TestMultiPartContentHandler.kt deleted file mode 100644 index b41ba4ad8..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/content/handler/TestMultiPartContentHandler.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.fireflysource.net.http.server.impl.content.handler - -import com.fireflysource.common.io.BufferUtils -import com.fireflysource.common.io.flipToFill -import com.fireflysource.common.io.flipToFlush -import com.fireflysource.common.io.useAwait -import com.fireflysource.net.http.client.HttpClientContentProviderFactory.stringBody -import com.fireflysource.net.http.client.impl.content.provider.MultiPartContentProvider -import com.fireflysource.net.http.common.exception.BadMessageException -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.server.RoutingContext -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito -import org.mockito.Mockito.`when` -import java.nio.ByteBuffer -import java.nio.charset.StandardCharsets -import java.util.* - -class TestMultiPartContentHandler { - - @Test - @DisplayName("should handle multi part content successfully.") - fun test(): Unit = runTest { - val provider = MultiPartContentProvider() - val buffer = createMultiPartContent(provider) - val ctx = mockRoutingContext(provider) - provider.closeAsync().await() - val handler = MultiPartContentHandler() - - handler.accept(buffer, ctx) - handler.closeAsync().await() - handler.getParts().forEach { - val body = BufferUtils.allocate(64) - val pos = body.flipToFill() - it.read(body) - body.flipToFlush(pos) - println( - """ - |------------------- - |${it.httpFields} - |${BufferUtils.toString(body)} - |------------------- - """.trimMargin() - ) - } - - assertEquals("Hello string body", handler.getPart("hello string")?.stringBody) - - val string2Part = handler.getPart("string 2") - requireNotNull(string2Part) - assertEquals("string body 2", string2Part.stringBody) - assertEquals("y1", string2Part.httpFields.get("x1")) - - val filePart = handler.getPart("file body") - requireNotNull(filePart) - assertEquals("file body 1", filePart.stringBody) - assertEquals("testFile.txt", filePart.fileName) - assertEquals("g1", filePart.httpFields.get("f1")) - } - - @Test - @DisplayName("should get the bad message exception.") - fun testEof(): Unit = runTest { - val provider = MultiPartContentProvider() - val ctx = mockRoutingContext(provider) - val handler = MultiPartContentHandler() - provider.closeAsync().await() - - val buf = BufferUtils.toBuffer((1..100).joinToString { "a" }) - handler.accept(buf, ctx) - - val success = try { - handler.closeAsync().await() - true - } catch (e: Exception) { - assertTrue(e is BadMessageException) - false - } - assertFalse(success) - } - - @Test - @DisplayName("should save content to temp file successfully") - fun testFileSizeThreshold(): Unit = runTest { - val provider = MultiPartContentProvider() - val buffer = createMultiPartContent(provider) - val ctx = mockRoutingContext(provider) - provider.closeAsync().await() - val handler = MultiPartContentHandler(uploadFileSizeThreshold = 100) - - val buffers = LinkedList() - while (buffer.hasRemaining()) { - val b = BufferUtils.allocate(32) - val pos = b.flipToFill() - BufferUtils.put(buffer, b) - b.flipToFlush(pos) - buffers.add(b) - } - - buffers.forEach { handler.accept(it, ctx) } - handler.closeAsync().await() - - val filePart = handler.getPart("file body") - requireNotNull(filePart) - assertEquals("file body 1", filePart.stringBody) - assertEquals("testFile.txt", filePart.fileName) - assertEquals("g1", filePart.httpFields.get("f1")) - - val bigFilePart = handler.getPart("bigFile") - requireNotNull(bigFilePart) - bigFilePart.useAwait { - assertTrue(bigFilePart.stringBody.isBlank()) - val bigFileBuffer = BufferUtils.allocate(500) - val pos = bigFileBuffer.flipToFill() - bigFilePart.read(bigFileBuffer).await() - bigFileBuffer.flipToFlush(pos) - - assertEquals(500, bigFileBuffer.remaining()) - val content = BufferUtils.toString(bigFileBuffer) - assertTrue(content.contains("ccccc")) - } - } - - private fun mockRoutingContext(provider: MultiPartContentProvider): RoutingContext { - val ctx = Mockito.mock(RoutingContext::class.java) - val httpFields = HttpFields() - httpFields.put(HttpHeader.CONTENT_TYPE, provider.contentType) - `when`(ctx.httpFields).thenReturn(httpFields) - return ctx - } - - private suspend fun createMultiPartContent(provider: MultiPartContentProvider): ByteBuffer { - val string1 = "Hello string body" - val string1Provider = stringBody(string1, StandardCharsets.UTF_8) - provider.addPart("hello string", string1Provider, null) - - val string2 = "string body 2" - val string2Provider = stringBody(string2, StandardCharsets.UTF_8) - val string2HttpFields = HttpFields() - string2HttpFields.put("x1", "y1") - provider.addPart("string 2", string2Provider, string2HttpFields) - - val file1 = "file body 1" - val file1Provider = stringBody(file1, StandardCharsets.UTF_8) - val file1HttpFields = HttpFields() - file1HttpFields.put("f1", "g1") - provider.addFilePart("file body", "testFile.txt", file1Provider, file1HttpFields) - - val bigFile = (1..500).joinToString(separator = "") { "c" } - val bigFileProvider = stringBody(bigFile, StandardCharsets.UTF_8) - provider.addFilePart("bigFile", "bigFile.txt", bigFileProvider, HttpFields()) - - val buffer = BufferUtils.allocate(provider.length().toInt()) - val pos = BufferUtils.flipToFill(buffer) - while (buffer.hasRemaining()) { - val len = provider.read(buffer).await() - if (len < 0) { - break - } - } - BufferUtils.flipToFlush(buffer, pos) - return buffer - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestAcceptHeaderMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestAcceptHeaderMatcher.kt deleted file mode 100644 index b53e8f55a..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestAcceptHeaderMatcher.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestAcceptHeaderMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by the accept-type successfully.") - fun test() { - val matcher = AcceptHeaderMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - matcher.add("application/json", router1) - matcher.add("application/json", router2) - matcher.add("text/html", router3) - - val result1 = matcher.match("text/html,application/xml;q=0.9,application/json;q=0.8") - requireNotNull(result1) - assertEquals(1, result1.routers.size) - assertEquals(Matcher.MatchType.ACCEPT, result1.matchType) - - val result2 = matcher.match("text/html;q=0.6,application/xml;q=0.7,application/json;q=0.8") - requireNotNull(result2) - assertEquals(2, result2.routers.size) - assertEquals(Matcher.MatchType.ACCEPT, result2.matchType) - - val result3 = matcher.match("text/html,application/xml,application/json") - requireNotNull(result3) - assertEquals(1, result3.routers.size) - assertEquals(Matcher.MatchType.ACCEPT, result3.matchType) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestHttpMethodMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestHttpMethodMatcher.kt deleted file mode 100644 index fea260536..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestHttpMethodMatcher.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestHttpMethodMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by the http method successfully.") - fun test() { - val matcher = HttpMethodMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - matcher.add(HttpMethod.GET.value, router1) - matcher.add("get", router2) - matcher.add("POST", router3) - - val result1 = matcher.match("GET") - requireNotNull(result1) - assertEquals(2, result1.routers.size) - assertEquals(Matcher.MatchType.METHOD, result1.matchType) - - val result2 = matcher.match("get") - requireNotNull(result2) - assertEquals(2, result2.routers.size) - assertEquals(Matcher.MatchType.METHOD, result2.matchType) - - val result3 = matcher.match("POST") - requireNotNull(result3) - assertEquals(1, result3.routers.size) - assertEquals(Matcher.MatchType.METHOD, result3.matchType) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestParameterPathMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestParameterPathMatcher.kt deleted file mode 100644 index 903ceaecf..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestParameterPathMatcher.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestParameterPathMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by parameter path successfully.") - fun test() { - val matcher = ParameterPathMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - - matcher.add("/hello/:foo", router1) - matcher.add("/:hello/:foo/", router2) - matcher.add("/hello/:foo/:bar", router3) - - val result1 = matcher.match("/hello/abc") - requireNotNull(result1) - assertEquals(2, result1.routers.size) - assertEquals(Matcher.MatchType.PATH, result1.matchType) - assertEquals("abc", result1.parameters[router1]?.get("foo")) - assertEquals("abc", result1.parameters[router2]?.get("foo")) - assertEquals("hello", result1.parameters[router2]?.get("hello")) - - val result2 = matcher.match("/hello/abc/eee/") - requireNotNull(result2) - assertEquals(1, result2.routers.size) - assertEquals(Matcher.MatchType.PATH, result2.matchType) - assertEquals("abc", result2.parameters[router3]?.get("foo")) - assertEquals("eee", result2.parameters[router3]?.get("bar")) - - val result3 = matcher.match("/hello/abc/eee/ddd") - assertNull(result3) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPatternedContentTypeMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPatternedContentTypeMatcher.kt deleted file mode 100644 index 3f40a579b..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPatternedContentTypeMatcher.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestPatternedContentTypeMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by content type successfully.") - fun test() { - val matcher = PatternedContentTypeMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - matcher.add("*/json", router1) - matcher.add("*/json", router2) - - val result = matcher.match("application/json;charset=utf-8") - requireNotNull(result) - assertEquals(2, result.routers.size) - assertEquals(Matcher.MatchType.CONTENT_TYPE, result.matchType) - assertEquals("application", result.parameters[router1]?.get("param0")) - assertEquals("application", result.parameters[router2]?.get("param0")) - - val result2 = matcher.match("text/html") - assertNull(result2) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPatternedPathMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPatternedPathMatcher.kt deleted file mode 100644 index 067df09b1..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPatternedPathMatcher.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestPatternedPathMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by the path successfully.") - fun test() { - val matcher = PatternedPathMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - val router4 = AsyncRouter(4, routerManager) - matcher.add("*", router1) - matcher.add("/*", router2) - matcher.add("/he*/*", router3) - matcher.add("/hello*", router4) - - val result1 = matcher.match("/test") - requireNotNull(result1) - assertEquals(Matcher.MatchType.PATH, result1.matchType) - assertEquals(2, result1.routers.size) - assertEquals("/test", result1.parameters[router1]?.get("param0")) - assertEquals("test", result1.parameters[router2]?.get("param0")) - - val result2 = matcher.match("/hello/1") - requireNotNull(result2) - assertEquals(Matcher.MatchType.PATH, result2.matchType) - assertEquals(4, result2.routers.size) - assertEquals("llo", result2.parameters[router3]?.get("param0")) - assertEquals("1", result2.parameters[router3]?.get("param1")) - assertEquals("/1", result2.parameters[router4]?.get("param0")) - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPreciseContentTypeMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPreciseContentTypeMatcher.kt deleted file mode 100644 index c5011eebc..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPreciseContentTypeMatcher.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestPreciseContentTypeMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by the precise content type successfully.") - fun test() { - val matcher = PreciseContentTypeMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - matcher.add("application/json", router1) - matcher.add("application/json", router2) - matcher.add("text/json", router3) - - val result1 = matcher.match("application/json;charset=utf-8") - requireNotNull(result1) - assertEquals(2, result1.routers.size) - assertEquals(Matcher.MatchType.CONTENT_TYPE, result1.matchType) - - val result2 = matcher.match("text/json") - requireNotNull(result2) - assertEquals(1, result2.routers.size) - assertEquals(Matcher.MatchType.CONTENT_TYPE, result2.matchType) - - val result3 = matcher.match("text/html") - assertNull(result3) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPrecisePathMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPrecisePathMatcher.kt deleted file mode 100644 index 558c35f73..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestPrecisePathMatcher.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestPrecisePathMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by the precise path successfully.") - fun test() { - val matcher = PrecisePathMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - val router4 = AsyncRouter(4, routerManager) - matcher.add("/", router1) - matcher.add("/hello", router2) - matcher.add("/hello/", router3) - matcher.add("/hello/foo", router4) - - val result1 = matcher.match("/") - requireNotNull(result1) - assertEquals(Matcher.MatchType.PATH, result1.matchType) - assertEquals(1, result1.routers.size) - assertEquals(1, result1.routers.first().id) - - val result2 = matcher.match("/hello") - requireNotNull(result2) - assertEquals(Matcher.MatchType.PATH, result2.matchType) - assertEquals(2, result2.routers.size) - - val result3 = matcher.match("/hello/foo/") - requireNotNull(result3) - assertEquals(Matcher.MatchType.PATH, result3.matchType) - assertEquals(1, result3.routers.size) - assertEquals(4, result3.routers.first().id) - - val result4 = matcher.match("/hello/foo/bar") - assertNull(result4) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestRegexPathMatcher.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestRegexPathMatcher.kt deleted file mode 100644 index 22521d956..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/matcher/TestRegexPathMatcher.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.fireflysource.net.http.server.impl.matcher - -import com.fireflysource.net.http.server.Matcher -import com.fireflysource.net.http.server.impl.router.AsyncRouter -import com.fireflysource.net.http.server.impl.router.AsyncRouterManager -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito - -class TestRegexPathMatcher { - - private val routerManager = Mockito.mock(AsyncRouterManager::class.java) - - @Test - @DisplayName("should match router by regex path successfully.") - fun test() { - val matcher = RegexPathMatcher() - val router1 = AsyncRouter(1, routerManager) - val router2 = AsyncRouter(2, routerManager) - val router3 = AsyncRouter(3, routerManager) - - matcher.add("/hello(\\d*)", router1) - matcher.add("/hello(\\d*)", router2) - matcher.add("/foo/([a-c]+)", router3) - - val result1 = matcher.match("/hello345") - requireNotNull(result1) - assertEquals(2, result1.routers.size) - assertEquals(Matcher.MatchType.PATH, result1.matchType) - assertEquals("345", result1.parameters[router2]?.get("group1")) - - val result2 = matcher.match("/foo/abccc") - requireNotNull(result2) - assertEquals(1, result2.routers.size) - assertEquals(Matcher.MatchType.PATH, result2.matchType) - assertEquals("abccc", result2.parameters[router3]?.get("group1")) - - val result3 = matcher.match("/foo/abcde") - assertNull(result3) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/TestAsyncRouterManager.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/TestAsyncRouterManager.kt deleted file mode 100644 index 8241916c8..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/TestAsyncRouterManager.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.fireflysource.net.http.server.impl.router - -import com.fireflysource.net.http.common.model.HttpFields -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpURI -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.net.http.server.Matcher.MatchType.* -import com.fireflysource.net.http.server.RoutingContext -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.mockito.Mockito -import org.mockito.Mockito.`when` - -class TestAsyncRouterManager { - - private val httpServer: HttpServer = Mockito.mock(HttpServer::class.java) - - @Test - @DisplayName("should find routers") - fun test() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register().get("/hello") - routerManager.register().post("/hello") - routerManager.register().put("/hello") - routerManager.register().delete("/hello") - - var ctx = createContext("GET", "/hello") - var result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(0, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - - ctx = createContext("POST", "/hello") - result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(1, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - - ctx = createContext("PUT", "/hello") - result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(2, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - - ctx = createContext("DELETE", "/hello") - result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(3, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - } - - @Test - @DisplayName("should not find routers") - fun testNotFound() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register().get("/hello") - - val ctx = createContext("POST", "/hello") - val result1 = routerManager.findRouters(ctx) - assertTrue(result1.isEmpty()) - } - - @Test - @DisplayName("should find routers by paths successfully") - fun testPaths() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register().paths(listOf("/hello", "/hello/*")) - routerManager.register().paths(listOf("/hello/:name", "/foo")) - - val ctx1 = createContext("POST", "/hello") - val result1 = routerManager.findRouters(ctx1) - assertEquals(1, result1.size) - assertEquals(0, result1.first().router.id) - assertTrue(result1.first().matchTypes.containsAll(listOf(PATH))) - - val ctx2 = createContext("POST", "/hello/xxx") - val result2 = routerManager.findRouters(ctx2) - assertEquals(2, result2.size) - assertTrue(result2.first().matchTypes.containsAll(listOf(PATH))) - - val ctx3 = createContext("PUT", "/foo") - val result3 = routerManager.findRouters(ctx3) - assertEquals(1, result3.size) - assertEquals(1, result3.first().router.id) - assertTrue(result3.first().matchTypes.containsAll(listOf(PATH))) - } - - @Test - @DisplayName("should find routers by content type successfully") - fun testConsume() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register().path("/hello").consumes("*/json") - routerManager.register().path("/hello").consumes("text/html") - - val ctx1 = createContext("GET", "/hello", "application/json") - val result1 = routerManager.findRouters(ctx1) - assertEquals(1, result1.size) - assertEquals(0, result1.first().router.id) - assertTrue(result1.first().matchTypes.containsAll(listOf(PATH, CONTENT_TYPE))) - - val ctx2 = createContext("GET", "/hello", "text/html") - val result2 = routerManager.findRouters(ctx2) - assertEquals(1, result2.size) - assertEquals(1, result2.first().router.id) - assertTrue(result1.first().matchTypes.containsAll(listOf(PATH, CONTENT_TYPE))) - - val ctx3 = createContext("POST", "/test", "text/html") - val result3 = routerManager.findRouters(ctx3) - assertTrue(result3.isEmpty()) - } - - @Test - @DisplayName("should find routers by accept successfully") - fun testAccept() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register().path("/*").produces("application/json") - routerManager.register().path("/hello").produces("text/html") - - var ctx = createContext("GET", "/hello", "", "text/html,application/xml;q=0.9,application/json;q=0.8") - var result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(1, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(PATH, ACCEPT))) - - ctx = createContext("GET", "/hello", "", "text/html;q=0.6,application/xml;q=0.7,application/json;q=0.8") - result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(0, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(PATH, ACCEPT))) - } - - @Test - @DisplayName("should find routers by regex successfully") - fun testRegex() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register(30).pathRegex("/foo/([a-c]+)") - routerManager.register(20).path("/foo/*") - - var ctx = createContext("GET", "/foo/abc", "", "text/html,application/xml;q=0.9,application/json;q=0.8") - var result = routerManager.findRouters(ctx) - assertEquals(2, result.size) - assertEquals(20, result.first().router.id) - assertEquals(30, result.last().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(PATH))) - - ctx = createContext("GET", "/foo/ddd") - result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(20, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(PATH))) - } - - private fun createContext( - method: String, - uri: String, - contentType: String = "", - accept: String = "" - ): RoutingContext { - val ctx = Mockito.mock(RoutingContext::class.java) - `when`(ctx.method).thenReturn(method) - `when`(ctx.uri).thenReturn(HttpURI(uri)) - val fields = HttpFields() - fields.put(HttpHeader.CONTENT_TYPE, contentType) - fields.put(HttpHeader.ACCEPT, accept) - `when`(ctx.httpFields).thenReturn(fields) - `when`(ctx.contentType).thenReturn(contentType) - return ctx - } - - @Test - fun testCopy() { - val routerManager = AsyncRouterManager(httpServer) - routerManager.register().get("/hello") - routerManager.register().post("/hello") - routerManager.register().put("/hello") - routerManager.register().delete("/hello") - - var ctx = createContext("GET", "/hello") - var result = routerManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(0, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - - val newRouterManager = routerManager.copy(httpServer) - newRouterManager.register().get("/helloCopy") - - ctx = createContext("POST", "/hello") - result = newRouterManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(1, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - - ctx = createContext("GET", "/helloCopy") - result = newRouterManager.findRouters(ctx) - assertEquals(1, result.size) - assertEquals(4, result.first().router.id) - assertTrue(result.first().matchTypes.containsAll(listOf(METHOD, PATH))) - - ctx = createContext("GET", "/helloCopy") - result = routerManager.findRouters(ctx) - assertTrue(result.isEmpty()) - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/handler/TestCorsHandler.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/handler/TestCorsHandler.kt deleted file mode 100644 index a64a96c1c..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/handler/TestCorsHandler.kt +++ /dev/null @@ -1,369 +0,0 @@ -package com.fireflysource.net.http.server.impl.router.handler - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.client.HttpClientResponse -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpMethod -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.impl.AbstractHttpServerTestBase -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource -import java.util.concurrent.CompletableFuture -import kotlin.system.measureTimeMillis - -class TestCorsHandler : AbstractHttpServerTestBase() { - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should allow request successfully.") - fun testSimpleRequest(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().get("/cors-simple-request/*").handler { it.end("success") } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/cors-simple-request/$it") - .put(HttpHeader.ORIGIN, "simple.request.cors.test.com") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("success", response.stringBody) - assertEquals("simple.request.cors.test.com", response.httpFields[HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN]) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response expose headers successfully.") - fun testExposeHeaders(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - corsConfig.exposeHeaders = setOf("x1-x2", "x3-x4") - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().get("/cors-simple-request/*").handler { it.end("success") } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/cors-simple-request/$it") - .put(HttpHeader.ORIGIN, "simple.request.cors.test.com") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("success", response.stringBody) - assertEquals("simple.request.cors.test.com", response.httpFields[HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN]) - assertEquals("x1-x2, x3-x4", response.httpFields[HttpHeader.ACCESS_CONTROL_EXPOSE_HEADERS]) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should not allow origin via simple request.") - fun testNotAllowOriginSimpleRequest(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().get("/cors-simple-request/*").handler { it.end("success") } - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/cors-simple-request/$it") - .put(HttpHeader.ORIGIN, "www.fireflysource.com") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.FORBIDDEN_403, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should response preflight request successfully.") - fun testPreflightRequest(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().post("/cors-data-request/*").handler { it.end("success") } - .listen(address) - - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient - .request( - HttpMethod.OPTIONS, - "$schema://${address.hostName}:${address.port}/cors-data-request/$it" - ) - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.value) - .put(HttpHeader.ACCESS_CONTROL_REQUEST_HEADERS, HttpHeader.CONTENT_TYPE.lowerCaseValue) - .submit() - .thenCompose { resp -> - if (resp.status == HttpStatus.NO_CONTENT_204) { - httpClient.post("$schema://${address.hostName}:${address.port}/cors-data-request/$it") - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .body( - """ - |{"id": 333} - """.trimMargin() - ) - .submit() - } else { - val future = CompletableFuture() - future.complete(resp) - future - } - } - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("success", response.stringBody) - assertEquals("data.request.cors.test.com", response.httpFields[HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN]) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should block by preflight request when the headers do not allow to access.") - fun testNotAllowHeader(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().post("/cors-data-request/*").handler { it.end("success") } - .listen(address) - - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient - .request( - HttpMethod.OPTIONS, - "$schema://${address.hostName}:${address.port}/cors-data-request/$it" - ) - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.value) - .put(HttpHeader.ACCESS_CONTROL_REQUEST_HEADERS, "x1-x2") - .submit() - .thenCompose { resp -> - if (resp.status == HttpStatus.NO_CONTENT_204) { - httpClient.post("$schema://${address.hostName}:${address.port}/cors-data-request/$it") - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .body( - """ - |{"id": 333} - """.trimMargin() - ) - .submit() - } else { - val future = CompletableFuture() - future.complete(resp) - future - } - } - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.BAD_REQUEST_400, response.status) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should block by preflight request when the methods do not allow to access.") - fun testNotAllowMethod(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - corsConfig.allowMethods = setOf(HttpMethod.GET.value) - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().post("/cors-data-request/*").handler { it.end("success") } - .listen(address) - - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient - .request( - HttpMethod.OPTIONS, - "$schema://${address.hostName}:${address.port}/cors-data-request/$it" - ) - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.value) - .put(HttpHeader.ACCESS_CONTROL_REQUEST_HEADERS, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .submit() - .thenCompose { resp -> - if (resp.status == HttpStatus.NO_CONTENT_204) { - httpClient.post("$schema://${address.hostName}:${address.port}/cors-data-request/$it") - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .body( - """ - |{"id": 333} - """.trimMargin() - ) - .submit() - } else { - val future = CompletableFuture() - future.complete(resp) - future - } - } - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.METHOD_NOT_ALLOWED_405, response.status) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should not allow origin via preflight.") - fun testNotAllowOriginPreflight(protocol: String, schema: String): Unit = runTest { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val corsConfig = CorsConfig("*.cors.test.com") - corsConfig.allowMethods = setOf(HttpMethod.GET.value) - httpServer - .router().path("*").handler(CorsHandler(corsConfig)) - .router().post("/cors-data-request/*").handler { it.end("success") } - .listen(address) - - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient - .request( - HttpMethod.OPTIONS, - "$schema://${address.hostName}:${address.port}/cors-data-request/$it" - ) - .put(HttpHeader.ORIGIN, "www.fireflysource.com") - .put(HttpHeader.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.value) - .put(HttpHeader.ACCESS_CONTROL_REQUEST_HEADERS, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .submit() - .thenCompose { resp -> - if (resp.status == HttpStatus.NO_CONTENT_204) { - httpClient.post("$schema://${address.hostName}:${address.port}/cors-data-request/$it") - .put(HttpHeader.ORIGIN, "data.request.cors.test.com") - .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .body( - """ - |{"id": 333} - """.trimMargin() - ) - .submit() - } else { - val future = CompletableFuture() - future.complete(resp) - future - } - } - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.FORBIDDEN_403, response.status) - } - - finish(count, time, httpClient, httpServer) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/handler/TestFileHandler.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/handler/TestFileHandler.kt deleted file mode 100644 index 400d5bb1b..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/http/server/impl/router/handler/TestFileHandler.kt +++ /dev/null @@ -1,200 +0,0 @@ -package com.fireflysource.net.http.server.impl.router.handler - -import com.fireflysource.common.concurrent.exceptionallyAccept -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.server.impl.AbstractHttpServerTestBase -import kotlinx.coroutines.future.await -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource -import java.nio.file.Paths -import java.util.* -import java.util.concurrent.CompletableFuture -import kotlin.system.measureTimeMillis - -class TestFileHandler : AbstractHttpServerTestBase() { - - private fun createFileHandler(): FileHandler { - val path = Optional.ofNullable(FileHandler::class.java.classLoader.getResource("files")) - .map { it.toURI() } - .map { Paths.get(it) } - .map { it.toString() } - .orElse("") - val fileConfig = FileConfig(path) - return FileHandler(fileConfig) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should get file successfully.") - fun testFile(protocol: String, schema: String): Unit = runBlocking { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val fileHandler = createFileHandler() - httpServer - .router().paths(listOf("/favicon.ico", "/*.html", "/*.txt")).handler(fileHandler) - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/poem.html") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.OK_200, response.status) - assertEquals("text/html", response.httpFields[HttpHeader.CONTENT_TYPE]) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should not find the file.") - fun testFileNotFound(protocol: String, schema: String): Unit = runBlocking { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val fileHandler = createFileHandler() - httpServer - .router().paths(listOf("/favicon.ico", "/*.html", "/*.txt")).handler(fileHandler) - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/poem-$it.html") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.NOT_FOUND_404, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should get partial file successfully.") - fun testPartialFile(protocol: String, schema: String): Unit = runBlocking { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val fileHandler = createFileHandler() - httpServer - .router().paths(listOf("/favicon.ico", "/*.html", "/*.txt")).handler(fileHandler) - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/poem.html") - .put(HttpHeader.RANGE, "bytes=5-10") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.PARTIAL_CONTENT_206, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should get partial file unsuccessfully.") - fun testRangeNotSatisfiable(protocol: String, schema: String): Unit = runBlocking { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val fileHandler = createFileHandler() - httpServer - .router().paths(listOf("/favicon.ico", "/*.html", "/*.txt")).handler(fileHandler) - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/poem.html") - .put(HttpHeader.RANGE, "bytes=1000000000000000000-") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.RANGE_NOT_SATISFIABLE_416, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should not response multi-range.") - fun testMultiRange(protocol: String, schema: String): Unit = runBlocking { - val count = 1 - - val httpServer = createHttpServer(protocol, schema) - val fileHandler = createFileHandler() - httpServer - .router().paths(listOf("/favicon.ico", "/*.html", "/*.txt")).handler(fileHandler) - .listen(address) - - val httpClient = HttpClientFactory.create() - val time = measureTimeMillis { - val futures = (1..count) - .map { - httpClient.get("$schema://${address.hostName}:${address.port}/poem.html") - .put(HttpHeader.RANGE, "bytes=5-20,35-65,-5") - .submit() - } - futures.forEach { f -> f.exceptionallyAccept { println(it.message) } } - CompletableFuture.allOf(*futures.toTypedArray()).await() - val allDone = futures.all { it.isDone } - Assertions.assertTrue(allDone) - - val response = futures[0].await() - - assertEquals(HttpStatus.RANGE_NOT_SATISFIABLE_416, response.status) - println(response) - } - - finish(count, time, httpClient, httpServer) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/aio/TestAdaptiveBufferSize.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/aio/TestAdaptiveBufferSize.kt deleted file mode 100644 index f88037712..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/aio/TestAdaptiveBufferSize.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -/** - * @author Pengtao Qiu - */ -class TestAdaptiveBufferSize { - - @Test - @DisplayName("should increase or decrease the buffer size when the data size changes.") - fun test() { - val bufSize = AdaptiveBufferSize() - assertEquals(128, bufSize.getBufferSize()) - - bufSize.update(1024) - assertEquals(256, bufSize.getBufferSize()) - - bufSize.update(1024) - assertEquals(512, bufSize.getBufferSize()) - - bufSize.update(1024) - assertEquals(1024, bufSize.getBufferSize()) - - bufSize.update(2048) - assertEquals(2048, bufSize.getBufferSize()) - - bufSize.update(4096) - assertEquals(4096, bufSize.getBufferSize()) - - bufSize.update(500) - assertEquals(2048, bufSize.getBufferSize()) - - bufSize.update(100) - assertEquals(1024, bufSize.getBufferSize()) - - repeat(100) { bufSize.update(512 * 1024) } - assertEquals(512 * 1024, bufSize.getBufferSize()) - - repeat(100) { bufSize.update(15) } - assertEquals(128, bufSize.getBufferSize()) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/aio/TestAioServerAndClient.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/aio/TestAioServerAndClient.kt deleted file mode 100644 index 0ecc84905..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/aio/TestAioServerAndClient.kt +++ /dev/null @@ -1,306 +0,0 @@ -package com.fireflysource.net.tcp.aio - -import com.fireflysource.common.coroutine.CoroutineDispatchers.defaultPoolSize -import com.fireflysource.common.coroutine.event -import com.fireflysource.common.coroutine.eventAsync -import com.fireflysource.common.sys.Result.discard -import com.fireflysource.net.tcp.* -import com.fireflysource.net.tcp.onAcceptAsync -import com.fireflysource.net.tcp.secure.conscrypt.NoCheckConscryptSSLContextFactory -import com.fireflysource.net.tcp.secure.conscrypt.SelfSignedCertificateConscryptSSLContextFactory -import com.fireflysource.net.tcp.secure.wildfly.NoCheckWildflySSLContextFactory -import com.fireflysource.net.tcp.secure.wildfly.SelfSignedCertificateWildflySSLContextFactory -import kotlinx.coroutines.delay -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withTimeout -import kotlinx.coroutines.withTimeoutOrNull -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.nio.ByteBuffer -import java.util.concurrent.atomic.AtomicInteger -import java.util.stream.Stream -import kotlin.math.roundToLong -import kotlin.random.Random -import kotlin.system.measureTimeMillis - - -/** - * @author Pengtao Qiu - */ -class TestAioServerAndClient { - - companion object { - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - arguments("single", true, false, "default"), - arguments("array", true, false, "default"), - arguments("list", true, false, "default"), - - arguments("single", true, false, "conscrypt"), - arguments("array", true, false, "conscrypt"), - arguments("list", true, false, "conscrypt"), - -// arguments("single", true, false, "wildfly"), -// arguments("array", true, false, "wildfly"), -// arguments("list", true, false, "wildfly"), - - arguments("single", true, true, "default"), - arguments("array", true, true, "default"), - arguments("list", true, true, "default"), - - arguments("single", false, false, "default"), - arguments("array", false, false, "default"), - arguments("list", false, false, "default"), - - arguments("single", false, true, "default"), - arguments("array", false, true, "default"), - arguments("list", false, true, "default") - ) - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should send and receive messages successfully.") - fun test(bufType: String, enableSecure: Boolean, enableBuffer: Boolean, securityProvider: String) = runTest { - val host = "localhost" - val port = Random.nextInt(30000, 50000) - - val connectionCount = defaultPoolSize - val maxMessageCountPerOneConnection = 32 - val expectMessageCount = maxMessageCountPerOneConnection * connectionCount - - val messageCount = AtomicInteger() - val tcpConfig = TcpConfig( - timeout = 30, - enableSecureConnection = enableSecure, - enableOutputBuffer = enableBuffer - ) - - val server = TcpServerFactory.create(tcpConfig) - when (securityProvider) { - "conscrypt" -> server.secureEngineFactory(SelfSignedCertificateConscryptSSLContextFactory()) - "wildfly" -> server.secureEngineFactory(SelfSignedCertificateWildflySSLContextFactory()) - } - server.onAcceptAsync { connection -> - println("accept connection. ${connection.id}") - connection.beginHandshake().await() - readLoop@ while (true) { - val buf = try { - connection.read(3000L) - } catch (e: Exception) { - println(e.message + "|" + e::class.java.name) - break@readLoop - } - - writeLoop@ while (buf.hasRemaining()) { - val num = buf.int - val newBuf = ByteBuffer.allocate(4) - newBuf.putInt(num).flip() - - if (num == maxMessageCountPerOneConnection) { - connection.write(newBuf) - try { - connection.flush(300L) - } catch (e: Exception) { - println(e.message + "|" + e::class.java.name) - } - break@readLoop - } else { - connection.write(newBuf, discard()) - } - } - } - }.listen(host, port) - - val client = TcpClientFactory.create(tcpConfig) - when (securityProvider) { - "conscrypt" -> client.secureEngineFactory(NoCheckConscryptSSLContextFactory()) - "wildfly" -> client.secureEngineFactory(NoCheckWildflySSLContextFactory()) - } - val time = measureTimeMillis { - val jobs = (1..connectionCount).map { - event { - val connection = withTimeout(1000L) { - val c = client.connect(host, port).await() - println("create connection. ${c.id}") - c.beginHandshake().await() - c - } - - val readingJob = connection.coroutineScope.launch { - readLoop@ while (true) { - val buf = try { - connection.read(3000L) - } catch (e: Exception) { - println(e.message + "|" + e::class.java.name) - break@readLoop - } - - writeLoop@ while (buf.hasRemaining()) { - val num = buf.int - messageCount.incrementAndGet() - - if (num == maxMessageCountPerOneConnection) { - try { - connection.close(1000L) - } catch (e: Exception) { - println(e.message + "|" + e::class.java.name) - } - break@readLoop - } - } - } - } - - when (bufType) { - "single" -> { - (1..maxMessageCountPerOneConnection).forEach { i -> - val buf = ByteBuffer.allocate(4) - buf.putInt(i).flip() - connection.write(buf, discard()) - } - connection.flush() - } - "array" -> { - val bufArray = Array(maxMessageCountPerOneConnection) { index -> - val buf = ByteBuffer.allocate(4) - buf.putInt(index + 1).flip() - buf - } - connection.write(bufArray, 0, bufArray.size, discard()) - connection.flush() - } - "list" -> { - val bufList = List(maxMessageCountPerOneConnection) { index -> - val buf = ByteBuffer.allocate(4) - buf.putInt(index + 1).flip() - buf - } - connection.write(bufList, 0, bufList.size, discard()) - connection.flush() - } - } - - readingJob.join() - - assertTrue(connection.readBytes > 0) - assertTrue(connection.writtenBytes > 0) - assertTrue(connection.lastActiveTime > 0L) - assertTrue(connection.lastReadTime > 0L) - assertTrue(connection.lastWrittenTime > 0L) - } - } - - jobs.forEach { it.join() } - - assertEquals(expectMessageCount, messageCount.get()) - } - - val throughput = expectMessageCount / (time / 1000.00) - println("success. $time ms, ${throughput.roundToLong()} qps") - println("connection: ${connectionCount}, messageCount: $expectMessageCount") - - val stopTime = measureTimeMillis { - client.stop() - server.stop() - } - println("stop success. $stopTime") - } - - @Test - @DisplayName("should close when the connection is timeout.") - fun testTimeout() = runTest { - val host = "localhost" - val port = Random.nextInt(10000, 50000) - - val server = TcpServerFactory.create(TcpConfig(30)).onAcceptAsync { conn -> - conn.setReadTimeout(1) - conn.setWriteTimeout(1) - try { - conn.read().await() - println("Server reads success.") - } catch (e: Exception) { - println("Server reads failure. ${e.javaClass.name}") - } - }.listen(host, port) - - val client = TcpClientFactory.create(TcpConfig(30)) - val connection = client.connect(host, port).await() - assertEquals(port, connection.remoteAddress.port) - - connection.setReadTimeout(1) - connection.setWriteTimeout(1) - val success = try { - connection.read().await() - true - } catch (e: Exception) { - println("Client reads failure. ${e.javaClass.name}") - false - } - delay(500) - assertFalse(success) - assertTrue(connection.duration > 0) - assertTrue(connection.isShutdownInput) - - val stopTime = measureTimeMillis { - client.stop() - server.stop() - } - println("stop success. $stopTime") - } - - @Test - @DisplayName("should close connection successfully.") - fun testClose(): Unit = runTest { - val host = "localhost" - val port = Random.nextInt(10000, 50000) - - val server = TcpServerFactory.create().onAcceptAsync { connection -> - try { - connection.read(2000L) - println("Server reads success.") - } catch (e: Exception) { - println("Server reads failure. ${e.javaClass.name}") - } - }.listen(host, port) - - val client = TcpClientFactory.create() - val connection = client.connect(host, port).await() - - val success = eventAsync { - try { - connection.read(2000L) - println("Client reads success.") - true - } catch (e: Exception) { - println("Client reads failure. ${e.javaClass.name}") - false - } - } - - connection.closeAsync().await() - println("close client connection success") - assertTrue(connection.isShutdownOutput) - assertTrue(connection.isShutdownInput) - assertTrue(connection.isClosed) - assertFalse(success.await()) - - val stopTime = measureTimeMillis { - withTimeoutOrNull(2000L) { - client.stop() - server.stop() - } - } - println("stop success. $stopTime") - } - -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/buffer/TestMessage.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/buffer/TestMessage.kt deleted file mode 100644 index 57005b0ec..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/buffer/TestMessage.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.fireflysource.net.tcp.buffer - -import com.fireflysource.common.sys.Result.discard -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.CsvSource -import java.nio.ByteBuffer - -/** - * @author Pengtao Qiu - */ -class TestMessage { - - @ParameterizedTest - @CsvSource(value = ["4,2", "2,3", "-1,3", "0,0", "0,-5", "0,6"]) - @DisplayName("the length should be less than and equal buffers size subtracts offset") - fun testOutOfBoundException(offset: Int, length: Int) { - val size = 4 - assertThrows { - OutputBuffers(Array(size) { ByteBuffer.allocate(16) }, offset, length, discard()) - } - - assertThrows { - OutputBufferList(List(size) { ByteBuffer.allocate(16) }, offset, length, discard()) - } - } - - @ParameterizedTest - @CsvSource(value = ["0,3", "1,3", "2,2", "3,1", "0,6", "1,5", "2,3"]) - @DisplayName("the current offset and length should change by the buffers consume") - fun testCurrentOffsetAndLength(offset: Int, length: Int) { - val size = 6 - val capacity = 16 - val buffers = OutputBuffers(Array(size) { ByteBuffer.allocate(capacity) }, offset, length, discard()) - buffers.buffers[offset].putInt(1) - assertEquals(offset, buffers.getCurrentOffset()) - assertEquals(length, buffers.getCurrentLength()) - - buffers.buffers[offset].putLong(2) - buffers.buffers[offset].putInt(3) - assertEquals(offset + 1, buffers.getCurrentOffset()) - assertEquals(length - 1, buffers.getCurrentLength()) - } - - @ParameterizedTest - @CsvSource(value = ["0,3", "1,3", "2,2", "3,1", "0,6", "1,5", "2,3"]) - @DisplayName("should not get the remaining") - fun testHasRemaining(offset: Int, length: Int) { - val size = 6 - val capacity = 16 - val buffers = OutputBuffers(Array(size) { ByteBuffer.allocate(capacity) }, offset, length, discard()) - - val lastIndex = offset + length - 1 - val bytes = ByteArray(capacity) - (offset..lastIndex).forEach { i -> buffers.buffers[i].put(bytes) } - assertFalse(buffers.hasRemaining()) - assertEquals(0, buffers.getCurrentLength()) - assertEquals(offset + length, buffers.getCurrentOffset()) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/secure/TestSSLContextFactory.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/secure/TestSSLContextFactory.kt deleted file mode 100644 index da02a10cf..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/tcp/secure/TestSSLContextFactory.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.fireflysource.net.tcp.secure - -import com.fireflysource.net.tcp.secure.common.AbstractSecureEngineFactory -import com.fireflysource.net.tcp.secure.conscrypt.FileConscryptSSLContextFactory -import com.fireflysource.net.tcp.secure.conscrypt.SelfSignedCertificateConscryptSSLContextFactory -import com.fireflysource.net.tcp.secure.jdk.FileOpenJdkSSLContextFactory -import com.fireflysource.net.tcp.secure.jdk.SelfSignedCertificateOpenJdkSSLContextFactory -import com.fireflysource.net.tcp.secure.utils.SecureUtils -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.security.KeyStore -import java.util.stream.Stream -import javax.net.ssl.KeyManagerFactory -import javax.net.ssl.TrustManagerFactory - -class TestSSLContextFactory { - - companion object { - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - arguments( - "jsse", - FileOpenJdkSSLContextFactory("fireflyKeystore.jks", "123456", "654321", "JKS") - ), - arguments( - "jsse", - SelfSignedCertificateOpenJdkSSLContextFactory() - ), - arguments( - "Conscrypt", - FileConscryptSSLContextFactory("fireflyKeystore.jks", "123456", "654321", "JKS") - ), - arguments( - "Conscrypt", - SelfSignedCertificateConscryptSSLContextFactory() - ) - ) - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should create secure engine factory successfully.") - fun test(name: String, factory: AbstractSecureEngineFactory) { - val sslContext = factory.sslContext - println(sslContext.provider.name) - println(sslContext.provider.info) - assertTrue(sslContext.provider.name.contains(name, true)) - } - - @Test - @DisplayName("should load self signed certificate successfully.") - fun testKeyStore() { - val ks = KeyStore.getInstance("JKS") - SecureUtils.getSelfSignedCertificate().use { - ks.load(it, "123456".toCharArray()) - println("size: ${ks.size()}") - assertTrue(ks.size() > 0) - val certificate = ks.getCertificate("fireflyselfcert") - println(certificate.type) - assertEquals("X.509", certificate.type) - - val km = KeyManagerFactory.getInstance("SunX509") - km.init(ks, "654321".toCharArray()) - assertTrue(km.keyManagers.isNotEmpty()) - val tmf = TrustManagerFactory.getInstance("SunX509") - tmf.init(ks) - assertTrue(tmf.trustManagers.isNotEmpty()) - } - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/websocket/TestWebSocketServerAndClient.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/websocket/TestWebSocketServerAndClient.kt deleted file mode 100644 index ee4706e51..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/websocket/TestWebSocketServerAndClient.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.fireflysource.net.websocket - -import com.fireflysource.common.sys.Result -import com.fireflysource.net.http.client.HttpClientFactory -import com.fireflysource.net.http.server.HttpServerFactory -import com.fireflysource.net.websocket.common.frame.Frame -import com.fireflysource.net.websocket.common.frame.TextFrame -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream -import kotlin.random.Random - -/** - * @author Pengtao Qiu - */ -class TestWebSocketServerAndClient { - - companion object { - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - arguments("none", "ws"), - arguments("fragment", "ws"), - arguments("identity", "ws"), - arguments("deflate-frame", "ws"), - arguments("permessage-deflate", "ws"), - arguments("x-webkit-deflate-frame", "ws"), - arguments("identity,permessage-deflate", "ws"), - arguments("fragment,identity,permessage-deflate", "ws"), - - arguments("none", "wss"), - arguments("fragment", "wss"), - arguments("identity", "wss"), - arguments("deflate-frame", "wss"), - arguments("permessage-deflate", "wss"), - arguments("x-webkit-deflate-frame", "wss"), - arguments("identity,permessage-deflate", "wss"), - arguments("fragment,identity,permessage-deflate", "wss") - ) - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should receive websocket messages successfully.") - fun test(extensions: String, scheme: String) = runTest { - val host = "localhost" - val port = Random.nextInt(10000, 20000) - val count = 100 - - val serverChannel = Channel(Channel.UNLIMITED) - val server = HttpServerFactory.create() - if (scheme == "wss") { - server.enableSecureConnection() - } - server.websocket("/websocket/echo") - .onMessage { frame, _ -> - if (frame.type == Frame.Type.TEXT && frame is TextFrame) { - serverChannel.trySend(frame.payloadAsUTF8) - } - Result.DONE - } - .onAccept { connection -> - (1..count).forEach { i -> connection.sendText("Server $i") } - Result.DONE - } - .listen(host, port) - - val clientChannel = Channel(Channel.UNLIMITED) - val client = HttpClientFactory.create() - val webSocketConnection = client - .websocket("$scheme://$host:$port/websocket/echo") - .extensions(extensions.split(",")) - .onMessage { frame, _ -> - if (frame.type == Frame.Type.TEXT && frame is TextFrame) { - clientChannel.trySend(frame.payloadAsUTF8) - } - Result.DONE - } - .connect() - .await() - - (1..count).forEach { i -> webSocketConnection.sendText("Client $i") } - - (1..count).forEach { i -> - val serverReceivedMessage = serverChannel.receive() - assertEquals("Client $i", serverReceivedMessage) - } - - (1..count).forEach { i -> - val clientReceivedMessage = clientChannel.receive() - assertEquals("Server $i", clientReceivedMessage) - } - - webSocketConnection.closeAsync() - client.stop() - server.stop() - } -} \ No newline at end of file diff --git a/firefly-net/src/test/kotlin/com/fireflysource/net/websocket/common/impl/TestAsyncWebSocketConnection.kt b/firefly-net/src/test/kotlin/com/fireflysource/net/websocket/common/impl/TestAsyncWebSocketConnection.kt deleted file mode 100644 index d37c16f3f..000000000 --- a/firefly-net/src/test/kotlin/com/fireflysource/net/websocket/common/impl/TestAsyncWebSocketConnection.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.fireflysource.net.websocket.common.impl - -import com.fireflysource.common.sys.Result -import com.fireflysource.net.tcp.TcpClientFactory -import com.fireflysource.net.tcp.TcpConnection -import com.fireflysource.net.tcp.TcpServerFactory -import com.fireflysource.net.tcp.onAcceptAsync -import com.fireflysource.net.websocket.common.WebSocketConnection -import com.fireflysource.net.websocket.common.frame.TextFrame -import com.fireflysource.net.websocket.common.model.WebSocketBehavior -import com.fireflysource.net.websocket.common.model.WebSocketPolicy -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream -import kotlin.random.Random - -/** - * @author Pengtao Qiu - */ -class TestAsyncWebSocketConnection { - - companion object { - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - arguments("none", false), - arguments("fragment", false), - arguments("identity", false), - arguments("deflate-frame", false), - arguments("permessage-deflate", false), - arguments("x-webkit-deflate-frame", false), - arguments("identity,permessage-deflate", false), - arguments("fragment,identity,permessage-deflate", false), - - arguments("none", true), - arguments("fragment", true), - arguments("identity", true), - arguments("deflate-frame", true), - arguments("permessage-deflate", true), - arguments("x-webkit-deflate-frame", true), - arguments("identity,permessage-deflate", true), - arguments("fragment,identity,permessage-deflate", true) - ) - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should send and receive websocket messages successfully.") - fun test(extension: String, tls: Boolean) = runTest { - val host = "localhost" - val port = Random.nextInt(10000, 20000) - - val server = TcpServerFactory.create() - if (tls) { - server.enableSecureConnection() - } - server.onAcceptAsync { connection -> - connection.beginHandshake().await() - val webSocketConnection = createWebSocketConnection(WebSocketBehavior.SERVER, extension, connection) - webSocketConnection.setWebSocketMessageHandler { frame, conn -> - println("Server receive: ${frame.type}") - when (frame) { - is TextFrame -> { - val payload = frame.payloadAsUTF8 - println("server receive: $payload") - conn.sendText("response $payload") - } - else -> Result.DONE - } - } - webSocketConnection.begin() - } - server.listen(host, port) - - val channel = Channel(Channel.UNLIMITED) - val client = TcpClientFactory.create() - if (tls) { - client.enableSecureConnection() - } - val connection = client.connect(host, port).await() - connection.beginHandshake().await() - val webSocketConnection = createWebSocketConnection(WebSocketBehavior.CLIENT, extension, connection) - webSocketConnection.setWebSocketMessageHandler { frame, _ -> - when (frame) { - is TextFrame -> { - val payload = frame.payloadAsUTF8 - println("Client receive: $payload") - channel.trySend(payload) - Result.DONE - } - else -> Result.DONE - } - } - webSocketConnection.begin() - - (1..10).forEach { webSocketConnection.sendText("text: $it") } - - (1..10).forEach { - val text = channel.receive() - assertEquals("response text: $it", text) - } - - webSocketConnection.closeAsync() - client.stop() - server.stop() - } - - private fun createWebSocketConnection( - behavior: WebSocketBehavior, - extension: String, - connection: TcpConnection - ): WebSocketConnection { - val policy = WebSocketPolicy(behavior) - val extensions = if (extension != "none") listOf(extension) else listOf() - return AsyncWebSocketConnection(connection, policy, "localhost", extensions) - } -} \ No newline at end of file diff --git a/firefly-net/src/test/resources/files/favicon.ico b/firefly-net/src/test/resources/files/favicon.ico deleted file mode 100644 index d888a0c47..000000000 Binary files a/firefly-net/src/test/resources/files/favicon.ico and /dev/null differ diff --git a/firefly-net/src/test/resources/files/poem.html b/firefly-net/src/test/resources/files/poem.html deleted file mode 100644 index 415dcaccf..000000000 --- a/firefly-net/src/test/resources/files/poem.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Poem - - -

    家庭

    -

    - 我独自在横跨过田地的路上行走,夕阳像一个守财奴似的,正藏起它的最后的金子。 - 白昼更加深沉地陷入黑暗之中,那已经收割了的孤独的田地, - 默默地躺在那里。 - 天空里突然升起了一个男孩童的尖锐的歌声。他穿过看不见的黑暗,留下他的歌声的辙痕跨过黄昏的静谧。 - 他的乡村的家坐落在荒凉的边上,在甘蔗田的后面,躲藏在香蕉树,瘦长的槟榔树,椰子树和深绿色的贾克果树的阴影里。 - 我在星光下独自走着的路上停留了一会,我看见黑沉沉的大地展开在我的面前,用她的手臂拥抱着无量数的家庭, - 在那些家庭里有着摇篮和床铺,母亲们的心和夜晚的灯,还有年轻轻的生命, - 他们满心快乐,却浑然不知这样的快乐对于世界的价值。 -

    - - \ No newline at end of file diff --git a/firefly-net/src/test/resources/files/poem.txt b/firefly-net/src/test/resources/files/poem.txt deleted file mode 100644 index a9ccadd62..000000000 --- a/firefly-net/src/test/resources/files/poem.txt +++ /dev/null @@ -1,22 +0,0 @@ -偷睡眠的人 -谁从孩童的眼里把睡眠偷了去呢?我一定要知道。 -妈妈把她的水罐挟在腰间,走到近村汲水去了。 -这是正午的时候,孩童们游戏的时间已经过去了,池中的鸭子缄默无声。 -牧童躺在榕树的荫下睡着了。 -白鹤庄重而安静地立在檬果树边的泥泽里。 - -就在这个时候,偷睡眠的人跑来从孩童的两眼里捉住睡眠,便飞去了。 -当妈妈回来时,她看见孩童四肢着地地在屋里爬着。 -谁从孩童的眼里把睡眠偷了去呢?我一定要知道。我一定要找到她,把她锁起来。 -我一定要向那个黑洞里张望,在这个洞里,有一道小泉从圆的和有皱纹的石上滴下来。 -我一定要到醉花林中的沉寂的树影里搜寻,在这林中,鸽子在它们住的地方咕咕地叫着,仙女的脚环在繁星满天的静夜里叮当地响着。 -我要在黄昏时,向静静的萧萧的竹林里窥望,在这林中,萤火虫闪闪地耗费它们的光明,只要遇见一个人,我便要问他:“谁能告诉我偷睡眠者住在什么地方?” - -谁从孩童的眼里把睡眠偷了去呢?我一定要知道。 -只要我能捉住她,怕不会给她一顿好教训! -我要闯入她的巢穴,看她把所有偷来的睡眠藏在什么地方。 -我要把它都夺回来,带回家去。 -我要把她的双翼缚得紧紧的,把她放在河边,然后叫她拿一根芦苇在灯心草和睡莲间钓鱼为戏。 -黄昏,街上已经收了市,村里的孩童们都坐在妈妈的膝上时, -夜鸟便会讥讽地在她耳边说: -“你现在还想偷谁的睡眠呢?” \ No newline at end of file diff --git a/firefly-net/src/test/resources/firefly-log.xml b/firefly-net/src/test/resources/firefly-log.xml deleted file mode 100644 index d139af581..000000000 --- a/firefly-net/src/test/resources/firefly-log.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - firefly-system - INFO - ${log.path} - false - - - - firefly-monitor - INFO - ${log.path} - - - diff --git a/firefly-net/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/firefly-net/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker deleted file mode 100644 index ca6ee9cea..000000000 --- a/firefly-net/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ /dev/null @@ -1 +0,0 @@ -mock-maker-inline \ No newline at end of file diff --git a/firefly-serialization/pom.xml b/firefly-serialization/pom.xml deleted file mode 100644 index 61e1d8d6d..000000000 --- a/firefly-serialization/pom.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-serialization - jar - - firefly-serialization - http://www.fireflysource.com - - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - - com.fireflysource - firefly-slf4j - test - - - - - firefly-serialization - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.jks - - - - src/main/resources - false - - **/*.xml - **/*.properties - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.ico - **/*.html - **/*.txt - - - - src/test/resources - false - - **/*.xml - **/*.properties - - - - - diff --git a/firefly-serialization/src/main/java/com/fireflysource/doc/FeignedSerializationDoc.java b/firefly-serialization/src/main/java/com/fireflysource/doc/FeignedSerializationDoc.java deleted file mode 100644 index 44fa031ca..000000000 --- a/firefly-serialization/src/main/java/com/fireflysource/doc/FeignedSerializationDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedSerializationDoc { -} diff --git a/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/SerializationService.kt b/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/SerializationService.kt deleted file mode 100644 index c8237d5b0..000000000 --- a/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/SerializationService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.serialization - -import com.fasterxml.jackson.core.type.TypeReference - -interface SerializationService { - - fun read(content: String, clazz: Class): T - - fun read(content: String, ref: TypeReference): T - - fun write(obj: Any): String - -} \ No newline at end of file diff --git a/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/SerializationServiceFactory.kt b/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/SerializationServiceFactory.kt deleted file mode 100644 index b2dc7bb09..000000000 --- a/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/SerializationServiceFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.serialization - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fireflysource.serialization.impl.json.JsonSerializationService - -object SerializationServiceFactory { - - val json = json() - - @JvmOverloads - fun json(mapper: ObjectMapper = ObjectMapper()): SerializationService = JsonSerializationService(mapper) - -} \ No newline at end of file diff --git a/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/impl/json/JsonSerializationService.kt b/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/impl/json/JsonSerializationService.kt deleted file mode 100644 index ed5d6ce6e..000000000 --- a/firefly-serialization/src/main/kotlin/com/fireflysource/serialization/impl/json/JsonSerializationService.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.serialization.impl.json - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fireflysource.serialization.SerializationService - -class JsonSerializationService(val mapper: ObjectMapper = ObjectMapper()) : SerializationService { - - init { - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - } - - override fun read(content: String, clazz: Class): T = mapper.readValue(content, clazz) - - override fun read(content: String, ref: TypeReference): T = mapper.readValue(content, ref) - - override fun write(obj: Any): String = mapper.writeValueAsString(obj) - -} - -inline fun SerializationService.read(content: String): T { - return this.read(content, object : TypeReference() {}) -} - diff --git a/firefly-slf4j/pom.xml b/firefly-slf4j/pom.xml deleted file mode 100644 index e18f52fdd..000000000 --- a/firefly-slf4j/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-slf4j - jar - - firefly-slf4j - http://www.fireflysource.com - - - - org.slf4j - slf4j-api - - - - - firefly-slf4j - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.xml - **/*.properties - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/test/resources - false - - **/*.xml - **/*.properties - - - - - diff --git a/firefly-slf4j/src/main/java/com/fireflysource/doc/FeignedLogDoc.java b/firefly-slf4j/src/main/java/com/fireflysource/doc/FeignedLogDoc.java deleted file mode 100644 index 7d494ef41..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/doc/FeignedLogDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedLogDoc { -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/AbstractLogConfigParser.java b/firefly-slf4j/src/main/java/com/fireflysource/log/AbstractLogConfigParser.java deleted file mode 100644 index 90b0252a8..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/AbstractLogConfigParser.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.fireflysource.log; - -import com.fireflysource.log.internal.utils.StringUtils; - -import java.io.File; -import java.nio.charset.Charset; -import java.time.LocalDateTime; - -public abstract class AbstractLogConfigParser implements LogConfigParser { - - protected FileLog createLog(Configuration c) { - FileLog fileLog = new FileLog(); - fileLog.setLogName(c.getName()); - fileLog.setLevel(LogLevel.fromName(c.getLevel())); - fileLog.setMaxFileSize(c.getMaxFileSize()); - fileLog.setCharset(Charset.forName(c.getCharset())); - - boolean success; - if (StringUtils.hasText(c.getPath())) { - File file = new File(c.getPath()); - success = createLogDirectory(file); - if (success) { - fileLog.setPath(c.getPath()); - fileLog.setFileOutput(true); - } else { - success = createDefaultLogDir(fileLog); - } - } else { - success = createDefaultLogDir(fileLog); - } - - if (success) { - fileLog.setConsoleOutput(c.isConsole()); - } else { - fileLog.setConsoleOutput(true); - System.err.println("create log directory is failure"); - } - - if (StringUtils.hasText(c.getFormatter())) { - fileLog.setLogFormatter(new LogFormatter() { - - private LogFormatter formatter; - - @Override - public String format(LogItem logItem) { - init(); - return formatter.format(logItem); - } - - private void init() { - if (formatter == null) { - try { - Class clazz = AbstractLogConfigParser.class.getClassLoader().loadClass(c.getFormatter()); - formatter = (LogFormatter) clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - e.printStackTrace(); - formatter = DEFAULT_LOG_FORMATTER; - } - } - } - }); - } - - if (StringUtils.hasText(c.getLogNameFormatter())) { - fileLog.setLogNameFormatter(new LogNameFormatter() { - - private LogNameFormatter formatter; - - @Override - public String format(String name, LocalDateTime localDateTime) { - init(); - return formatter.format(name, localDateTime); - } - - @Override - public String formatBak(String name, LocalDateTime localDateTime, int index) { - init(); - return formatter.formatBak(name, localDateTime, index); - } - - private void init() { - if (formatter == null) { - try { - Class clazz = AbstractLogConfigParser.class.getClassLoader().loadClass(c.getLogNameFormatter()); - formatter = (LogNameFormatter) clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - e.printStackTrace(); - formatter = DEFAULT_LOG_NAME_FORMATTER; - } - } - } - }); - } - - if (StringUtils.hasText(c.getLogFilter())) { - fileLog.setLogFilter(new LogFilter() { - - private LogFilter filter; - - @Override - public void filter(LogItem logItem) { - init(); - filter.filter(logItem); - } - - private void init() { - if (filter == null) { - try { - Class clazz = AbstractLogConfigParser.class.getClassLoader().loadClass(c.getLogFilter()); - filter = (LogFilter) clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - e.printStackTrace(); - filter = DEFAULT_LOG_FILTER; - } - } - } - }); - } - - if (StringUtils.hasText(c.getMaxSplitTime())) { - fileLog.setMaxSplitTime(MaxSplitTimeEnum.from(c.getMaxSplitTime()).orElse(DEFAULT_MAX_SPLIT_TIME)); - } else { - fileLog.setMaxSplitTime(DEFAULT_MAX_SPLIT_TIME); - } - - System.out.println("initialize log " + fileLog); - return fileLog; - } - - private boolean createLogDirectory(File file) { - return file.exists() && file.isDirectory() || file.mkdirs(); - } - - private boolean createDefaultLogDir(FileLog fileLog) { - boolean success = createLogDirectory(DEFAULT_LOG_DIRECTORY); - if (success) { - fileLog.setPath(DEFAULT_LOG_DIRECTORY.getAbsolutePath()); - fileLog.setFileOutput(true); - } else { - fileLog.setFileOutput(false); - } - return success; - } - - @Override - public FileLog createDefaultLog() { - Configuration c = new Configuration(); - c.setName(DEFAULT_LOG_NAME); - c.setLevel(DEFAULT_LOG_LEVEL); - c.setPath(DEFAULT_LOG_DIRECTORY.getAbsolutePath()); - c.setConsole(false); - c.setMaxFileSize(DEFAULT_MAX_FILE_SIZE); - c.setCharset(DEFAULT_CHARSET.name()); - c.setMaxSplitTime(DEFAULT_MAX_SPLIT_TIME.getValue()); - c.setFormatter(DEFAULT_LOG_FORMATTER.getClass().getName()); - c.setLogNameFormatter(DEFAULT_LOG_NAME_FORMATTER.getClass().getName()); - c.setLogFilter(DEFAULT_LOG_FILTER.getClass().getName()); - return createLog(c); - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/ClassNameLogWrap.java b/firefly-slf4j/src/main/java/com/fireflysource/log/ClassNameLogWrap.java deleted file mode 100644 index ead19987e..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/ClassNameLogWrap.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.fireflysource.log; - -import java.io.IOException; - -/** - * @author Pengtao Qiu - */ -public class ClassNameLogWrap implements Log { - - public static final ThreadLocal name = new ThreadLocal<>(); - - private final Log log; - private final String className; - - public ClassNameLogWrap(Log log, String className) { - this.log = log; - this.className = className; - } - - public String getName() { - return log.getName(); - } - - public boolean isTraceEnabled() { - return log.isTraceEnabled(); - } - - public void trace(String str) { - name.set(className); - log.trace(str); - } - - public void trace(String str, Object... objs) { - name.set(className); - log.trace(str, objs); - } - - public void trace(String str, Throwable throwable, Object... objs) { - name.set(className); - log.trace(str, throwable, objs); - } - - public boolean isDebugEnabled() { - return log.isDebugEnabled(); - } - - public void debug(String str) { - name.set(className); - log.debug(str); - } - - public void debug(String str, Object... objs) { - name.set(className); - log.debug(str, objs); - } - - public void debug(String str, Throwable throwable, Object... objs) { - name.set(className); - log.debug(str, throwable, objs); - } - - public boolean isInfoEnabled() { - return log.isInfoEnabled(); - } - - public void info(String str) { - name.set(className); - log.info(str); - } - - public void info(String str, Object... objs) { - name.set(className); - log.info(str, objs); - } - - public void info(String str, Throwable throwable, Object... objs) { - name.set(className); - log.info(str, throwable, objs); - } - - public boolean isWarnEnabled() { - return log.isWarnEnabled(); - } - - public void warn(String str) { - name.set(className); - log.warn(str); - } - - public void warn(String str, Object... objs) { - name.set(className); - log.warn(str, objs); - } - - public void warn(String str, Throwable throwable, Object... objs) { - name.set(className); - log.warn(str, throwable, objs); - } - - public boolean isErrorEnabled() { - return log.isErrorEnabled(); - } - - public void error(String str) { - name.set(className); - log.error(str); - } - - public void error(String str, Object... objs) { - name.set(className); - log.error(str, objs); - } - - public void error(String str, Throwable throwable, Object... objs) { - name.set(className); - log.error(str, throwable, objs); - } - - public Log getLog() { - return log; - } - - public String getClassName() { - return className; - } - - @Override - public void close() throws IOException { - log.close(); - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/Configuration.java b/firefly-slf4j/src/main/java/com/fireflysource/log/Configuration.java deleted file mode 100644 index 06f35b542..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/Configuration.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.fireflysource.log; - -/** - * @author Pengtao Qiu - */ -public class Configuration { - - private String name; - private String level; - private String path; - private boolean console; - private long maxFileSize; - private String charset; - private String formatter; - private String logNameFormatter; - private String logFilter; - private String maxSplitTime; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getLevel() { - return level; - } - - public void setLevel(String level) { - this.level = level; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public boolean isConsole() { - return console; - } - - public void setConsole(boolean console) { - this.console = console; - } - - public long getMaxFileSize() { - return maxFileSize; - } - - public void setMaxFileSize(long maxFileSize) { - this.maxFileSize = maxFileSize; - } - - public String getCharset() { - return charset; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public String getFormatter() { - return formatter; - } - - public void setFormatter(String formatter) { - this.formatter = formatter; - } - - public String getLogNameFormatter() { - return logNameFormatter; - } - - public void setLogNameFormatter(String logNameFormatter) { - this.logNameFormatter = logNameFormatter; - } - - public String getLogFilter() { - return logFilter; - } - - public void setLogFilter(String logFilter) { - this.logFilter = logFilter; - } - - public String getMaxSplitTime() { - return maxSplitTime; - } - - public void setMaxSplitTime(String maxSplitTime) { - this.maxSplitTime = maxSplitTime; - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogFilter.java b/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogFilter.java deleted file mode 100644 index b7c144765..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogFilter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fireflysource.log; - -/** - * @author Pengtao Qiu - */ -public class DefaultLogFilter implements LogFilter { - - @Override - public void filter(LogItem logItem) { - - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogFormatter.java b/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogFormatter.java deleted file mode 100644 index fa3ed244e..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogFormatter.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fireflysource.log; - -/** - * @author Pengtao Qiu - */ -public class DefaultLogFormatter implements LogFormatter { - - @Override - public String format(LogItem logItem) { - return logItem.toString(); - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogNameFormatter.java b/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogNameFormatter.java deleted file mode 100644 index 95413db04..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/DefaultLogNameFormatter.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.fireflysource.log; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; - -import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; -import static java.time.temporal.ChronoField.*; - -/** - * @author Pengtao Qiu - */ -public class DefaultLogNameFormatter implements LogNameFormatter { - - public static final DateTimeFormatter DEFAULT_LOG_NAME_FORMATTER = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE) - .appendLiteral('_') - .append(new DateTimeFormatterBuilder() - .appendValue(HOUR_OF_DAY, 2) - .appendLiteral('-') - .appendValue(MINUTE_OF_HOUR, 2) - .optionalStart() - .appendLiteral('-') - .appendValue(SECOND_OF_MINUTE, 2) - .toFormatter()) - .toFormatter(); - - @Override - public String format(String name, LocalDateTime localDateTime) { - return name + ".txt"; - } - - @Override - public String formatBak(String name, LocalDateTime localDateTime, int index) { - return name + "." + localDateTime.format(DEFAULT_LOG_NAME_FORMATTER) + "." + index + ".bak.txt"; - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/Log.java b/firefly-slf4j/src/main/java/com/fireflysource/log/Log.java deleted file mode 100644 index 28674577d..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/Log.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.log; - -import java.io.Closeable; - -public interface Log extends Closeable { - - String CL = "\r\n"; - - String getName(); - - boolean isTraceEnabled(); - - void trace(String str); - - void trace(String str, Object... objs); - - void trace(String str, Throwable throwable, Object... objs); - - boolean isDebugEnabled(); - - void debug(String str); - - void debug(String str, Object... objs); - - void debug(String str, Throwable throwable, Object... objs); - - boolean isInfoEnabled(); - - void info(String str); - - void info(String str, Object... objs); - - void info(String str, Throwable throwable, Object... objs); - - boolean isWarnEnabled(); - - void warn(String str); - - void warn(String str, Object... objs); - - void warn(String str, Throwable throwable, Object... objs); - - boolean isErrorEnabled(); - - void error(String str); - - void error(String str, Object... objs); - - void error(String str, Throwable throwable, Object... objs); -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogConfigParser.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogConfigParser.java deleted file mode 100644 index 12b8d85f2..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogConfigParser.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.fireflysource.log; - -import java.io.File; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.function.Consumer; - -public interface LogConfigParser { - - String DEFAULT_XML_CONFIG_FILE_NAME = "firefly-log.xml"; - String DEFAULT_LOG_NAME = "firefly-system"; - String DEFAULT_LOG_LEVEL = "INFO"; - long DEFAULT_MAX_FILE_SIZE = 209715200; - File DEFAULT_LOG_DIRECTORY = new File(System.getProperty("user.dir"), "logs"); - boolean DEFAULT_CONSOLE_ENABLED = false; - Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - MaxSplitTimeEnum DEFAULT_MAX_SPLIT_TIME = MaxSplitTimeEnum.DAY; - LogFormatter DEFAULT_LOG_FORMATTER = new DefaultLogFormatter(); - LogNameFormatter DEFAULT_LOG_NAME_FORMATTER = new DefaultLogNameFormatter(); - LogFilter DEFAULT_LOG_FILTER = new DefaultLogFilter(); - - boolean parse(Consumer consumer); - - FileLog createDefaultLog(); - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogFactory.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogFactory.java deleted file mode 100644 index c231e3b79..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogFactory.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.fireflysource.log; - -import com.fireflysource.log.internal.utils.collection.TreeTrie; -import com.fireflysource.log.internal.utils.collection.Trie; - -import java.io.Closeable; -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class LogFactory implements Closeable { - - private final Trie logTree = new TreeTrie<>(); - private final AtomicBoolean closed = new AtomicBoolean(false); - - private LogFactory() { - LogConfigParser parser = new XmlLogConfigParser(); - boolean success = parser.parse((fileLog) -> logTree.put(fileLog.getName(), fileLog)); - - if (!success) { - System.out.println("log configuration parsing failure!"); - } - - if (logTree.get(LogConfigParser.DEFAULT_LOG_NAME) == null) { - FileLog fileLog = parser.createDefaultLog(); - logTree.put(fileLog.getName(), fileLog); - } - } - - public static LogFactory getInstance() { - return Holder.INSTANCE; - } - - public Log getLog(Class clazz) { - return getLog(clazz.getName()); - } - - public Log getLog(String name) { - Log log = logTree.getBest(name); - if (log == null) { - log = logTree.get(LogConfigParser.DEFAULT_LOG_NAME); - } - return new ClassNameLogWrap(log, name); - } - - @Override - public void close() { - if (closed.compareAndSet(false, true)) { - logTree.keySet().forEach(k -> { - Log log = logTree.get(k); - try { - log.close(); - } catch (IOException e) { - e.printStackTrace(); - } - }); - - - ExecutorService pool = FileLog.Companion.getExecutor(); - long timeout = 15; - TimeUnit unit = TimeUnit.SECONDS; - pool.shutdown(); // Disable new tasks from being submitted - try { - // Wait a while for existing tasks to terminate - if (!pool.awaitTermination(timeout, unit)) { - pool.shutdownNow(); // Cancel currently executing tasks - // Wait a while for tasks to respond to being cancelled - if (!pool.awaitTermination(timeout, unit)) - System.err.println("Pool did not terminate"); - } - } catch (InterruptedException ie) { - // (Re-)Cancel if current thread also interrupted - pool.shutdownNow(); - // Preserve interrupt status - Thread.currentThread().interrupt(); - } - System.out.println("The file log thread is shutdown"); - } - } - - private static class Holder { - private static final LogFactory INSTANCE = new LogFactory(); - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogFilter.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogFilter.java deleted file mode 100644 index 7663074da..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogFilter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fireflysource.log; - -/** - * @author Pengtao Qiu - */ -@FunctionalInterface -public interface LogFilter { - - void filter(LogItem logItem); - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogFormatter.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogFormatter.java deleted file mode 100644 index a7b2377d4..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogFormatter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fireflysource.log; - -/** - * @author Pengtao Qiu - */ -@FunctionalInterface -public interface LogFormatter { - - String format(LogItem logItem); - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogItem.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogItem.java deleted file mode 100644 index 0b3bedd3c..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogItem.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.fireflysource.log; - -import com.fireflysource.log.internal.utils.StringUtils; -import com.fireflysource.log.internal.utils.TimeUtils; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Date; -import java.util.Map; - -import static com.fireflysource.log.internal.utils.TimeUtils.DEFAULT_LOCAL_DATE_TIME; - -public class LogItem { - - private String name; - private String className; - private String content; - private String level; - private Object[] objs; - private Throwable throwable; - private StackTraceElement stackTraceElement; - private String logStr; - private Map mdcData; - private Date date; - private String threadName; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getClassName() { - return className; - } - - public void setClassName(String className) { - this.className = className; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getLevel() { - return level; - } - - public void setLevel(String level) { - this.level = level; - } - - public Object[] getObjs() { - return objs; - } - - public void setObjs(Object[] objs) { - this.objs = objs; - } - - public Throwable getThrowable() { - return throwable; - } - - public void setThrowable(Throwable throwable) { - this.throwable = throwable; - } - - public StackTraceElement getStackTraceElement() { - return stackTraceElement; - } - - public void setStackTraceElement(StackTraceElement stackTraceElement) { - this.stackTraceElement = stackTraceElement; - } - - public Map getMdcData() { - return mdcData; - } - - public void setMdcData(Map mdcData) { - this.mdcData = mdcData; - } - - public Date getDate() { - return date; - } - - public void setDate(Date date) { - this.date = date; - } - - public String getThreadName() { - return threadName; - } - - public void setThreadName(String threadName) { - this.threadName = threadName; - } - - public String renderContentTemplate() { - String ret = StringUtils.replace(content, objs); - if (throwable != null) { - StringWriter str = new StringWriter(); - try (PrintWriter out = new PrintWriter(str)) { - out.println(); - out.println("$err_start"); - throwable.printStackTrace(out); - out.println("$err_end"); - } - ret += str.toString(); - } - return ret; - } - - @Override - public String toString() { - if (logStr == null) { - logStr = level + " " + TimeUtils.format(date, DEFAULT_LOCAL_DATE_TIME); - - if (mdcData != null && !mdcData.isEmpty()) { - logStr += " " + mdcData; - } - - if (StringUtils.hasText(className)) { - logStr += " " + className; - } - - if (StringUtils.hasText(threadName)) { - logStr += " " + threadName; - } - - if (stackTraceElement != null) { - logStr += " " + stackTraceElement; - } - - logStr += "\t" + renderContentTemplate(); - } - return logStr; - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogLevel.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogLevel.java deleted file mode 100644 index 43b71fe74..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogLevel.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.fireflysource.log; - -import java.util.HashMap; -import java.util.Map; - -public enum LogLevel { - - TRACE(0, "TRACE"), - DEBUG(1, "DEBUG"), - INFO(2, "INFO"), - WARN(3, "WARN"), - ERROR(4, "ERROR"); - - private static final LogLevel[] levels = new LogLevel[5]; - private static final Map levelNameMap = new HashMap<>(); - - static { - for (LogLevel logLevel : LogLevel.values()) { - levels[logLevel.level] = logLevel; - levelNameMap.put(logLevel.name, logLevel); - } - } - - private final int level; - private final String name; - - LogLevel(int level, String name) { - this.level = level; - this.name = name; - } - - public static LogLevel fromLevel(int level) { - if (level >= 0 && level < levels.length) { - return levels[level]; - } else { - return INFO; - } - } - - public static LogLevel fromName(String name) { - if (name == null) - return INFO; - - LogLevel logLevel = levelNameMap.get(name); - if (logLevel == null) { - return INFO; - } else { - return levelNameMap.get(name); - } - } - - public int getLevel() { - return level; - } - - public String getName() { - return name; - } - - public boolean isEnabled(LogLevel logLevel) { - return this.level <= logLevel.level; - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/LogNameFormatter.java b/firefly-slf4j/src/main/java/com/fireflysource/log/LogNameFormatter.java deleted file mode 100644 index 7045fb82a..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/LogNameFormatter.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.fireflysource.log; - -import java.time.LocalDateTime; - -/** - * @author Pengtao Qiu - */ -public interface LogNameFormatter { - - String format(String name, LocalDateTime localDateTime); - - String formatBak(String name, LocalDateTime localDateTime, int index); - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/MappedDiagnosticContext.java b/firefly-slf4j/src/main/java/com/fireflysource/log/MappedDiagnosticContext.java deleted file mode 100644 index 24e0ebcbd..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/MappedDiagnosticContext.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.log; - -import java.util.Map; -import java.util.Set; - -/** - * @author Pengtao Qiu - */ -public interface MappedDiagnosticContext { - - void put(String key, String val); - - String get(String key); - - void remove(String key); - - void clear(); - - Set getKeys(); - - Map getCopyOfContextMap(); - - void setContextMap(Map contextMap); -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/MappedDiagnosticContextFactory.java b/firefly-slf4j/src/main/java/com/fireflysource/log/MappedDiagnosticContextFactory.java deleted file mode 100644 index 9cd44dfcb..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/MappedDiagnosticContextFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.fireflysource.log; - -import com.fireflysource.log.internal.utils.ServiceUtils; - -/** - * @author Pengtao Qiu - */ -public class MappedDiagnosticContextFactory { - - private static MappedDiagnosticContextFactory ourInstance = new MappedDiagnosticContextFactory(); - private MappedDiagnosticContext mappedDiagnosticContext; - - private MappedDiagnosticContextFactory() { - mappedDiagnosticContext = ServiceUtils.loadService(MappedDiagnosticContext.class, new ThreadLocalMappedDiagnosticContext()); - } - - public static MappedDiagnosticContextFactory getInstance() { - return ourInstance; - } - - public MappedDiagnosticContext getMappedDiagnosticContext() { - return mappedDiagnosticContext; - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/MaxSplitTimeEnum.java b/firefly-slf4j/src/main/java/com/fireflysource/log/MaxSplitTimeEnum.java deleted file mode 100644 index 707509636..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/MaxSplitTimeEnum.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fireflysource.log; - -import java.util.Arrays; -import java.util.Optional; - -/** - * @author Pengtao Qiu - */ -public enum MaxSplitTimeEnum { - MINUTE("minute"), HOUR("hour"), DAY("day"); - - private final String value; - - MaxSplitTimeEnum(String value) { - this.value = value; - } - - public static Optional from(String value) { - return Arrays.stream(MaxSplitTimeEnum.values()).filter(e -> e.value.equals(value)).findFirst(); - } - - public String getValue() { - return value; - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/ThreadLocalMappedDiagnosticContext.java b/firefly-slf4j/src/main/java/com/fireflysource/log/ThreadLocalMappedDiagnosticContext.java deleted file mode 100644 index a9078f9db..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/ThreadLocalMappedDiagnosticContext.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.fireflysource.log; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * @author Pengtao Qiu - */ -public class ThreadLocalMappedDiagnosticContext implements MappedDiagnosticContext { - - private InheritableThreadLocal> inheritableThreadLocal = new InheritableThreadLocal>() { - @Override - protected Map childValue(Map parentValue) { - if (parentValue == null) { - return null; - } - return new HashMap<>(parentValue); - } - }; - - /** - * Put a context value (the val parameter) as identified with - * the key parameter into the current thread's context map. - * Note that contrary to log4j, the val parameter can be null. - *

    - *

    - * If the current thread does not have a context map it is created as a side - * effect of this call. - * - * @throws IllegalArgumentException in case the "key" parameter is null - */ - @Override - public void put(String key, String val) { - if (key == null) { - throw new IllegalArgumentException("key cannot be null"); - } - Map map = inheritableThreadLocal.get(); - if (map == null) { - map = new HashMap<>(); - inheritableThreadLocal.set(map); - } - map.put(key, val); - } - - /** - * Get the context identified by the key parameter. - */ - @Override - public String get(String key) { - Map map = inheritableThreadLocal.get(); - if ((map != null) && (key != null)) { - return map.get(key); - } else { - return null; - } - } - - /** - * Remove the the context identified by the key parameter. - */ - @Override - public void remove(String key) { - Map map = inheritableThreadLocal.get(); - if (map != null) { - map.remove(key); - } - } - - /** - * Clear all entries in the MDC. - */ - @Override - public void clear() { - Map map = inheritableThreadLocal.get(); - if (map != null) { - map.clear(); - inheritableThreadLocal.remove(); - } - } - - /** - * Returns the keys in the MDC as a {@link Set} of {@link String}s The - * returned value can be null. - * - * @return the keys in the MDC - */ - @Override - public Set getKeys() { - Map map = inheritableThreadLocal.get(); - if (map != null) { - return map.keySet(); - } else { - return null; - } - } - - /** - * Return a copy of the current thread's context map. - * Returned value may be null. - */ - @Override - public Map getCopyOfContextMap() { - Map oldMap = inheritableThreadLocal.get(); - if (oldMap != null) { - return new HashMap<>(oldMap); - } else { - return null; - } - } - - @Override - public void setContextMap(Map contextMap) { - inheritableThreadLocal.set(new HashMap<>(contextMap)); - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/XmlLogConfigParser.java b/firefly-slf4j/src/main/java/com/fireflysource/log/XmlLogConfigParser.java deleted file mode 100644 index e619699ba..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/XmlLogConfigParser.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.fireflysource.log; - -import com.fireflysource.log.internal.utils.xml.DefaultDom; -import com.fireflysource.log.internal.utils.xml.Dom; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.util.List; -import java.util.function.Consumer; - -public class XmlLogConfigParser extends AbstractLogConfigParser { - - @Override - public boolean parse(Consumer consumer) { - Dom dom = new DefaultDom(); - Document doc = dom.getDocument(DEFAULT_XML_CONFIG_FILE_NAME); - if (doc == null) { - return false; - } - Element root = dom.getRoot(doc); - List loggerList = dom.elements(root, "logger"); - if (loggerList == null || loggerList.isEmpty()) { - return false; - } else { - for (Element e : loggerList) { - Configuration c = new Configuration(); - c.setName(dom.getTextValueByTagName(e, "name", DEFAULT_LOG_NAME)); - c.setLevel(dom.getTextValueByTagName(e, "level", DEFAULT_LOG_LEVEL)); - c.setPath(dom.getTextValueByTagName(e, "path", DEFAULT_LOG_DIRECTORY.getAbsolutePath())); - try { - c.setConsole(Boolean.parseBoolean(dom.getTextValueByTagName(e, "enable-console"))); - } catch (Exception ex) { - c.setConsole(DEFAULT_CONSOLE_ENABLED); - } - try { - c.setMaxFileSize(Long.parseLong(dom.getTextValueByTagName(e, "max-file-size"))); - } catch (Exception ex) { - c.setMaxFileSize(DEFAULT_MAX_FILE_SIZE); - } - if (c.getMaxFileSize() < 1024 * 1024 * 10) { - System.err.println("the max log file less than 10MB, please set a larger file size"); - } - c.setCharset(dom.getTextValueByTagName(e, "charset", DEFAULT_CHARSET.name())); - c.setFormatter(dom.getTextValueByTagName(e, "formatter", DEFAULT_LOG_FORMATTER.getClass().getName())); - c.setLogNameFormatter(dom.getTextValueByTagName(e, "log-name-formatter", DEFAULT_LOG_NAME_FORMATTER.getClass().getName())); - c.setLogFilter(dom.getTextValueByTagName(e, "log-filter", DEFAULT_LOG_FILTER.getClass().getName())); - c.setMaxSplitTime(dom.getTextValueByTagName(e, "max-split-time", DEFAULT_MAX_SPLIT_TIME.getValue())); - consumer.accept(createLog(c)); - } - } - return true; - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/ServiceUtils.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/ServiceUtils.java deleted file mode 100644 index b93109572..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/ServiceUtils.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fireflysource.log.internal.utils; - -import java.util.ServiceLoader; - -/** - * @author Pengtao Qiu - */ -abstract public class ServiceUtils { - - public static T loadService(Class clazz, T defaultService) { - T service = null; - ServiceLoader serviceLoader = ServiceLoader.load(clazz); - for (T t : serviceLoader) { - service = t; - } - if (service == null) { - service = defaultService; - } - return service; - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/StringUtils.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/StringUtils.java deleted file mode 100644 index f76dbb6d0..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/StringUtils.java +++ /dev/null @@ -1,371 +0,0 @@ -package com.fireflysource.log.internal.utils; - -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * @author Pengtao Qiu - */ -abstract public class StringUtils { - - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - public static final String EMPTY = ""; - - public static boolean hasText(String str) { - return hasText((CharSequence) str); - } - - public static boolean hasText(CharSequence str) { - if (!hasLength(str)) { - return false; - } - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; - } - - public static boolean hasLength(CharSequence str) { - return (str != null && str.length() > 0); - } - - public static boolean hasLength(String str) { - return hasLength((CharSequence) str); - } - - public static String replace(String s, Object... objs) { - if (objs == null || objs.length == 0) - return s; - if (!s.contains("{}")) - return s; - - StringBuilder ret = new StringBuilder((int) (s.length() * 1.5)); - int cursor = 0; - int index = 0; - for (int start; (start = s.indexOf("{}", cursor)) != -1; ) { - ret.append(s, cursor, start); - if (index < objs.length) { - Object obj = objs[index]; - try { - if (obj != null) { - if (obj instanceof AbstractCollection) { - ret.append(Arrays.toString(((AbstractCollection) obj).toArray())); - } else { - ret.append(obj); - } - } else { - ret.append("null"); - } - } catch (Throwable ignored) { - } - } else { - ret.append("{}"); - } - cursor = start + 2; - index++; - } - ret.append(s, cursor, s.length()); - return ret.toString(); - } - - // Splitting - // ----------------------------------------------------------------------- - - /** - *

    - * Splits the provided text into an array, using whitespace as the - * separator. Whitespace is defined by {@link Character#isWhitespace(char)}. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. For more control over the split - * use the StrTokenizer class. - *

    - *

    - *

    - * A null input String returns null. - *

    - *

    - *

    -     * StringUtils.split(null)       = null
    -     * StringUtils.split("")         = []
    -     * StringUtils.split("abc def")  = ["abc", "def"]
    -     * StringUtils.split("abc  def") = ["abc", "def"]
    -     * StringUtils.split(" abc ")    = ["abc"]
    -     * 
    - * - * @param str the String to parse, may be null - * @return an array of parsed Strings, null if null String - * input - */ - public static String[] split(String str) { - return split(str, null, -1); - } - - /** - *

    - * Splits the provided text into an array, separators specified. This is an - * alternative to using StringTokenizer. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. For more control over the split - * use the StrTokenizer class. - *

    - *

    - *

    - * A null input String returns null. A - * null separatorChars splits on whitespace. - *

    - *

    - *

    -     * StringUtils.split(null, *)         = null
    -     * StringUtils.split("", *)           = []
    -     * StringUtils.split("abc def", null) = ["abc", "def"]
    -     * StringUtils.split("abc def", " ")  = ["abc", "def"]
    -     * StringUtils.split("abc  def", " ") = ["abc", "def"]
    -     * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separatorChars the characters used as the delimiters, null - * splits on whitespace - * @return an array of parsed Strings, null if null String - * input - */ - public static String[] split(String str, String separatorChars) { - return splitWorker(str, separatorChars, -1, false); - } - - /** - *

    - * Splits the provided text into an array, separator specified. This is an - * alternative to using StringTokenizer. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. For more control over the split - * use the StrTokenizer class. - *

    - *

    - *

    - * A null input String returns null. - *

    - *

    - *

    -     * StringUtils.split(null, *)         = null
    -     * StringUtils.split("", *)           = []
    -     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
    -     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
    -     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
    -     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separatorChar the character used as the delimiter - * @return an array of parsed Strings, null if null String - * input - * @since 2.0 - */ - public static String[] split(String str, char separatorChar) { - return splitWorker(str, separatorChar, false); - } - - /** - *

    - * Splits the provided text into an array with a maximum length, separators - * specified. - *

    - *

    - *

    - * The separator is not included in the returned String array. Adjacent - * separators are treated as one separator. - *

    - *

    - *

    - * A null input String returns null. A - * null separatorChars splits on whitespace. - *

    - *

    - *

    - * If more than max delimited substrings are found, the last - * returned string includes all characters after the first - * max - 1 returned strings (including separator characters). - *

    - *

    - *

    -     * StringUtils.split(null, *, *)            = null
    -     * StringUtils.split("", *, *)              = []
    -     * StringUtils.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
    -     * StringUtils.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
    -     * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
    -     * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
    -     * 
    - * - * @param str the String to parse, may be null - * @param separatorChars the characters used as the delimiters, null - * splits on whitespace - * @param max the maximum number of elements to include in the array. A zero - * or negative value implies no limit - * @return an array of parsed Strings, null if null String - * input - */ - public static String[] split(String str, String separatorChars, int max) { - return splitWorker(str, separatorChars, max, false); - } - - /** - * Performs the logic for the split and - * splitPreserveAllTokens methods that return a maximum array - * length. - * - * @param str the String to parse, may be null - * @param separatorChars the separate character - * @param max the maximum number of elements to include in the array. A zero - * or negative value implies no limit. - * @param preserveAllTokens if true, adjacent separators are treated as empty - * token separators; if false, adjacent separators - * are treated as one separator. - * @return an array of parsed Strings, null if null String - * input - */ - private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) { - // Performance tuned for 2.0 (JDK1.4) - // Direct code is quicker than StringTokenizer. - // Also, StringTokenizer uses isSpace() not isWhitespace() - - if (str == null) { - return null; - } - int len = str.length(); - if (len == 0) { - return EMPTY_STRING_ARRAY; - } - List list = new ArrayList<>(); - int sizePlus1 = 1; - int i = 0, start = 0; - boolean match = false; - boolean lastMatch = false; - if (separatorChars == null) { - // Null separator means use whitespace - while (i < len) { - if (Character.isWhitespace(str.charAt(i))) { - if (match || preserveAllTokens) { - lastMatch = true; - if (sizePlus1++ == max) { - i = len; - lastMatch = false; - } - list.add(str.substring(start, i)); - match = false; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - } else if (separatorChars.length() == 1) { - // Optimise 1 character case - char sep = separatorChars.charAt(0); - while (i < len) { - if (str.charAt(i) == sep) { - if (match || preserveAllTokens) { - lastMatch = true; - if (sizePlus1++ == max) { - i = len; - lastMatch = false; - } - list.add(str.substring(start, i)); - match = false; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - } else { - // standard case - while (i < len) { - if (separatorChars.indexOf(str.charAt(i)) >= 0) { - if (match || preserveAllTokens) { - lastMatch = true; - if (sizePlus1++ == max) { - i = len; - lastMatch = false; - } - list.add(str.substring(start, i)); - match = false; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - } - if (match || (preserveAllTokens && lastMatch)) { - list.add(str.substring(start, i)); - } - return list.toArray(EMPTY_STRING_ARRAY); - } - - /** - * Performs the logic for the split and - * splitPreserveAllTokens methods that do not return a maximum - * array length. - * - * @param str the String to parse, may be null - * @param separatorChar the separate character - * @param preserveAllTokens if true, adjacent separators are treated as empty - * token separators; if false, adjacent separators - * are treated as one separator. - * @return an array of parsed Strings, null if null String - * input - */ - private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) { - // Performance tuned for 2.0 (JDK1.4) - - if (str == null) { - return null; - } - int len = str.length(); - if (len == 0) { - return EMPTY_STRING_ARRAY; - } - List list = new ArrayList<>(); - int i = 0, start = 0; - boolean match = false; - boolean lastMatch = false; - while (i < len) { - if (str.charAt(i) == separatorChar) { - if (match || preserveAllTokens) { - list.add(str.substring(start, i)); - match = false; - lastMatch = true; - } - start = ++i; - continue; - } - lastMatch = false; - match = true; - i++; - } - if (match || (preserveAllTokens && lastMatch)) { - list.add(str.substring(start, i)); - } - return list.toArray(EMPTY_STRING_ARRAY); - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/TimeUtils.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/TimeUtils.java deleted file mode 100644 index 1509b8d00..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/TimeUtils.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.fireflysource.log.internal.utils; - -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.SignStyle; -import java.time.temporal.ChronoUnit; -import java.time.temporal.Temporal; -import java.util.Date; - -import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; -import static java.time.temporal.ChronoField.*; - -/** - * @author Pengtao Qiu - */ -abstract public class TimeUtils { - - public static final DateTimeFormatter LOCAL_DATE_SLASH_SEPARATOR = new DateTimeFormatterBuilder() - .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - .appendLiteral('/') - .appendValue(MONTH_OF_YEAR, 2) - .appendLiteral('/') - .appendValue(DAY_OF_MONTH, 2) - .toFormatter(); - - public static final DateTimeFormatter DEFAULT_LOCAL_MONTH = new DateTimeFormatterBuilder() - .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - .appendLiteral('-') - .appendValue(MONTH_OF_YEAR, 2) - .toFormatter(); - - public static final DateTimeFormatter DEFAULT_LOCAL_DATE = new DateTimeFormatterBuilder() - .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) - .appendLiteral('-') - .appendValue(MONTH_OF_YEAR, 2) - .appendLiteral('-') - .appendValue(DAY_OF_MONTH, 2) - .toFormatter(); - - public static final DateTimeFormatter DEFAULT_LOCAL_DATE_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE) - .appendLiteral(' ') - .append(new DateTimeFormatterBuilder() - .appendValue(HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(MINUTE_OF_HOUR, 2) - .optionalStart() - .appendLiteral(':') - .appendValue(SECOND_OF_MINUTE, 2) - .toFormatter()) - .toFormatter(); - - public static final DateTimeFormatter ISO_LOCAL_DATE_TIME = new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE) - .appendLiteral('T') - .append(new DateTimeFormatterBuilder() - .appendValue(HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(MINUTE_OF_HOUR, 2) - .optionalStart() - .appendLiteral(':') - .appendValue(SECOND_OF_MINUTE, 2) - .toFormatter()) - .appendLiteral('Z') - .toFormatter(); - - public static Date toDate(LocalDate localDate) { - return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); - } - - public static Date toDate(LocalDateTime localDateTime) { - return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); - } - - public static LocalDate toLocalDate(Date date) { - return LocalDate.from(date.toInstant().atZone(ZoneId.systemDefault())); - } - - public static LocalDateTime toLocalDateTime(Date date) { - return LocalDateTime.from(date.toInstant().atZone(ZoneId.systemDefault())); - } - - public static String format(Date date, DateTimeFormatter formatter) { - return date.toInstant().atZone(ZoneId.systemDefault()).format(formatter); - } - - public static String format(Date date, ZoneOffset offset, DateTimeFormatter formatter) { - return date.toInstant().atOffset(offset).format(formatter); - } - - public static Date parseLocalDate(String text, DateTimeFormatter formatter) { - return toDate(LocalDate.parse(text, formatter)); - } - - public static Date parseLocalDateTime(String text, DateTimeFormatter formatter) { - return toDate(LocalDateTime.parse(text, formatter)); - } - - public static LocalDate parseYearMonth(String text, DateTimeFormatter formatter) { - return YearMonth.parse(text, formatter).atDay(1); - } - - public static long between(ChronoUnit chronoUnit, Temporal temporal1Inclusive, Temporal temporal2Exclusive) { - return chronoUnit.between(temporal1Inclusive, temporal2Exclusive); - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/AbstractTrie.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/AbstractTrie.java deleted file mode 100644 index e03807dae..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/AbstractTrie.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.fireflysource.log.internal.utils.collection; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -public abstract class AbstractTrie implements Trie { - final boolean _caseInsensitive; - - protected AbstractTrie(boolean insensitive) { - _caseInsensitive = insensitive; - } - - @Override - public boolean put(V v) { - return put(v.toString(), v); - } - - @Override - public V remove(String s) { - V o = get(s); - put(s, null); - return o; - } - - @Override - public V get(String s) { - return get(s, 0, s.length()); - } - - @Override - public V get(ByteBuffer b) { - return get(b, 0, b.remaining()); - } - - @Override - public V getBest(String s) { - return getBest(s, 0, s.length()); - } - - @Override - public V getBest(byte[] b, int offset, int len) { - return getBest(new String(b, offset, len, StandardCharsets.UTF_8)); - } - - @Override - public boolean isCaseInsensitive() { - return _caseInsensitive; - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/TreeTrie.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/TreeTrie.java deleted file mode 100644 index a775fc049..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/TreeTrie.java +++ /dev/null @@ -1,325 +0,0 @@ -package com.fireflysource.log.internal.utils.collection; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.*; - -/** - * A Trie String lookup data structure using a tree - *

    - * This implementation is always case insensitive and is optimal for a variable - * number of fixed strings with few special characters. - *

    - *

    - * This Trie is stored in a Tree and is unlimited in capacity - *

    - *

    - *

    - * This Trie is not Threadsafe and contains no mutual exclusion or deliberate - * memory barriers. It is intended for an ArrayTrie to be built by a single - * thread and then used concurrently by multiple threads and not mutated during - * that access. If concurrent mutations of the Trie is required external locks - * need to be applied. - *

    - * - * @param the entry type - */ -public class TreeTrie extends AbstractTrie { - private static final int[] __lookup = - { // 0 1 2 3 4 5 6 7 8 9 A B C D E F - /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, 30, -1, - /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1, - /*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - }; - private static final int INDEX = 32; - private final TreeTrie[] _nextIndex; - private final List> _nextOther = new ArrayList<>(); - private final char _c; - private String _key; - private V _value; - - @SuppressWarnings("unchecked") - public TreeTrie() { - super(true); - _nextIndex = new TreeTrie[INDEX]; - _c = 0; - } - - @SuppressWarnings("unchecked") - private TreeTrie(char c) { - super(true); - _nextIndex = new TreeTrie[INDEX]; - this._c = c; - } - - private static void toString(Appendable out, TreeTrie t) { - if (t != null) { - if (t._value != null) { - try { - out.append(','); - out.append(t._key); - out.append('='); - out.append(t._value.toString()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (int i = 0; i < INDEX; i++) { - if (t._nextIndex[i] != null) - toString(out, t._nextIndex[i]); - } - for (int i = t._nextOther.size(); i-- > 0; ) - toString(out, t._nextOther.get(i)); - } - } - - private static void keySet(Set set, TreeTrie t) { - if (t != null) { - if (t._key != null) - set.add(t._key); - - for (int i = 0; i < INDEX; i++) { - if (t._nextIndex[i] != null) - keySet(set, t._nextIndex[i]); - } - for (int i = t._nextOther.size(); i-- > 0; ) - keySet(set, t._nextOther.get(i)); - } - } - - @Override - public void clear() { - Arrays.fill(_nextIndex, null); - _nextOther.clear(); - _key = null; - _value = null; - } - - @Override - public boolean put(String s, V v) { - TreeTrie t = this; - int limit = s.length(); - for (int k = 0; k < limit; k++) { - char c = s.charAt(k); - - int index = c >= 0 && c < 0x7f ? __lookup[c] : -1; - if (index >= 0) { - if (t._nextIndex[index] == null) - t._nextIndex[index] = new TreeTrie(c); - t = t._nextIndex[index]; - } else { - TreeTrie n = null; - for (int i = t._nextOther.size(); i-- > 0; ) { - n = t._nextOther.get(i); - if (n._c == c) - break; - n = null; - } - if (n == null) { - n = new TreeTrie<>(c); - t._nextOther.add(n); - } - t = n; - } - } - t._key = v == null ? null : s; - t._value = v; - return true; - } - - @Override - public V get(String s, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - char c = s.charAt(offset + i); - int index = c >= 0 && c < 0x7f ? __lookup[c] : -1; - if (index >= 0) { - if (t._nextIndex[index] == null) - return null; - t = t._nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t._nextOther.size(); j-- > 0; ) { - n = t._nextOther.get(j); - if (n._c == c) - break; - n = null; - } - if (n == null) - return null; - t = n; - } - } - return t._value; - } - - @Override - public V get(ByteBuffer b, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - byte c = b.get(offset + i); - int index = c >= 0 && c < 0x7f ? __lookup[c] : -1; - if (index >= 0) { - if (t._nextIndex[index] == null) - return null; - t = t._nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t._nextOther.size(); j-- > 0; ) { - n = t._nextOther.get(j); - if (n._c == c) - break; - n = null; - } - if (n == null) - return null; - t = n; - } - } - return t._value; - } - - @Override - public V getBest(byte[] b, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - byte c = b[offset + i]; - int index = c >= 0 && c < 0x7f ? __lookup[c] : -1; - if (index >= 0) { - if (t._nextIndex[index] == null) - break; - t = t._nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t._nextOther.size(); j-- > 0; ) { - n = t._nextOther.get(j); - if (n._c == c) - break; - n = null; - } - if (n == null) - break; - t = n; - } - - // Is the next Trie is a match - if (t._key != null) { - // Recurse so we can remember this possibility - V best = t.getBest(b, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return t._value; - } - - @Override - public V getBest(String s, int offset, int len) { - TreeTrie t = this; - for (int i = 0; i < len; i++) { - byte c = (byte) (0xff & s.charAt(offset + i)); - int index = c >= 0 && c < 0x7f ? __lookup[c] : -1; - if (index >= 0) { - if (t._nextIndex[index] == null) - break; - t = t._nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t._nextOther.size(); j-- > 0; ) { - n = t._nextOther.get(j); - if (n._c == c) - break; - n = null; - } - if (n == null) - break; - t = n; - } - - // Is the next Trie is a match - if (t._key != null) { - // Recurse so we can remember this possibility - V best = t.getBest(s, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return t._value; - } - - @Override - public V getBest(ByteBuffer b, int offset, int len) { - if (b.hasArray()) - return getBest(b.array(), b.arrayOffset() + b.position() + offset, len); - return getBestByteBuffer(b, offset, len); - } - - private V getBestByteBuffer(ByteBuffer b, int offset, int len) { - TreeTrie t = this; - int pos = b.position() + offset; - for (int i = 0; i < len; i++) { - byte c = b.get(pos++); - int index = c >= 0 && c < 0x7f ? __lookup[c] : -1; - if (index >= 0) { - if (t._nextIndex[index] == null) - break; - t = t._nextIndex[index]; - } else { - TreeTrie n = null; - for (int j = t._nextOther.size(); j-- > 0; ) { - n = t._nextOther.get(j); - if (n._c == c) - break; - n = null; - } - if (n == null) - break; - t = n; - } - - // Is the next Trie is a match - if (t._key != null) { - // Recurse so we can remember this possibility - V best = t.getBest(b, offset + i + 1, len - i - 1); - if (best != null) - return best; - break; - } - } - return t._value; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - toString(buf, this); - - if (buf.length() == 0) - return "{}"; - - buf.setCharAt(0, '{'); - buf.append('}'); - return buf.toString(); - } - - @Override - public Set keySet() { - Set keys = new HashSet<>(); - keySet(keys, this); - return keys; - } - - @Override - public boolean isFull() { - return false; - } - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/Trie.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/Trie.java deleted file mode 100644 index 6d4063072..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/collection/Trie.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.fireflysource.log.internal.utils.collection; - -import java.nio.ByteBuffer; -import java.util.Set; - -/** - * A Trie String lookup data structure. - * - * @param the Trie entry type - */ -public interface Trie { - - /** - * Put an entry into the Trie - * - * @param s The key for the entry - * @param v The value of the entry - * @return True if the Trie had capacity to add the field. - */ - boolean put(String s, V v); - - /** - * Put a value as both a key and a value. - * - * @param v The value and key - * @return True if the Trie had capacity to add the field. - */ - boolean put(V v); - - V remove(String s); - - /** - * Get an exact match from a String key - * - * @param s The key - * @return the value for the string key - */ - V get(String s); - - /** - * Get an exact match from a String key - * - * @param s The key - * @param offset The offset within the string of the key - * @param len the length of the key - * @return the value for the string / offset / length - */ - V get(String s, int offset, int len); - - /** - * Get an exact match from a segment of a ByteBuufer as key - * - * @param b The buffer - * @return The value or null if not found - */ - V get(ByteBuffer b); - - /** - * Get an exact match from a segment of a ByteBuufer as key - * - * @param b The buffer - * @param offset The offset within the buffer of the key - * @param len the length of the key - * @return The value or null if not found - */ - V get(ByteBuffer b, int offset, int len); - - /** - * Get the best match from key in a String. - * - * @param s The string - * @return The value or null if not found - */ - V getBest(String s); - - /** - * Get the best match from key in a String. - * - * @param s The string - * @param offset The offset within the string of the key - * @param len the length of the key - * @return The value or null if not found - */ - V getBest(String s, int offset, int len); - - /** - * Get the best match from key in a byte array. - * The key is assumed to by ISO_8859_1 characters. - * - * @param b The buffer - * @param offset The offset within the array of the key - * @param len the length of the key - * @return The value or null if not found - */ - V getBest(byte[] b, int offset, int len); - - /** - * Get the best match from key in a byte buffer. - * The key is assumed to by ISO_8859_1 characters. - * - * @param b The buffer - * @param offset The offset within the buffer of the key - * @param len the length of the key - * @return The value or null if not found - */ - V getBest(ByteBuffer b, int offset, int len); - - Set keySet(); - - boolean isFull(); - - boolean isCaseInsensitive(); - - void clear(); - -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/DefaultDom.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/DefaultDom.java deleted file mode 100644 index d2ff2f4a8..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/DefaultDom.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.fireflysource.log.internal.utils.xml; - -import com.fireflysource.log.internal.utils.StringUtils; -import org.w3c.dom.CharacterData; -import org.w3c.dom.*; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -public class DefaultDom implements Dom { - - private DocumentBuilder documentBuilder; - - public DefaultDom() { - try { - documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } catch (ParserConfigurationException e) { - throw new XmlParsingException(e); - } - } - - @Override - public Document getDocument(String file) { - try (InputStream is = DefaultDom.class.getResourceAsStream("/" + file)) { - if (is == null) { - throw new XmlParsingException("the configuration file: " + file + " is not found"); - } - Document doc = documentBuilder.parse(is); - return doc; - } catch (Exception e) { - throw new XmlParsingException(e); - } - } - - @Override - public Element getRoot(Document doc) { - return doc.getDocumentElement(); - } - - @Override - public List elements(Element e) { - return elements(e, null); - } - - @Override - public List elements(Element e, String name) { - List eList = new ArrayList(); - - NodeList nodeList = e.getChildNodes(); - for (int i = 0; i < nodeList.getLength(); ++i) { - Node node = nodeList.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - if (name != null) { - if (node.getNodeName().equals(name)) - eList.add((Element) node); - } else { - eList.add((Element) node); - } - } - } - return eList; - } - - @Override - public Element element(Element e, String name) { - NodeList element = e.getElementsByTagName(name); - if (element != null && e.getNodeType() == Node.ELEMENT_NODE) { - return (Element) element.item(0); - } - return null; - } - - @Override - public String getTextValue(Element valueElement) { - if (valueElement != null) { - StringBuilder sb = new StringBuilder(); - NodeList nl = valueElement.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node item = nl.item(i); - if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) { - sb.append(item.getNodeValue()); - } - } - return sb.toString().trim(); - } - return null; - } - - @Override - public String getTextValueByTagName(Element e, String name) { - Element valueElement = element(e, name); - if (valueElement == null) { - return null; - } else { - String value = getTextValue(valueElement); - if (StringUtils.hasText(value)) { - return value; - } else { - return null; - } - } - } - - @Override - public String getTextValueByTagName(Element e, String name, String defaultValue) { - String value = getTextValueByTagName(e, name); - return StringUtils.hasText(value) ? value : defaultValue; - } -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/Dom.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/Dom.java deleted file mode 100644 index 78d55a65e..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/Dom.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.fireflysource.log.internal.utils.xml; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import java.util.List; - -public interface Dom { - - /** - * Get the XML document - * - * @param file The file relative path - * @return XML document - */ - Document getDocument(String file); - - /** - * Get the root node - * - * @param doc XML document object; - * @return The root element. - */ - Element getRoot(Document doc); - - /** - * Get the children elements - * - * @param e A current XML element - * @return All children elements - */ - List elements(Element e); - - /** - * Get the children elements by element name - * - * @param e A current XML element - * @param name Element name - * @return Children elements - */ - List elements(Element e, String name); - - /** - * Get a element by name - * - * @param e A current XML element - * @param name The child node name - * @return A XML element - */ - Element element(Element e, String name); - - /** - * Get the value of a XML element - * - * @param valueElement The value node - * @return The text value - */ - String getTextValue(Element valueElement); - - /** - * Get the value of a XML node - * - * @param e Current XML element - * @param name The child node name - * @return The text value - */ - String getTextValueByTagName(Element e, String name); - - /** - * Get the value of a XML node - * - * @param e Current XML element - * @param name The child node name - * @param defaultValue Default text value, when the child node is not found or the - * child node has not text value, the method return it - * @return The text value - */ - String getTextValueByTagName(Element e, String name, String defaultValue); -} diff --git a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/XmlParsingException.java b/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/XmlParsingException.java deleted file mode 100644 index e6ae67f81..000000000 --- a/firefly-slf4j/src/main/java/com/fireflysource/log/internal/utils/xml/XmlParsingException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.log.internal.utils.xml; - -/** - * @author Pengtao Qiu - */ -public class XmlParsingException extends RuntimeException { - - public XmlParsingException(String message) { - super(message); - } - - public XmlParsingException(Throwable cause) { - super(cause); - } -} diff --git a/firefly-slf4j/src/main/java/org/slf4j/impl/LogFactoryImpl.java b/firefly-slf4j/src/main/java/org/slf4j/impl/LogFactoryImpl.java deleted file mode 100644 index e93fad2c3..000000000 --- a/firefly-slf4j/src/main/java/org/slf4j/impl/LogFactoryImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.slf4j.impl; - -import com.fireflysource.log.Log; -import com.fireflysource.log.LogFactory; -import org.slf4j.ILoggerFactory; -import org.slf4j.Logger; - -import java.io.Closeable; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class LogFactoryImpl implements ILoggerFactory, Closeable { - - private final Map map = new ConcurrentHashMap<>(); - - @Override - public Logger getLogger(String name) { - Logger logger = map.get(name); - if (logger != null) { - return logger; - } else { - Log log = LogFactory.getInstance().getLog(name); - if (log != null) { - Logger newInstance = new LoggerImpl(log); - Logger oldInstance = map.putIfAbsent(name, newInstance); - return oldInstance == null ? newInstance : oldInstance; - } else { - return null; - } - } - } - - @Override - public void close() { - LogFactory.getInstance().close(); - } -} diff --git a/firefly-slf4j/src/main/java/org/slf4j/impl/LoggerImpl.java b/firefly-slf4j/src/main/java/org/slf4j/impl/LoggerImpl.java deleted file mode 100644 index b2ef6fe43..000000000 --- a/firefly-slf4j/src/main/java/org/slf4j/impl/LoggerImpl.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.slf4j.impl; - -import com.fireflysource.log.Log; -import org.slf4j.helpers.MarkerIgnoringBase; - -public class LoggerImpl extends MarkerIgnoringBase { - - private static final long serialVersionUID = 689005039688030280L; - - private Log log; - - public LoggerImpl(Log log) { - this.log = log; - } - - @Override - public String getName() { - return log.getName(); - } - - @Override - public boolean isTraceEnabled() { - return log.isTraceEnabled(); - } - - @Override - public void trace(String msg) { - log.trace(msg); - } - - @Override - public void trace(String format, Object arg) { - log.trace(format, arg); - } - - @Override - public void trace(String format, Object arg1, Object arg2) { - log.trace(format, arg1, arg2); - } - - @Override - public void trace(String format, Object... arguments) { - log.trace(format, arguments); - } - - @Override - public void trace(String msg, Throwable t) { - log.trace(msg, t); - } - - @Override - public boolean isDebugEnabled() { - return log.isDebugEnabled(); - } - - @Override - public void debug(String msg) { - log.debug(msg); - } - - @Override - public void debug(String format, Object arg) { - log.debug(format, arg); - } - - @Override - public void debug(String format, Object arg1, Object arg2) { - log.debug(format, arg1, arg2); - } - - @Override - public void debug(String format, Object... arguments) { - log.debug(format, arguments); - } - - @Override - public void debug(String msg, Throwable t) { - log.debug(msg, t); - } - - @Override - public boolean isInfoEnabled() { - return log.isInfoEnabled(); - } - - @Override - public void info(String msg) { - log.info(msg); - } - - @Override - public void info(String format, Object arg) { - log.info(format, arg); - } - - @Override - public void info(String format, Object arg1, Object arg2) { - log.info(format, arg1, arg2); - } - - @Override - public void info(String format, Object... arguments) { - log.info(format, arguments); - } - - @Override - public void info(String msg, Throwable t) { - log.info(msg, t); - } - - @Override - public boolean isWarnEnabled() { - return log.isWarnEnabled(); - } - - @Override - public void warn(String msg) { - log.warn(msg); - } - - @Override - public void warn(String format, Object arg) { - log.warn(format, arg); - } - - @Override - public void warn(String format, Object... arguments) { - log.warn(format, arguments); - } - - @Override - public void warn(String format, Object arg1, Object arg2) { - if (arg1 instanceof Throwable) { - log.warn(format, (Throwable) arg1, arg2); - } else { - log.warn(format, arg1, arg2); - } - } - - @Override - public void warn(String msg, Throwable t) { - log.warn(msg, t); - } - - @Override - public boolean isErrorEnabled() { - return log.isErrorEnabled(); - } - - @Override - public void error(String msg) { - log.error(msg); - } - - @Override - public void error(String format, Object arg) { - log.error(format, arg); - } - - @Override - public void error(String format, Object arg1, Object arg2) { - if (arg1 instanceof Throwable) { - log.error(format, (Throwable) arg1, arg2); - } else { - log.error(format, arg1, arg2); - } - } - - @Override - public void error(String format, Object... arguments) { - log.error(format, arguments); - } - - @Override - public void error(String msg, Throwable t) { - log.error(msg, t); - } - -} diff --git a/firefly-slf4j/src/main/java/org/slf4j/impl/MDCAdapterImpl.java b/firefly-slf4j/src/main/java/org/slf4j/impl/MDCAdapterImpl.java deleted file mode 100644 index 19d03f83a..000000000 --- a/firefly-slf4j/src/main/java/org/slf4j/impl/MDCAdapterImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.slf4j.impl; - -import com.fireflysource.log.MappedDiagnosticContext; -import com.fireflysource.log.MappedDiagnosticContextFactory; -import org.slf4j.spi.MDCAdapter; - -import java.util.Map; - -/** - * @author Pengtao Qiu - */ -public class MDCAdapterImpl implements MDCAdapter { - - private MappedDiagnosticContext mdc; - - public MDCAdapterImpl() { - mdc = MappedDiagnosticContextFactory.getInstance().getMappedDiagnosticContext(); - } - - @Override - public void put(String key, String val) { - mdc.put(key, val); - } - - @Override - public String get(String key) { - return mdc.get(key); - } - - @Override - public void remove(String key) { - mdc.remove(key); - } - - @Override - public void clear() { - mdc.clear(); - } - - @Override - public Map getCopyOfContextMap() { - return mdc.getCopyOfContextMap(); - } - - @Override - public void setContextMap(Map contextMap) { - mdc.setContextMap(contextMap); - } -} diff --git a/firefly-slf4j/src/main/java/org/slf4j/impl/StaticLoggerBinder.java b/firefly-slf4j/src/main/java/org/slf4j/impl/StaticLoggerBinder.java deleted file mode 100644 index 294fc5bda..000000000 --- a/firefly-slf4j/src/main/java/org/slf4j/impl/StaticLoggerBinder.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.slf4j.impl; - -import org.slf4j.ILoggerFactory; -import org.slf4j.spi.LoggerFactoryBinder; - -public class StaticLoggerBinder implements LoggerFactoryBinder { - - private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); - - private static final ILoggerFactory loggerFactory = new LogFactoryImpl(); - private static final String loggerFactoryClassStr = LogFactoryImpl.class.getName(); - - public static StaticLoggerBinder getSingleton() { - return SINGLETON; - } - - @Override - public ILoggerFactory getLoggerFactory() { - return loggerFactory; - } - - @Override - public String getLoggerFactoryClassStr() { - return loggerFactoryClassStr; - } - -} diff --git a/firefly-slf4j/src/main/java/org/slf4j/impl/StaticMDCBinder.java b/firefly-slf4j/src/main/java/org/slf4j/impl/StaticMDCBinder.java deleted file mode 100644 index 64514ce6f..000000000 --- a/firefly-slf4j/src/main/java/org/slf4j/impl/StaticMDCBinder.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.slf4j.impl; - -import com.fireflysource.log.internal.utils.ServiceUtils; -import org.slf4j.spi.MDCAdapter; - -/** - * @author Pengtao Qiu - */ -public class StaticMDCBinder { - - /** - * The unique instance of this class. - */ - public static final StaticMDCBinder SINGLETON = new StaticMDCBinder(); - private MDCAdapter mdca; - - private StaticMDCBinder() { - mdca = ServiceUtils.loadService(MDCAdapter.class, new MDCAdapterImpl()); - } - - public MDCAdapter getMDCA() { - return mdca; - } - - public String getMDCAdapterClassStr() { - return mdca.getClass().getName(); - } -} diff --git a/firefly-slf4j/src/main/kotlin/com/fireflysource/log/FileLog.kt b/firefly-slf4j/src/main/kotlin/com/fireflysource/log/FileLog.kt deleted file mode 100644 index dbf0511d0..000000000 --- a/firefly-slf4j/src/main/kotlin/com/fireflysource/log/FileLog.kt +++ /dev/null @@ -1,404 +0,0 @@ -package com.fireflysource.log - -import com.fireflysource.log.LogConfigParser.* -import com.fireflysource.log.internal.utils.TimeUtils -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED -import java.io.* -import java.nio.charset.Charset -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.* -import java.util.concurrent.* -import java.util.concurrent.CancellationException - - -/** - * @author Pengtao Qiu - */ -class FileLog : Log { - - companion object { - private val stackTrace = java.lang.Boolean.getBoolean("com.fireflysource.log.FileLog.debugMode") - private val fileBufferSize = Integer.getInteger("com.fireflysource.log.FileLog.bufferSize", 8192) - private val fileFlushInterval = java.lang.Long.getLong("com.fireflysource.log.FileLog.flushInterval", 500) - val executor = newSingleThreadExecutor() - private val fileLogThreadScope: CoroutineScope = - CoroutineScope(executor.asCoroutineDispatcher() + CoroutineName("FireflyFileLogThread")) - - private fun newSingleThreadExecutor(): ExecutorService { - return ThreadPoolExecutor( - 1, 1, 0, TimeUnit.MILLISECONDS, LinkedTransferQueue() - ) { runnable -> Thread(runnable, "firefly-log-thread") } - } - } - - sealed interface LogMessage - - @JvmInline - value class WriteLogMessage(val logItem: LogItem) : LogMessage - object Flush : LogMessage - object Stop : LogMessage - - - var level: LogLevel = LogLevel.fromName(DEFAULT_LOG_LEVEL) - var path: String = DEFAULT_LOG_DIRECTORY.absolutePath - var logName: String = DEFAULT_LOG_NAME - var consoleOutput: Boolean = false - var fileOutput: Boolean = true - var maxFileSize: Long = DEFAULT_MAX_FILE_SIZE - var logFormatter: LogFormatter = DEFAULT_LOG_FORMATTER - var logNameFormatter: LogNameFormatter = DEFAULT_LOG_NAME_FORMATTER - var logFilter: LogFilter = DEFAULT_LOG_FILTER - var maxSplitTime: MaxSplitTimeEnum = MaxSplitTimeEnum.DAY - var charset: Charset = DEFAULT_CHARSET - - private val mdc: MappedDiagnosticContext = MappedDiagnosticContextFactory.getInstance().mappedDiagnosticContext - - private val output = LogOutputStream() - private val channel = Channel(UNLIMITED) - private val consumerJob = fileLogThreadScope.launch { - while (true) { - val message = channel.receive() - val exit = handleWriteLogMessage(message) - if (exit) break - } - println("File log $logName is closed.") - } - private val flushTickJob = fileLogThreadScope.launch { - while (true) { - delay(fileFlushInterval) - channel.trySend(Flush) - } - } - - private fun handleWriteLogMessage(message: LogMessage): Boolean { - return when (message) { - is WriteLogMessage -> { - val logItem = message.logItem - logFilter.filter(logItem) - if (consoleOutput) { - println(logFormatter.format(logItem)) - } - - if (fileOutput) { - output.write(logFormatter.format(logItem), logItem.date) - } - return false - } - is Flush -> { - output.flush() - return false - } - is Stop -> true - } - } - - - private inner class LogOutputStream { - private var fileOutputStream: BufferedOutputStream? = null - private var writtenSize: Long = 0 - private var lastWrittenTime: LocalDateTime? = null - - private fun getLogName(localDateTime: LocalDateTime): String { - return logNameFormatter.format(name, localDateTime) - } - - private fun getLogBakName(localDateTime: LocalDateTime, index: Int): String { - return logNameFormatter.formatBak(name, localDateTime, index) - } - - private fun getLogBakName(localDateTime: LocalDateTime): String { - var index = 0 - var bakName = getLogBakName(localDateTime, index) - while (Files.exists(Paths.get(path, bakName))) { - index++ - bakName = getLogBakName(localDateTime, index) - } - return bakName - } - - private fun isNotSplitByTime(newLocalDateTime: LocalDateTime): Boolean { - val lastTime = lastWrittenTime - requireNotNull(lastTime) - when (maxSplitTime) { - MaxSplitTimeEnum.DAY -> { - return (lastTime.year == newLocalDateTime.year - && lastTime.month == newLocalDateTime.month - && lastTime.dayOfMonth == newLocalDateTime.dayOfMonth) - } - MaxSplitTimeEnum.HOUR -> { - return (lastTime.year == newLocalDateTime.year - && lastTime.month == newLocalDateTime.month - && lastTime.dayOfMonth == newLocalDateTime.dayOfMonth - && lastTime.hour == newLocalDateTime.hour) - } - MaxSplitTimeEnum.MINUTE -> { - return (lastTime.year == newLocalDateTime.year - && lastTime.month == newLocalDateTime.month - && lastTime.dayOfMonth == newLocalDateTime.dayOfMonth - && lastTime.hour == newLocalDateTime.hour - && lastTime.minute == newLocalDateTime.minute) - } - } - } - - private fun getFileLastModifiedTime(logPath: Path): LocalDateTime { - val lastTime = lastWrittenTime - return if (lastTime == null) { - val fileTime = Files.getLastModifiedTime(logPath) - val time = LocalDateTime.from(fileTime.toInstant().atZone(ZoneId.systemDefault())) - lastWrittenTime = time - time - } else lastTime - } - - private fun getOutput(newDate: Date, currentLogSize: Long): OutputStream { - initializeOutputStream(newDate, currentLogSize) - val output = fileOutputStream - requireNotNull(output) - return output - } - - private fun initializeOutputStream(newDate: Date, currentLogSize: Long) { - val newLocalDateTime = TimeUtils.toLocalDateTime(newDate) - val logName = getLogName(newLocalDateTime) - val logPath = Paths.get(path, logName) - - if (Files.exists(logPath)) { - val lastModifiedTime = getFileLastModifiedTime(logPath) - - if (isNotSplitByTime(newLocalDateTime)) { - if (maxFileSize > 0) { - if (writtenSize == 0L) { - writtenSize = Files.size(logPath) - } - if (currentLogSize + writtenSize > maxFileSize) { - createLogOutputAndBackupOldLog(logName, logPath, lastModifiedTime) - } else createLogOutputIfNull(logName) - } else createLogOutputIfNull(logName) - } else createLogOutputAndBackupOldLog(logName, logPath, lastModifiedTime) - } else createLogOutputIfNull(logName) - } - - private fun createLogOutputAndBackupOldLog( - logName: String, - logPath: Path, - fileLastModifiedDateTime: LocalDateTime - ) { - close() - Files.move(logPath, Paths.get(path, getLogBakName(fileLastModifiedDateTime))) - fileOutputStream = BufferedOutputStream(FileOutputStream(File(path, logName), true), fileBufferSize) - } - - private fun createLogOutputIfNull(logName: String) { - if (fileOutputStream == null) { - fileOutputStream = BufferedOutputStream(FileOutputStream(File(path, logName), true), fileBufferSize) - } - } - - fun write(str: String, date: Date) { - val text = (str + Log.CL).toByteArray(charset) - try { - val output = getOutput(date, text.size.toLong()) - output.write(text) - writtenSize += text.size - lastWrittenTime = TimeUtils.toLocalDateTime(date) - } catch (e: IOException) { - System.err.println("write log exception. " + e.message) - } - } - - fun close() { - val output = fileOutputStream - if (output != null) { - try { - output.close() - writtenSize = 0 - } catch (e: IOException) { - System.err.println("close log writer exception. " + e.message) - } - } - } - - fun flush() { - try { - fileOutputStream?.flush() - } catch (e: IOException) { - System.err.println("flush log exception. " + e.message) - } - } - - } - - override fun getName(): String = logName - - override fun isTraceEnabled(): Boolean = level.isEnabled(LogLevel.TRACE) - - override fun trace(str: String?) { - if (isTraceEnabled) { - write(str, LogLevel.TRACE.getName(), null, null) - } - } - - override fun trace(str: String?, objs: Array?) { - if (isTraceEnabled) { - write(str, LogLevel.TRACE.getName(), null, objs) - } - } - - override fun trace(str: String?, throwable: Throwable?, objs: Array?) { - if (isTraceEnabled) { - write(str, LogLevel.TRACE.getName(), throwable, objs) - } - } - - override fun isDebugEnabled(): Boolean = level.isEnabled(LogLevel.DEBUG) - - override fun debug(str: String?) { - if (isDebugEnabled) { - write(str, LogLevel.DEBUG.getName(), null, null) - } - } - - override fun debug(str: String?, objs: Array?) { - if (isDebugEnabled) { - write(str, LogLevel.DEBUG.getName(), null, objs) - } - } - - override fun debug(str: String?, throwable: Throwable?, objs: Array?) { - if (isDebugEnabled) { - write(str, LogLevel.DEBUG.getName(), throwable, objs) - } - } - - override fun isInfoEnabled(): Boolean = level.isEnabled(LogLevel.INFO) - - override fun info(str: String?) { - if (isInfoEnabled) { - write(str, LogLevel.INFO.getName(), null, null) - } - } - - override fun info(str: String?, objs: Array?) { - if (isInfoEnabled) { - write(str, LogLevel.INFO.getName(), null, objs) - } - } - - override fun info(str: String?, throwable: Throwable?, objs: Array?) { - if (isInfoEnabled) { - write(str, LogLevel.INFO.getName(), throwable, objs) - } - } - - override fun isWarnEnabled(): Boolean = level.isEnabled(LogLevel.WARN) - - override fun warn(str: String?) { - if (isWarnEnabled) { - write(str, LogLevel.WARN.getName(), null, null) - } - } - - override fun warn(str: String?, objs: Array?) { - if (isWarnEnabled) { - write(str, LogLevel.WARN.getName(), null, objs) - } - } - - override fun warn(str: String?, throwable: Throwable?, objs: Array?) { - if (isWarnEnabled) { - write(str, LogLevel.WARN.getName(), throwable, objs) - } - } - - override fun isErrorEnabled(): Boolean = level.isEnabled(LogLevel.ERROR) - - override fun error(str: String?) { - if (isErrorEnabled) { - write(str, LogLevel.ERROR.getName(), null, null) - } - } - - override fun error(str: String?, objs: Array?) { - if (isErrorEnabled) { - write(str, LogLevel.ERROR.getName(), null, objs) - } - } - - override fun error(str: String?, throwable: Throwable?, objs: Array?) { - if (isErrorEnabled) { - write(str, LogLevel.ERROR.getName(), throwable, objs) - } - } - - override fun close() = runBlocking { - try { - channel.trySend(Stop) - consumerJob.cancel(CancellationException("Cancel file log exception.")) - consumerJob.join() - flushTickJob.cancel(CancellationException("Cancel flush file log exception.")) - } finally { - output.close() - } - } - - private fun write(content: String?, level: String, throwable: Throwable?, objs: Array?) { - val item = LogItem() - item.level = level - item.name = name - item.content = content - item.objs = objs - item.throwable = throwable - item.date = Date() - item.mdcData = mdc.copyOfContextMap - item.className = ClassNameLogWrap.name.get() - item.threadName = Thread.currentThread().name - if (stackTrace) { - item.stackTraceElement = getStackTraceElement() - } - write(item) - } - - private fun getStackTraceElement(): StackTraceElement? { - val arr = Thread.currentThread().stackTrace - val stackTraceElement = arr[4] - return if (stackTraceElement != null) { - if (stackTraceElement.className == "com.fireflysource.log.ClassNameLogWrap") - arr[6] - else stackTraceElement - } else null - } - - private fun write(logItem: LogItem) { - channel.trySend(WriteLogMessage(logItem)) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as FileLog - - if (logName != other.logName) return false - - return true - } - - override fun hashCode(): Int { - return logName.hashCode() - } - - override fun toString(): String { - return "FileLog{level=$level, path='$path', name='$name', consoleOutput=$consoleOutput, " + - "fileOutput=$fileOutput, maxFileSize=$maxFileSize, " + - "fileBufferSize=$fileBufferSize, fileFlushInterval=$fileFlushInterval, " + - "charset=$charset, maxSplitTime=${maxSplitTime.value}}" - } - -} \ No newline at end of file diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/TestLog.java b/firefly-slf4j/src/test/java/com/fireflysource/log/TestLog.java deleted file mode 100644 index c24880f2b..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/TestLog.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.fireflysource.log; - -import com.fireflysource.log.foo.Foo; -import com.fireflysource.log.foo.bar.Bar; -import com.fireflysource.log.internal.utils.StringUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - - -class TestLog { - - private static final Log logTrace = LogFactory.getInstance().getLog("test-TRACE"); - private static final Log logDebug = LogFactory.getInstance().getLog("test-DEBUG"); - private static final Log logInfo = LogFactory.getInstance().getLog("test-INFO"); - private static final Log logWarn = LogFactory.getInstance().getLog("test-WARN"); - private static final Log logError = LogFactory.getInstance().getLog("test-ERROR"); - private static final Log testMdc = LogFactory.getInstance().getLog("test-request-id"); - private static final Log logFoo = LogFactory.getInstance().getLog(Foo.class); - private static final Log logBar = LogFactory.getInstance().getLog(Bar.class); - private static final Log logDefaultName = LogFactory.getInstance().getLog("test-illegal"); - private static final Log logConsole = LogFactory.getInstance().getLog("test-console"); - private static final Log logCommon = LogFactory.getInstance().getLog("firefly-common"); - private static final Log logErrorStack = LogFactory.getInstance().getLog("error-stack"); - - private static MappedDiagnosticContext mdc; - - @BeforeAll - static void init() { - mdc = MappedDiagnosticContextFactory.getInstance().getMappedDiagnosticContext(); - deleteAll(); - } - - @AfterAll - static void destroy() { - deleteAll(); - } - - private static void deleteAll() { - deleteLog(logTrace); - deleteLog(logDebug); - deleteLog(logError); - deleteLog(logWarn); - deleteLog(logInfo); - deleteLog(logFoo); - deleteLog(logBar); - deleteLog(testMdc); - deleteLog(logDefaultName); - deleteLog(logConsole); - deleteLog(logCommon); - deleteLog(logErrorStack); - } - - private static void deleteLog(Log log) { - File file = getFile(log); - if (file != null && file.exists()) { - boolean success = file.delete(); - System.out.println("delete file " + file.getAbsolutePath() + " | " + success); - } - } - - private static File getFile(Log log) { - System.out.println("getFile: " + log.getClass().getName()); - if (log instanceof ClassNameLogWrap) { - ClassNameLogWrap classNameLogWrap = (ClassNameLogWrap) log; - if (classNameLogWrap.getLog() instanceof FileLog) { - FileLog fileLog = (FileLog) classNameLogWrap.getLog(); - File file = new File(fileLog.getPath(), fileLog.getName() + ".txt"); - System.out.println("getFile: " + fileLog.getPath() + ", " + fileLog.getName()); - System.out.println("getFile: " + file.exists()); - if (file.exists()) { - return file; - } - } - } - return null; - } - - private static List readAllLines(Log log) throws IOException { - final File file = getFile(log); - if (file == null) return Collections.emptyList(); - - return Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - } - - private static void sleep() { - try { - Thread.sleep(2000L); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - } - - - public static void test1() { - throw new RuntimeException(); - } - - public static void test2() { - test1(); - } - - public static void test3() { - test2(); - } - - @Test - @DisplayName("should create log by class name successfully") - void testLogFactory() { - Log log = LogFactory.getInstance().getLog(Bar.class); - assertTrue(log instanceof ClassNameLogWrap); - assertFalse(log.isDebugEnabled()); - assertTrue(log.isInfoEnabled()); - } - - @Test - @DisplayName("should get log by class name successfully") - void testClassNameLog() throws IOException { - logFoo.info("testFoo"); - logBar.info("testBar"); - sleep(); - - readAllLines(logFoo).forEach(text -> { - String[] data = StringUtils.split(text, '\t'); - assertEquals("testFoo", data[1]); - }); - - readAllLines(logBar).forEach(text -> { - String[] data = StringUtils.split(text, '\t'); - assertEquals("testBar", data[1]); - }); - } - - @Test - @DisplayName("should print log the level is greater than and equals trace successfully") - void testTrace() throws IOException { - logTrace.trace("trace log"); - logTrace.debug("debug log"); - logTrace.info("info log"); - logTrace.warn("warn log"); - logTrace.error("error log"); - sleep(); - - List lines = readAllLines(logTrace); - assertTrue(lines.get(0).contains("trace log")); - assertTrue(lines.get(1).contains("debug log")); - assertTrue(lines.get(2).contains("info log")); - assertTrue(lines.get(3).contains("warn log")); - assertTrue(lines.get(4).contains("error log")); - } - - @Test - @DisplayName("should print log the level is greater than and equals debug successfully") - void testDebug() throws IOException { - logDebug.trace("trace log"); - logDebug.debug("debug log"); - logDebug.info("info log"); - logDebug.warn("warn log"); - logDebug.error("error log"); - sleep(); - - List lines = readAllLines(logDebug); - assertTrue(lines.get(0).contains("debug log")); - assertTrue(lines.get(1).contains("info log")); - assertTrue(lines.get(2).contains("warn log")); - assertTrue(lines.get(3).contains("error log")); - } - - @Test - @DisplayName("should print log the level is greater than and equals info successfully") - void testInfo() throws IOException { - logInfo.trace("trace log"); - logInfo.debug("debug log"); - logInfo.info("info log"); - logInfo.warn("warn log"); - logInfo.error("error log"); - sleep(); - - List lines = readAllLines(logInfo); - assertTrue(lines.get(0).contains("info log")); - assertTrue(lines.get(1).contains("warn log")); - assertTrue(lines.get(2).contains("error log")); - } - - @Test - @DisplayName("should print log the level is greater than and equals warn successfully") - void testWarn() throws IOException { - logWarn.trace("trace log"); - logWarn.debug("debug log"); - logWarn.info("info log"); - logWarn.warn("warn log"); - logWarn.error("error log"); - sleep(); - - List lines = readAllLines(logWarn); - assertTrue(lines.get(0).contains("warn log")); - assertTrue(lines.get(1).contains("error log")); - } - - @Test - @DisplayName("should print error log successfully") - void testError() throws IOException { - logError.trace("trace log"); - logError.debug("debug log"); - logError.info("info log"); - logError.warn("warn log"); - logError.error("error log"); - sleep(); - - List lines = readAllLines(logError); - assertTrue(lines.get(0).contains("error log")); - } - - @Test - @DisplayName("should put MDC successfully") - void testMdc() throws IOException { - mdc.put("reqId", "hello_req_id"); - testMdc.info("oooooooooo"); - testMdc.info("bbbbbbbbbb"); - sleep(); - - List lines = readAllLines(testMdc); - assertTrue(lines.get(0).contains("hello_req_id")); - assertTrue(lines.get(1).contains("hello_req_id")); - } - - @Test - @DisplayName("should create default log successfully when the log name not exists") - void testDefaultLogName() throws IOException { - logDefaultName.info("default name 0"); - sleep(); - - List lines = readAllLines(logDefaultName); - assertTrue(lines.get(0).contains("default name 0")); - } - - @Test - @DisplayName("should print log to console and file successfully") - void testConsoleAndFile() throws IOException { - logConsole.info("test console."); - logCommon.info("test common."); - sleep(); - - List lines = readAllLines(logConsole); - assertTrue(lines.get(0).contains("test console.")); - - lines = readAllLines(logCommon); - assertTrue(lines.get(0).contains("test common.")); - } - - @Test - void testErrorStack() throws IOException { - try { - test3(); - } catch (Exception e) { - logErrorStack.error("exception", e); - } - sleep(); - - List lines = readAllLines(logErrorStack); - assertTrue(lines.get(0).contains("exception")); - assertTrue(lines.size() > 1); - assertTrue(lines.get(1).contains("$err_start")); - assertTrue(lines.get(2).contains("java.lang.RuntimeException")); - } - -} diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/TestLogParser.java b/firefly-slf4j/src/test/java/com/fireflysource/log/TestLogParser.java deleted file mode 100644 index 4997af736..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/TestLogParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.fireflysource.log; - - -import com.fireflysource.log.internal.utils.collection.TreeTrie; -import com.fireflysource.log.internal.utils.collection.Trie; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class TestLogParser { - - @Test - void test() { - Trie xmlLogTree = new TreeTrie<>(); - LogConfigParser parser = new XmlLogConfigParser(); - boolean success = parser.parse((fileLog) -> xmlLogTree.put(fileLog.getName(), fileLog)); - assertTrue(success); - - Log bar = xmlLogTree.getBest("com.fireflysource.log.foo.bar.Bar"); - assertEquals("com.fireflysource.log.foo.bar", bar.getName()); - - Log debug = xmlLogTree.getBest("test-DEBUG"); - assertFalse(debug.isTraceEnabled()); - assertTrue(debug.isDebugEnabled()); - assertTrue(debug.isInfoEnabled()); - - System.out.println(debug.getClass().getName()); - } -} diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/demo/CustomLogFormatter.java b/firefly-slf4j/src/test/java/com/fireflysource/log/demo/CustomLogFormatter.java deleted file mode 100644 index 3a0af51ad..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/demo/CustomLogFormatter.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fireflysource.log.demo; - - -import com.fireflysource.log.LogFormatter; -import com.fireflysource.log.LogItem; -import com.fireflysource.log.internal.utils.StringUtils; -import com.fireflysource.log.internal.utils.TimeUtils; - -import static com.fireflysource.log.internal.utils.TimeUtils.DEFAULT_LOCAL_DATE_TIME; - -/** - * @author Pengtao Qiu - */ -public class CustomLogFormatter implements LogFormatter { - - @Override - public String format(LogItem logItem) { - String logStr = logItem.getLevel() + " " + TimeUtils.format(logItem.getDate(), DEFAULT_LOCAL_DATE_TIME); - - if (logItem.getMdcData() != null && !logItem.getMdcData().isEmpty()) { - logStr += " " + logItem.getMdcData(); - } - - if (StringUtils.hasText(logItem.getClassName())) { - logStr += " " + logItem.getClassName(); - } - - if (StringUtils.hasText(logItem.getThreadName())) { - logStr += " " + logItem.getThreadName(); - } - - logStr += " --> " + logItem.renderContentTemplate(); - return logStr; - } - -} diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/demo/LogBenchmark.java b/firefly-slf4j/src/test/java/com/fireflysource/log/demo/LogBenchmark.java deleted file mode 100644 index 4f1ef9b68..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/demo/LogBenchmark.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fireflysource.log.demo; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CountDownLatch; - -public class LogBenchmark { - - private static final Logger log = LoggerFactory.getLogger("test-INFO"); - - public static void main(String[] args) throws InterruptedException { - test(4, 10_000_000, 20); - } - - public static void test(int threadNum, int messageNum, int messageSize) throws InterruptedException { - StringBuilder data = new StringBuilder(messageSize); - for (int i = 0; i < messageSize; i++) { - data.append("a"); - } - String str = data.toString(); - - CountDownLatch latch = new CountDownLatch(threadNum); - - Thread[] threads = new Thread[threadNum]; - int size = messageNum / threadNum; - System.out.println("size per thread: " + size); - for (int i = 0; i < threads.length; i++) { - threads[i] = new Thread(() -> { - for (int j = 0; j < size; j++) { - log.info(str); - } -// System.out.println(Thread.currentThread().getName() + " arrived"); - latch.countDown(); - }, "test-thread-" + i); - } - - long start = System.currentTimeMillis(); - for (Thread thread : threads) { - thread.start(); - } - latch.await(); - long end = System.currentTimeMillis(); - long time = (end - start) / 1000; - System.out.println("time: " + time); - System.out.println("msg/sec: " + (messageNum / time)); - } - -} diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/demo/TimeSplitDemo.java b/firefly-slf4j/src/test/java/com/fireflysource/log/demo/TimeSplitDemo.java deleted file mode 100644 index 6933a7e2c..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/demo/TimeSplitDemo.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.log.demo; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.TimeUnit; - -/** - * @author Pengtao Qiu - */ -public class TimeSplitDemo { - - public static final Logger log = LoggerFactory.getLogger("time-split-minute"); - - public static void main(String[] args) throws Exception { - int i = 0; - while (true) { - log.info("test1 {}, {}", i++, System.currentTimeMillis()); - Thread.sleep(TimeUnit.SECONDS.toMillis(5)); - } - } -} diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/foo/Foo.java b/firefly-slf4j/src/test/java/com/fireflysource/log/foo/Foo.java deleted file mode 100644 index 36047ded0..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/foo/Foo.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fireflysource.log.foo; - -/** - * @author Pengtao Qiu - */ -public class Foo { -} diff --git a/firefly-slf4j/src/test/java/com/fireflysource/log/foo/bar/Bar.java b/firefly-slf4j/src/test/java/com/fireflysource/log/foo/bar/Bar.java deleted file mode 100644 index 6a75dc48b..000000000 --- a/firefly-slf4j/src/test/java/com/fireflysource/log/foo/bar/Bar.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fireflysource.log.foo.bar; - -/** - * @author Pengtao Qiu - */ -public class Bar { -} diff --git a/firefly-slf4j/src/test/resources/firefly-log.xml b/firefly-slf4j/src/test/resources/firefly-log.xml deleted file mode 100644 index bc5139f60..000000000 --- a/firefly-slf4j/src/test/resources/firefly-log.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - firefly-common - ${log.level} - ${log.path} - true - - - - test-TRACE - TRACE - ${log.path} - true - - - - test-DEBUG - DEBUG - ${log.path} - true - - - - test-ERROR - ERROR - ${log.path} - true - - - - test-WARN - WARN - ${log.path} - false - - - - test-INFO - INFO - ${log.path} - false - - - - test-console - INFO - true - - - - com.fireflysource.log.foo - INFO - ${log.path} - true - - - - com.fireflysource.log.foo.bar - INFO - ${log.path} - true - - - - test.max.size - INFO - ${log.path} - 300 - UTF-8 - - - - test.gbk - INFO - ${log.path} - GBK - - - - test-request-id - INFO - ${log.path} - - - - error-stack - INFO - ${log.path} - - - - com.fireflysource.log.demo - ${log.level} - ${log.path} - true - com.fireflysource.log.demo.CustomLogFormatter - - - - time-split-minute - INFO - ${log.path} - minute - - diff --git a/firefly-wechat/pom.xml b/firefly-wechat/pom.xml deleted file mode 100644 index bc55ae269..000000000 --- a/firefly-wechat/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - com.fireflysource - firefly-framework - 5.0.3-SNAPSHOT - - 4.0.0 - - firefly-wechat - jar - - firefly-wechat - http://www.fireflysource.com - - - - com.fireflysource - firefly-net - - - - com.fireflysource - firefly-serialization - - - - com.fireflysource - firefly-slf4j - test - - - - - firefly-wechat - install - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - src/main/resources - false - - **/*.xml - **/*.properties - - - - - - src/test/resources - true - - **/*.xml - **/*.properties - - - - src/test/resources - false - - **/*.xml - **/*.properties - - - - - diff --git a/firefly-wechat/src/main/java/com/fireflysource/doc/FeignedWechatDoc.java b/firefly-wechat/src/main/java/com/fireflysource/doc/FeignedWechatDoc.java deleted file mode 100644 index 3199d0f1d..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/doc/FeignedWechatDoc.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fireflysource.doc; - -/** - * Only used to generate javadoc. - * - * @author Pengtao Qiu - */ -public class FeignedWechatDoc { -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/Article.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/Article.java deleted file mode 100644 index d98e15ff9..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/Article.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class Article { - - private String title; - - private String description; - - private String url; - - @JsonProperty("picurl") - private String pictureUrl; - - public Article() { - } - - public Article(String title, String description, String url, String pictureUrl) { - this.title = title; - this.description = description; - this.url = url; - this.pictureUrl = pictureUrl; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getPictureUrl() { - return pictureUrl; - } - - public void setPictureUrl(String pictureUrl) { - this.pictureUrl = pictureUrl; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Article article = (Article) o; - return Objects.equals(title, article.title) && - Objects.equals(description, article.description) && - Objects.equals(url, article.url) && - Objects.equals(pictureUrl, article.pictureUrl); - } - - @Override - public int hashCode() { - return Objects.hash(title, description, url, pictureUrl); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/GroupBotMessageResult.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/GroupBotMessageResult.java deleted file mode 100644 index b720f5e94..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/GroupBotMessageResult.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class GroupBotMessageResult { - - @JsonProperty("errcode") - private int errorCode; - - @JsonProperty("errmsg") - private String errorMessage; - - public GroupBotMessageResult() { - } - - public GroupBotMessageResult(int errorCode, String errorMessage) { - this.errorCode = errorCode; - this.errorMessage = errorMessage; - } - - public int getErrorCode() { - return errorCode; - } - - public void setErrorCode(int errorCode) { - this.errorCode = errorCode; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - GroupBotMessageResult that = (GroupBotMessageResult) o; - return errorCode == that.errorCode; - } - - @Override - public int hashCode() { - return Objects.hash(errorCode); - } - - @Override - public String toString() { - return "GroupBotMessageResult{" + - "errorCode=" + errorCode + - ", errorMessage='" + errorMessage + '\'' + - '}'; - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/ImageMessage.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/ImageMessage.java deleted file mode 100644 index 8467f6811..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/ImageMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class ImageMessage extends Message { - - private ImageMessageContent image; - - public ImageMessage() { - setMessageType(MessageType.IMAGE); - } - - public ImageMessageContent getImage() { - return image; - } - - public void setImage(ImageMessageContent image) { - this.image = image; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - ImageMessage that = (ImageMessage) o; - return image.equals(that.image); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), image); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/ImageMessageContent.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/ImageMessageContent.java deleted file mode 100644 index bd5a4cae3..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/ImageMessageContent.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class ImageMessageContent { - - private String base64; - private String md5; - - public String getBase64() { - return base64; - } - - public void setBase64(String base64) { - this.base64 = base64; - } - - public String getMd5() { - return md5; - } - - public void setMd5(String md5) { - this.md5 = md5; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ImageMessageContent that = (ImageMessageContent) o; - return base64.equals(that.base64) && - md5.equals(that.md5); - } - - @Override - public int hashCode() { - return Objects.hash(base64, md5); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MarkdownMessage.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MarkdownMessage.java deleted file mode 100644 index fcf93edc0..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MarkdownMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class MarkdownMessage extends Message { - - private MarkdownMessageContent markdown; - - public MarkdownMessage() { - setMessageType(MessageType.MARKDOWN); - } - - public MarkdownMessageContent getMarkdown() { - return markdown; - } - - public void setMarkdown(MarkdownMessageContent markdown) { - this.markdown = markdown; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - MarkdownMessage that = (MarkdownMessage) o; - return markdown.equals(that.markdown); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), markdown); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MarkdownMessageContent.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MarkdownMessageContent.java deleted file mode 100644 index 4e7e7bef6..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MarkdownMessageContent.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class MarkdownMessageContent { - - private String content; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MarkdownMessageContent that = (MarkdownMessageContent) o; - return content.equals(that.content); - } - - @Override - public int hashCode() { - return Objects.hash(content); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/Message.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/Message.java deleted file mode 100644 index 32bf462e4..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/Message.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class Message { - - @JsonProperty("msgtype") - private MessageType messageType; - - public MessageType getMessageType() { - return messageType; - } - - public void setMessageType(MessageType messageType) { - this.messageType = messageType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Message message = (Message) o; - return messageType == message.messageType; - } - - @Override - public int hashCode() { - return Objects.hash(messageType); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MessageBuilder.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MessageBuilder.java deleted file mode 100644 index 9b59ba75d..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MessageBuilder.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.LinkedList; -import java.util.List; - -/** - * @author Pengtao Qiu - */ -public class MessageBuilder { - - public static TextMessageBuilder text() { - return new TextMessageBuilder(); - } - - public static MarkdownMessageBuilder markdown() { - return new MarkdownMessageBuilder(); - } - - public static ImageMessageBuilder image() { - return new ImageMessageBuilder(); - } - - public static NewsMessageBuilder news() { - return new NewsMessageBuilder(); - } - - public static class TextMessageBuilder { - - private TextMessageContent content = new TextMessageContent(); - - public TextMessageBuilder content(String content) { - this.content.setContent(content); - return this; - } - - public TextMessageBuilder mentionedList(List mentionedList) { - content.setMentionedList(mentionedList); - return this; - } - - public TextMessageBuilder mentionedMobileList(List mentionedMobileList) { - content.setMentionedMobileList(mentionedMobileList); - return this; - } - - public TextMessage end() { - TextMessage textMessage = new TextMessage(); - textMessage.setText(content); - return textMessage; - } - } - - public static class MarkdownMessageBuilder { - private MarkdownMessageContent content = new MarkdownMessageContent(); - - public MarkdownMessageBuilder content(String content) { - this.content.setContent(content); - return this; - } - - public MarkdownMessage end() { - MarkdownMessage markdownMessage = new MarkdownMessage(); - markdownMessage.setMarkdown(content); - return markdownMessage; - } - } - - public static class ImageMessageBuilder { - private ImageMessageContent content = new ImageMessageContent(); - - public ImageMessageBuilder md5(String md5) { - content.setMd5(md5); - return this; - } - - public ImageMessageBuilder base64(String base64) { - content.setBase64(base64); - return this; - } - - public ImageMessage end() { - ImageMessage imageMessage = new ImageMessage(); - imageMessage.setImage(content); - return imageMessage; - } - } - - public static class NewsMessageBuilder { - - private NewsMessageContent content = new NewsMessageContent(); - - public NewsMessageBuilder() { - content.setArticles(new LinkedList<>()); - } - - public NewsMessageBuilder addArticle(Article article) { - content.getArticles().add(article); - return this; - } - - public NewsMessageBuilder addArticles(List
    articles) { - content.getArticles().addAll(articles); - return this; - } - - public NewsMessage end() { - NewsMessage newsMessage = new NewsMessage(); - newsMessage.setNews(content); - return newsMessage; - } - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MessageType.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MessageType.java deleted file mode 100644 index d7a81136e..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/MessageType.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import com.fasterxml.jackson.annotation.JsonValue; - -/** - * @author Pengtao Qiu - */ -public enum MessageType { - - TEXT("text"), MARKDOWN("markdown"), IMAGE("image"), NEWS("news"); - - private final String value; - - MessageType(String value) { - this.value = value; - } - - @JsonValue - public String getValue() { - return value; - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/NewsMessage.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/NewsMessage.java deleted file mode 100644 index 021f9ecb2..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/NewsMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class NewsMessage extends Message { - - private NewsMessageContent news; - - public NewsMessage() { - setMessageType(MessageType.NEWS); - } - - public NewsMessageContent getNews() { - return news; - } - - public void setNews(NewsMessageContent news) { - this.news = news; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - NewsMessage that = (NewsMessage) o; - return news.equals(that.news); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), news); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/NewsMessageContent.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/NewsMessageContent.java deleted file mode 100644 index 5e168919d..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/NewsMessageContent.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.List; -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class NewsMessageContent { - - private List
    articles; - - public List
    getArticles() { - return articles; - } - - public void setArticles(List
    articles) { - this.articles = articles; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NewsMessageContent that = (NewsMessageContent) o; - return Objects.equals(articles, that.articles); - } - - @Override - public int hashCode() { - return Objects.hash(articles); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/TextMessage.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/TextMessage.java deleted file mode 100644 index 224035489..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/TextMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class TextMessage extends Message { - - private TextMessageContent text; - - public TextMessage() { - setMessageType(MessageType.TEXT); - } - - public TextMessageContent getText() { - return text; - } - - public void setText(TextMessageContent text) { - this.text = text; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - TextMessage that = (TextMessage) o; - return text.equals(that.text); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), text); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/TextMessageContent.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/TextMessageContent.java deleted file mode 100644 index 74132ce60..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/model/TextMessageContent.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; -import java.util.Objects; - -/** - * @author Pengtao Qiu - */ -public class TextMessageContent { - - private String content; - - @JsonProperty("mentioned_list") - private List mentionedList; - - @JsonProperty("mentioned_mobile_list") - private List mentionedMobileList; - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public List getMentionedList() { - return mentionedList; - } - - public void setMentionedList(List mentionedList) { - this.mentionedList = mentionedList; - } - - public List getMentionedMobileList() { - return mentionedMobileList; - } - - public void setMentionedMobileList(List mentionedMobileList) { - this.mentionedMobileList = mentionedMobileList; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TextMessageContent that = (TextMessageContent) o; - return content.equals(that.content) && - Objects.equals(mentionedList, that.mentionedList) && - Objects.equals(mentionedMobileList, that.mentionedMobileList); - } - - @Override - public int hashCode() { - return Objects.hash(content, mentionedList, mentionedMobileList); - } -} diff --git a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/service/GroupBotMessageService.java b/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/service/GroupBotMessageService.java deleted file mode 100644 index 33d88e824..000000000 --- a/firefly-wechat/src/main/java/com/fireflysource/wechat/enterprise/group/bot/service/GroupBotMessageService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.service; - -import com.fireflysource.wechat.enterprise.group.bot.model.GroupBotMessageResult; -import com.fireflysource.wechat.enterprise.group.bot.model.Message; - -import java.util.concurrent.CompletableFuture; - -/** - * @author Pengtao Qiu - */ -public interface GroupBotMessageService { - - CompletableFuture sendMessage(Message message); - -} diff --git a/firefly-wechat/src/main/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/GroupBotMessageServiceFactory.kt b/firefly-wechat/src/main/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/GroupBotMessageServiceFactory.kt deleted file mode 100644 index bd7f724e3..000000000 --- a/firefly-wechat/src/main/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/GroupBotMessageServiceFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.service - -import com.fireflysource.fx -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.wechat.enterprise.group.bot.service.impl.AsyncGroupBotMessageService - -/** - * @author Pengtao Qiu - */ -object GroupBotMessageServiceFactory { - - @JvmOverloads - fun create(webHookUrl: String, httpClient: HttpClient = fx.httpClient()): GroupBotMessageService { - return AsyncGroupBotMessageService(webHookUrl, httpClient) - } -} \ No newline at end of file diff --git a/firefly-wechat/src/main/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/impl/AsyncGroupBotMessageService.kt b/firefly-wechat/src/main/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/impl/AsyncGroupBotMessageService.kt deleted file mode 100644 index 70e9f8317..000000000 --- a/firefly-wechat/src/main/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/impl/AsyncGroupBotMessageService.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.service.impl - -import com.fireflysource.net.http.client.HttpClient -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.HttpStatus -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.serialization.SerializationServiceFactory.json -import com.fireflysource.serialization.impl.json.read -import com.fireflysource.wechat.enterprise.group.bot.model.GroupBotMessageResult -import com.fireflysource.wechat.enterprise.group.bot.model.Message -import com.fireflysource.wechat.enterprise.group.bot.service.GroupBotMessageService -import java.util.concurrent.CompletableFuture - -/** - * @author Pengtao Qiu - */ -class AsyncGroupBotMessageService( - private val webHookUrl: String, - private val httpClient: HttpClient -) : GroupBotMessageService { - - override fun sendMessage(message: Message): CompletableFuture { - return httpClient.post(webHookUrl) - .put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value) - .body(json.write(message)) - .submit() - .thenApply { response -> - if (response.status == HttpStatus.OK_200) { - json.read(response.stringBody) - } else { - GroupBotMessageResult( - -99999, - "The http request failure. status: ${response.status}, content: ${response.stringBody}" - ) - } - } - } -} \ No newline at end of file diff --git a/firefly-wechat/src/test/kotlin/com/fireflysource/wechat/enterprise/group/bot/model/TestMessageModel.kt b/firefly-wechat/src/test/kotlin/com/fireflysource/wechat/enterprise/group/bot/model/TestMessageModel.kt deleted file mode 100644 index b5450b803..000000000 --- a/firefly-wechat/src/test/kotlin/com/fireflysource/wechat/enterprise/group/bot/model/TestMessageModel.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.model - -import com.fireflysource.serialization.SerializationServiceFactory.json -import com.fireflysource.serialization.impl.json.read -import com.fireflysource.wechat.enterprise.group.bot.model.MessageBuilder.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream - -/** - * @author Pengtao Qiu - */ -class TestMessageModel { - - companion object { - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - arguments( - """ - { - "msgtype": "text", - "text": { - "content": "hello world" - } - } - """.trimIndent(), - text().content("hello world").end() - ), - arguments( - """ - { - "msgtype": "text", - "text": { - "content": "广州今日天气:29度,大部分多云,降雨概率:60%", - "mentioned_list":["wangqing","@all"], - "mentioned_mobile_list":["13800001111","@all"] - } - } - """.trimIndent(), - text().content("广州今日天气:29度,大部分多云,降雨概率:60%") - .mentionedList(listOf("wangqing", "@all")) - .mentionedMobileList(listOf("13800001111", "@all")) - .end() - ), - arguments( - "{\"markdown\":{\"content\":\"实时新增用户反馈132例,请相关同事注意。\\n>类型:用户反馈 \\n>普通用户反馈:117例 \\n>VIP用户反馈:15例\"},\"msgtype\":\"markdown\"}", - markdown().content( - """ - |实时新增用户反馈132例,请相关同事注意。 - |>类型:用户反馈 - |>普通用户反馈:117例 - |>VIP用户反馈:15例 - """.trimMargin() - ) - .end() - ), - arguments( - """ - { - "msgtype": "image", - "image": { - "base64": "DATA", - "md5": "MD5" - } - } - """.trimIndent(), - image().base64("DATA").md5("MD5").end() - ), - arguments( - """ - { - "msgtype": "news", - "news": { - "articles" : [ - { - "title" : "中秋节礼品领取", - "description" : "今年中秋节公司有豪礼相送", - "url" : "URL", - "picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png" - } - ] - } - } - """.trimIndent(), - news().addArticle( - Article( - "中秋节礼品领取", - "今年中秋节公司有豪礼相送", - "URL", - "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png" - ) - ) - .end() - ) - ) - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should read wechat enterprise group bot message successfully.") - fun test(string: String, message: Message) { - val m = when (message.messageType) { - MessageType.TEXT -> json.read(string) - MessageType.MARKDOWN -> json.read(string) - MessageType.IMAGE -> json.read(string) - MessageType.NEWS -> json.read(string) - else -> null - } - assertEquals(message, m) - } - -} \ No newline at end of file diff --git a/firefly-wechat/src/test/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/TestGroupBotMessageService.kt b/firefly-wechat/src/test/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/TestGroupBotMessageService.kt deleted file mode 100644 index 82b2e11d0..000000000 --- a/firefly-wechat/src/test/kotlin/com/fireflysource/wechat/enterprise/group/bot/service/TestGroupBotMessageService.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.fireflysource.wechat.enterprise.group.bot.service - -import com.fireflysource.fx -import com.fireflysource.net.http.common.model.HttpHeader -import com.fireflysource.net.http.common.model.MimeTypes -import com.fireflysource.net.http.server.HttpServer -import com.fireflysource.serialization.SerializationServiceFactory.json -import com.fireflysource.wechat.enterprise.group.bot.model.GroupBotMessageResult -import com.fireflysource.wechat.enterprise.group.bot.model.MessageBuilder -import kotlinx.coroutines.future.await -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream -import kotlin.random.Random - -/** - * @author Pengtao Qiu - */ -class TestGroupBotMessageService { - - companion object { - - @JvmStatic - fun testParametersProvider(): Stream { - return Stream.of( - arguments( - "/cgi-bin/webhook/send", - GroupBotMessageResult( - 93000, - "invalid webhook url, hint: [1592021214_50_551fc8807c11f27ed0e6714d0c267ba4], from ip: 171.43.164.231, more info at https://open.work.weixin.qq.com/devtool/query?e=93000" - ) - ), - arguments("/cgi-bin/webhook/send?key=testKey", GroupBotMessageResult(0, "ok")), - arguments( - "/cgi-bin/webhook/send?key=xxx", GroupBotMessageResult( - 93000, - "invalid webhook url, hint: [1592021214_50_551fc8807c11f27ed0e6714d0c267ba4], from ip: 171.43.164.231, more info at https://open.work.weixin.qq.com/devtool/query?e=93000" - ) - ), - arguments( - "/cgi-bin/webhook/sendFFFFF", GroupBotMessageResult( - -99999, - "The http request failure." - ) - ) - ) - } - } - - @ParameterizedTest - @MethodSource("testParametersProvider") - @DisplayName("should send wechat enterprise group bot message successfully.") - fun test(path: String, expectedResult: GroupBotMessageResult) = runTest { - val host = "localhost" - val port = Random.nextInt(10000, 20000) - val webHookUrl = "https://$host:$port$path" - - val server = createMockServer() - val client = fx.createHttpClient() - server.listen(host, port) - - val service = GroupBotMessageServiceFactory.create(webHookUrl, client) - val message = MessageBuilder.text().content("hello world").end() - val result = service.sendMessage(message).await() - assertEquals(expectedResult, result) - - client.stop() - server.stop() - } - - private fun createMockServer(): HttpServer { - return fx.createHttpServer() - .router() - .post("/cgi-bin/webhook/send") - .handler { ctx -> - val key = ctx.getQueryString("key") - val result = when { - key.isNullOrBlank() || key != "testKey" -> GroupBotMessageResult( - 93000, - "invalid webhook url, hint: [1592021214_50_551fc8807c11f27ed0e6714d0c267ba4], from ip: 171.43.164.231, more info at https://open.work.weixin.qq.com/devtool/query?e=93000" - ) - else -> GroupBotMessageResult(0, "ok") - } - - ctx.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.value).end(json.write(result)) - } - .enableSecureConnection() - } -} \ No newline at end of file diff --git a/firefly-wechat/src/test/resources/firefly-log.xml b/firefly-wechat/src/test/resources/firefly-log.xml deleted file mode 100644 index d139af581..000000000 --- a/firefly-wechat/src/test/resources/firefly-log.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - firefly-system - INFO - ${log.path} - false - - - - firefly-monitor - INFO - ${log.path} - - - diff --git a/fonts/9Xe8dq6pQDsPyVH2D3tMQgzyDMXhdD8sAj6OAJTFsBI.woff2 b/fonts/9Xe8dq6pQDsPyVH2D3tMQgzyDMXhdD8sAj6OAJTFsBI.woff2 new file mode 100644 index 000000000..1673ee479 Binary files /dev/null and b/fonts/9Xe8dq6pQDsPyVH2D3tMQgzyDMXhdD8sAj6OAJTFsBI.woff2 differ diff --git a/fonts/9Xe8dq6pQDsPyVH2D3tMQr3hpw3pgy2gAi-Ip7WPMi0.woff b/fonts/9Xe8dq6pQDsPyVH2D3tMQr3hpw3pgy2gAi-Ip7WPMi0.woff new file mode 100644 index 000000000..d9614e20f Binary files /dev/null and b/fonts/9Xe8dq6pQDsPyVH2D3tMQr3hpw3pgy2gAi-Ip7WPMi0.woff differ diff --git a/fonts/G28Ny31cr5orMqEQy6ljt2aVI6zN22yiurzcBKxPjFE.woff2 b/fonts/G28Ny31cr5orMqEQy6ljt2aVI6zN22yiurzcBKxPjFE.woff2 new file mode 100644 index 000000000..d1de85c13 Binary files /dev/null and b/fonts/G28Ny31cr5orMqEQy6ljt2aVI6zN22yiurzcBKxPjFE.woff2 differ diff --git a/fonts/G28Ny31cr5orMqEQy6ljt3bFhgvWbfSbdVg11QabG8w.woff b/fonts/G28Ny31cr5orMqEQy6ljt3bFhgvWbfSbdVg11QabG8w.woff new file mode 100644 index 000000000..b01c89f06 Binary files /dev/null and b/fonts/G28Ny31cr5orMqEQy6ljt3bFhgvWbfSbdVg11QabG8w.woff differ diff --git a/fonts/G28Ny31cr5orMqEQy6ljtzrEaqfC9P2pvLXik1Kbr9s.woff2 b/fonts/G28Ny31cr5orMqEQy6ljtzrEaqfC9P2pvLXik1Kbr9s.woff2 new file mode 100644 index 000000000..cbe4d5b66 Binary files /dev/null and b/fonts/G28Ny31cr5orMqEQy6ljtzrEaqfC9P2pvLXik1Kbr9s.woff2 differ diff --git a/fonts/d03oiboZGiaNuMDvH253CgsYbbCjybiHxArTLjt7FRU.woff2 b/fonts/d03oiboZGiaNuMDvH253CgsYbbCjybiHxArTLjt7FRU.woff2 new file mode 100644 index 000000000..28723c241 Binary files /dev/null and b/fonts/d03oiboZGiaNuMDvH253CgsYbbCjybiHxArTLjt7FRU.woff2 differ diff --git a/fonts/glyphicons-halflings-regular.eot b/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 000000000..87eaa4342 Binary files /dev/null and b/fonts/glyphicons-halflings-regular.eot differ diff --git a/fonts/glyphicons-halflings-regular.svg b/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 000000000..5fee06854 --- /dev/null +++ b/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/glyphicons-halflings-regular.ttf b/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 000000000..be784dc1d Binary files /dev/null and b/fonts/glyphicons-halflings-regular.ttf differ diff --git a/fonts/glyphicons-halflings-regular.woff b/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 000000000..2cc3e4852 Binary files /dev/null and b/fonts/glyphicons-halflings-regular.woff differ diff --git a/images/architecture.png b/images/architecture.png new file mode 100644 index 000000000..c2872fbeb Binary files /dev/null and b/images/architecture.png differ diff --git a/images/author-ali-pay.jpg b/images/author-ali-pay.jpg new file mode 100644 index 000000000..05d9f5df4 Binary files /dev/null and b/images/author-ali-pay.jpg differ diff --git a/images/author-red-envelope.png b/images/author-red-envelope.png new file mode 100644 index 000000000..df5c32fa3 Binary files /dev/null and b/images/author-red-envelope.png differ diff --git a/images/author-wechat-pay.jpg b/images/author-wechat-pay.jpg new file mode 100644 index 000000000..e54c0f9d8 Binary files /dev/null and b/images/author-wechat-pay.jpg differ diff --git a/images/firefly-logo.png b/images/firefly-logo.png new file mode 100644 index 000000000..464bd92e6 Binary files /dev/null and b/images/firefly-logo.png differ diff --git a/images/network-framework.png b/images/network-framework.png new file mode 100644 index 000000000..895418c92 Binary files /dev/null and b/images/network-framework.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..352c3fb2e --- /dev/null +++ b/index.html @@ -0,0 +1,86 @@ +--- +layout: default +title: Firefly Framework +--- + +
    +
    +

    + Firefly is an asynchronous web framework
    + for rapid development of high-performance web application. +

    + More details +
    +
    + +
    +
    +
    +

    + Firefly is an asynchronous web framework which bases on the intuitive design that enables QUICK and EASY development of network applications, such as website, protocol servers, etc. +

    +

    + Firefly is incredibly flexible - whether it's simple network utilities, sophisticated modern web applications, HTTP/REST microservices. + Firefly is not a restrictive framework or container, and we don't tell you a correct way to write an application. + Instead, we give you a lot of useful bricks and let you create your app the way you want to. + Need some guidance? We provide a large selection of examples to get you started for the particular type of application you want to write. +

    +

    + If you already worked with some web applications, such as ktor, http4k, akka, you will feel right at home. + Firefly uses some same concepts and naming schemes. + Even if you’re from a different background, you’ll get the hang of it quickly, because everything is designed to be an intuitive and easy to use as possible. +

    +

    + Quick and easy doesn't mean it has the performance issue or it's not maintainable. + Firefly exposes the hardware’s full potential and uses some technologies of state of the art to achieve high throughput. + In short: It’s FAST. +

    +

    + Enjoy being a developer again. Unlike restrictive traditional application containers. + Firefly gives you incredible power and agility to create compelling, scalable, 21st-century applications the way you want to. +

    +
    +
    + +
    +
    + +
    +
    +

    News

    +
    +
    +
    + +
    +
    + {% for post in site.posts limit:10 %} +
    +

    {{ post.title }}

    +

    {{ post.date | date_to_string }}, {{ post.author }}

    +

    {{ post.excerpt }}

    + +
    + {% endfor %} +
    + +
    +
    +
    + +

    +


    + +

    +
    +
    + YourKit supports open source projects with its full-featured Java Profiler. + YourKit, LLC is the creator of YourKit Java Profiler + and YourKit .NET Profiler, + innovative and intelligent tools for profiling Java and .NET applications. +
    +
    + +
    diff --git a/js/bootstrap.min.js b/js/bootstrap.min.js new file mode 100644 index 000000000..9bcd2fcca --- /dev/null +++ b/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/js/html5shiv.js b/js/html5shiv.js new file mode 100644 index 000000000..448cebd79 --- /dev/null +++ b/js/html5shiv.js @@ -0,0 +1,8 @@ +/* + HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); +a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; +c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| +"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f); +if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;da?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
    ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
    a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("