瀏覽代碼

init blelib-orangePI

zzf 2 年之前
父節點
當前提交
2e2e9dd468
共有 100 個文件被更改,包括 4756 次插入0 次删除
  1. 1 0
      .gitignore
  2. 二進制
      .gradle/5.6.4/executionHistory/executionHistory.bin
  3. 二進制
      .gradle/5.6.4/executionHistory/executionHistory.lock
  4. 二進制
      .gradle/5.6.4/fileChanges/last-build.bin
  5. 二進制
      .gradle/5.6.4/fileContent/annotation-processors.bin
  6. 二進制
      .gradle/5.6.4/fileContent/fileContent.lock
  7. 二進制
      .gradle/5.6.4/fileHashes/fileHashes.bin
  8. 二進制
      .gradle/5.6.4/fileHashes/fileHashes.lock
  9. 二進制
      .gradle/5.6.4/fileHashes/resourceHashesCache.bin
  10. 0 0
      .gradle/5.6.4/gc.properties
  11. 二進制
      .gradle/5.6.4/javaCompile/classAnalysis.bin
  12. 二進制
      .gradle/5.6.4/javaCompile/jarAnalysis.bin
  13. 二進制
      .gradle/5.6.4/javaCompile/javaCompile.lock
  14. 二進制
      .gradle/5.6.4/javaCompile/taskHistory.bin
  15. 二進制
      .gradle/buildOutputCleanup/buildOutputCleanup.lock
  16. 2 0
      .gradle/buildOutputCleanup/cache.properties
  17. 二進制
      .gradle/buildOutputCleanup/outputFiles.bin
  18. 0 0
      .gradle/vcs-1/gc.properties
  19. 3 0
      .idea/.gitignore
  20. 6 0
      .idea/compiler.xml
  21. 24 0
      .idea/gradle.xml
  22. 30 0
      .idea/jarRepositories.xml
  23. 10 0
      .idea/misc.xml
  24. 6 0
      .idea/vcs.xml
  25. 10 0
      CONTRIBUTING.md
  26. 202 0
      LICENSE
  27. 1 0
      annotation/.gitignore
  28. 8 0
      annotation/build.gradle
  29. 3 0
      annotation/gradle.properties
  30. 79 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/Addition.java
  31. 35 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/AppInfo.java
  32. 48 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/Config.java
  33. 34 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/Controller.java
  34. 34 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/Converter.java
  35. 56 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/CookieValue.java
  36. 105 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/CrossOrigin.java
  37. 59 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/DeleteMapping.java
  38. 37 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/FormPart.java
  39. 59 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/GetMapping.java
  40. 34 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/Interceptor.java
  41. 59 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/PatchMapping.java
  42. 55 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/PathVariable.java
  43. 59 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/PostMapping.java
  44. 59 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/PutMapping.java
  45. 55 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/QueryParam.java
  46. 36 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestBody.java
  47. 55 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestHeader.java
  48. 125 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMapping.java
  49. 45 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMethod.java
  50. 55 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestParam.java
  51. 34 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/Resolver.java
  52. 28 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/ResponseBody.java
  53. 34 0
      annotation/src/main/java/com/yanzhenjie/andserver/annotation/RestController.java
  54. 1 0
      api/.gitignore
  55. 35 0
      api/build.gradle
  56. 3 0
      api/gradle.properties
  57. 1 0
      api/proguard-rules.txt
  58. 17 0
      api/src/main/AndroidManifest.xml
  59. 83 0
      api/src/main/java/com/yanzhenjie/andserver/AndServer.java
  60. 87 0
      api/src/main/java/com/yanzhenjie/andserver/ComponentRegister.java
  61. 276 0
      api/src/main/java/com/yanzhenjie/andserver/DispatcherHandler.java
  62. 181 0
      api/src/main/java/com/yanzhenjie/andserver/ProxyHandler.java
  63. 29 0
      api/src/main/java/com/yanzhenjie/andserver/SSLSocketInitializer.java
  64. 179 0
      api/src/main/java/com/yanzhenjie/andserver/Server.java
  65. 37 0
      api/src/main/java/com/yanzhenjie/andserver/error/BasicException.java
  66. 35 0
      api/src/main/java/com/yanzhenjie/andserver/error/BodyMissingException.java
  67. 34 0
      api/src/main/java/com/yanzhenjie/andserver/error/ContentNotAcceptableException.java
  68. 35 0
      api/src/main/java/com/yanzhenjie/andserver/error/ContentNotSupportedException.java
  69. 38 0
      api/src/main/java/com/yanzhenjie/andserver/error/CookieMissingException.java
  70. 38 0
      api/src/main/java/com/yanzhenjie/andserver/error/HeaderMissingException.java
  71. 36 0
      api/src/main/java/com/yanzhenjie/andserver/error/HeaderValidateException.java
  72. 46 0
      api/src/main/java/com/yanzhenjie/andserver/error/HttpException.java
  73. 51 0
      api/src/main/java/com/yanzhenjie/andserver/error/InvalidMediaTypeException.java
  74. 42 0
      api/src/main/java/com/yanzhenjie/andserver/error/InvalidMimeTypeException.java
  75. 53 0
      api/src/main/java/com/yanzhenjie/andserver/error/MaxUploadSizeExceededException.java
  76. 47 0
      api/src/main/java/com/yanzhenjie/andserver/error/MethodNotSupportException.java
  77. 43 0
      api/src/main/java/com/yanzhenjie/andserver/error/MultipartException.java
  78. 42 0
      api/src/main/java/com/yanzhenjie/andserver/error/NotFoundException.java
  79. 38 0
      api/src/main/java/com/yanzhenjie/andserver/error/ParamMissingException.java
  80. 36 0
      api/src/main/java/com/yanzhenjie/andserver/error/ParamValidateException.java
  81. 38 0
      api/src/main/java/com/yanzhenjie/andserver/error/PathMissingException.java
  82. 38 0
      api/src/main/java/com/yanzhenjie/andserver/error/ServerInternalException.java
  83. 33 0
      api/src/main/java/com/yanzhenjie/andserver/framework/ETag.java
  84. 79 0
      api/src/main/java/com/yanzhenjie/andserver/framework/ExceptionResolver.java
  85. 40 0
      api/src/main/java/com/yanzhenjie/andserver/framework/HandlerInterceptor.java
  86. 38 0
      api/src/main/java/com/yanzhenjie/andserver/framework/LastModified.java
  87. 56 0
      api/src/main/java/com/yanzhenjie/andserver/framework/MessageConverter.java
  88. 56 0
      api/src/main/java/com/yanzhenjie/andserver/framework/ModifiedInterceptor.java
  89. 72 0
      api/src/main/java/com/yanzhenjie/andserver/framework/body/FileBody.java
  90. 42 0
      api/src/main/java/com/yanzhenjie/andserver/framework/body/JsonBody.java
  91. 97 0
      api/src/main/java/com/yanzhenjie/andserver/framework/body/StreamBody.java
  92. 90 0
      api/src/main/java/com/yanzhenjie/andserver/framework/body/StringBody.java
  93. 56 0
      api/src/main/java/com/yanzhenjie/andserver/framework/config/Delegate.java
  94. 119 0
      api/src/main/java/com/yanzhenjie/andserver/framework/config/Multipart.java
  95. 58 0
      api/src/main/java/com/yanzhenjie/andserver/framework/config/WebConfig.java
  96. 90 0
      api/src/main/java/com/yanzhenjie/andserver/framework/cross/CrossOrigin.java
  97. 49 0
      api/src/main/java/com/yanzhenjie/andserver/framework/handler/HandlerAdapter.java
  98. 338 0
      api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingAdapter.java
  99. 186 0
      api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingHandler.java
  100. 53 0
      api/src/main/java/com/yanzhenjie/andserver/framework/handler/MethodHandler.java

+ 1 - 0
.gitignore

@@ -12,3 +12,4 @@
 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
 hs_err_pid*
 
+/megablelibopen/build/

二進制
.gradle/5.6.4/executionHistory/executionHistory.bin


二進制
.gradle/5.6.4/executionHistory/executionHistory.lock


二進制
.gradle/5.6.4/fileChanges/last-build.bin


二進制
.gradle/5.6.4/fileContent/annotation-processors.bin


二進制
.gradle/5.6.4/fileContent/fileContent.lock


二進制
.gradle/5.6.4/fileHashes/fileHashes.bin


二進制
.gradle/5.6.4/fileHashes/fileHashes.lock


二進制
.gradle/5.6.4/fileHashes/resourceHashesCache.bin


+ 0 - 0
.gradle/5.6.4/gc.properties


二進制
.gradle/5.6.4/javaCompile/classAnalysis.bin


二進制
.gradle/5.6.4/javaCompile/jarAnalysis.bin


二進制
.gradle/5.6.4/javaCompile/javaCompile.lock


二進制
.gradle/5.6.4/javaCompile/taskHistory.bin


二進制
.gradle/buildOutputCleanup/buildOutputCleanup.lock


+ 2 - 0
.gradle/buildOutputCleanup/cache.properties

@@ -0,0 +1,2 @@
+#Wed Apr 26 10:16:20 CST 2023
+gradle.version=5.6.4

二進制
.gradle/buildOutputCleanup/outputFiles.bin


+ 0 - 0
.gradle/vcs-1/gc.properties


+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="1.8" />
+  </component>
+</project>

+ 24 - 0
.idea/gradle.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/annotation" />
+            <option value="$PROJECT_DIR$/api" />
+            <option value="$PROJECT_DIR$/megablelibopen" />
+            <option value="$PROJECT_DIR$/plugin" />
+            <option value="$PROJECT_DIR$/processor" />
+            <option value="$PROJECT_DIR$/sample" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 30 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="MavenLocal" />
+      <option name="name" value="MavenLocal" />
+      <option name="url" value="file:/$USER_HOME$/.m2/repository/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="MavenRepo" />
+      <option name="name" value="MavenRepo" />
+      <option name="url" value="https://repo.maven.apache.org/maven2/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="Google" />
+      <option name="name" value="Google" />
+      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
+    </remote-repository>
+  </component>
+</project>

+ 10 - 0
.idea/misc.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 10 - 0
CONTRIBUTING.md

@@ -0,0 +1,10 @@
+# Contributing to AndServer
+First off, thanks for taking the time to contribute.  
+
+The following is a set of guidelines for contributing to AndServer. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+1. All your actions in AndServer should be in English, not in other languages.
+2. Please keep the existing code style, not according to your habits.
+3. Just modify the code you are sure need to be optimized, not all the different code from your ideas.
+4. Before launching a pull request, you should test your commit code adequately.
+5. Please commit new code to the [dev](https://github.com/yanzhenjie/AndServer/tree/dev) branch instead of the master branch.

+ 202 - 0
LICENSE

@@ -0,0 +1,202 @@
+                                 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 2019 Zhenjie Yan
+
+   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.
+

+ 1 - 0
annotation/.gitignore

@@ -0,0 +1 @@
+/build

+ 8 - 0
annotation/build.gradle

@@ -0,0 +1,8 @@
+apply plugin: plugin.javaLibrary
+
+compileJava {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+apply from: publishScript

+ 3 - 0
annotation/gradle.properties

@@ -0,0 +1,3 @@
+POM_NAME=andserver-annotation
+POM_ARTIFACT_ID=annotation
+POM_PACKAGING=jar

+ 79 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/Addition.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Addition {
+
+    /**
+     * Alias for {@link #stringType()}.
+     */
+    String[] value() default {};
+
+    /**
+     * The added value of the String type.
+     */
+    String[] stringType() default {};
+
+    /**
+     * The added value of the boolean type.
+     */
+    boolean[] booleanType() default {};
+
+    /**
+     * The added value of the int type.
+     */
+    int[] intTypeType() default {};
+
+    /**
+     * The added value of the long type.
+     */
+    long[] longType() default {};
+
+    /**
+     * The added value of the short type.
+     */
+    short[] shortType() default {};
+
+    /**
+     * The added value of the float type.
+     */
+    float[] floatType() default {};
+
+    /**
+     * The added value of the double type.
+     */
+    double[] doubleType() default {};
+
+    /**
+     * The added value of the byte type.
+     */
+    byte[] byteType() default {};
+
+    /**
+     * The added value of the char type.
+     */
+    char[] charType() default {};
+}

+ 35 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/AppInfo.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 4/11/20.
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface AppInfo {
+
+    /**
+     * Application Id.
+     */
+    String value() default "";
+
+}

+ 48 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/Config.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2019 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ * <pre>
+ * <code>@Config</code>
+ * public class AppConfig implements WebConfig {
+ *
+ *     <code>@Override</code>
+ *     public void onConfig(Context context, Delegate delegate) {
+ *         Website website = ...;
+ *         delegate.addWebsite(website);
+ *
+ *         Multipart multipart = Multipart.newBuilder()...build();
+ *         delegate.setMultipart(multipart);
+ *     }
+ * }
+ * </pre>
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Config {
+
+    /**
+     * Group name.
+     */
+    String value() default "default";
+}

+ 34 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/Controller.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Controller {
+
+    /**
+     * Group name.
+     */
+    String value() default "default";
+}

+ 34 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/Converter.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/11.
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Converter {
+
+    /**
+     * Group name.
+     */
+    String value() default "default";
+}

+ 56 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/CookieValue.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.SOURCE)
+public @interface CookieValue {
+
+    /**
+     * Alias for {@link #name()}.
+     */
+    String value() default "";
+
+    /**
+     * The name of the cookie to bind to.
+     */
+    String name() default "";
+
+    /**
+     * Whether the cookie is required.
+     *
+     * <p>Defaults to {@code true}, leading to an exception being thrown if the cookie is missing in the request. Switch
+     * this to {@code false} if you prefer a {@code null} value if the cookie is not present in the request.
+     *
+     * <p>Alternatively, provide a {@link #defaultValue()}, which implicitly sets this flag to {@code false}.
+     */
+    boolean required() default true;
+
+    /**
+     * The default value to use as a fallback.
+     *
+     * <p>Supplying a default value implicitly sets {@link #required()} to {@code false}.
+     */
+    String defaultValue() default "";
+}

+ 105 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/CrossOrigin.java

@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 10/10/20.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface CrossOrigin {
+
+    /**
+     * Alias for {@link #origins()}.
+     */
+    String[] value() default {};
+
+    /**
+     * List of allowed origins, e.g. {@code "http://domain1.com"}.
+     *
+     * <p>These values are placed in the {@code Access-Control-Allow-Origin}
+     * header of both the pre-flight response and the actual response.
+     * {@code "*"} means that all origins are allowed.
+     *
+     * <p>If undefined, all origins are allowed.
+     *
+     * @see #value()
+     */
+    String[] origins() default {};
+
+    /**
+     * List of request headers that can be used during the actual request.
+     *
+     * <p>This property controls the value of the pre-flight response's
+     * {@code Access-Control-Allow-Headers} header.
+     * {@code "*"}  means that all headers requested by the client are allowed.
+     *
+     * <p>If undefined, all requested headers are allowed.
+     */
+    String[] allowedHeaders() default {};
+
+    /**
+     * List of response headers that the user-agent will allow the client to access.
+     *
+     * <p>This property controls the value of actual response's
+     * {@code Access-Control-Expose-Headers} header.
+     *
+     * <p>If undefined, an empty exposed header list is used.
+     */
+    String[] exposedHeaders() default {};
+
+    /**
+     * List of supported HTTP request methods, e.g.
+     * {@code "{RequestMethod.GET, RequestMethod.POST}"}.
+     *
+     * <p>Methods specified here override those specified via {@code RequestMapping}.
+     *
+     * <p>If undefined, methods defined by {@link RequestMapping} annotation are used.
+     */
+    RequestMethod[] methods() default {};
+
+    /**
+     * Whether the browser should include any cookies associated with the
+     * domain of the request being annotated.
+     *
+     * <p>Set to {@code "false"} if such cookies should not included.
+     * An empty string ({@code ""}) means <em>undefined</em>.
+     * {@code "true"} means that the pre-flight response will include the header
+     * {@code Access-Control-Allow-Credentials=true}.
+     *
+     * <p>If undefined, credentials are allowed.
+     */
+    String allowCredentials() default "";
+
+    /**
+     * The maximum age (in seconds) of the cache duration for pre-flight responses.
+     *
+     * <p>This property controls the value of the {@code Access-Control-Max-Age}
+     * header in the pre-flight response.
+     *
+     * <p>Setting this to a reasonable value can reduce the number of pre-flight
+     * request/response interactions required by the browser.
+     * A negative value means <em>undefined</em>.
+     *
+     * <p>If undefined, max age is set to {@code 1800} seconds (i.e., 30 minutes).
+     */
+    long maxAge() default -1;
+}

+ 59 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/DeleteMapping.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/4.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface DeleteMapping {
+
+    /**
+     * Alias for {@link RequestMapping#value()}.
+     */
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path()}.
+     */
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params()}.
+     */
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers()}.
+     */
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes()}.
+     */
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces()}.
+     */
+    String[] produces() default {};
+}

+ 37 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/FormPart.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/13.
+ */
+public @interface FormPart {
+
+    /**
+     * Alias for {@link #name()}.
+     */
+    String value() default "";
+
+    /**
+     * The name of the request parameter to bind to.
+     */
+    String name() default "";
+
+    /**
+     * Whether the parameter is required.
+     */
+    boolean required() default true;
+}

+ 59 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/GetMapping.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/4.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface GetMapping {
+
+    /**
+     * Alias for {@link RequestMapping#value()}.
+     */
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path()}.
+     */
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params()}.
+     */
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers()}.
+     */
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes()}.
+     */
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces()}.
+     */
+    String[] produces() default {};
+}

+ 34 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/Interceptor.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/11.
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Interceptor {
+
+    /**
+     * Group name.
+     */
+    String value() default "default";
+}

+ 59 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/PatchMapping.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/4.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PatchMapping {
+
+    /**
+     * Alias for {@link RequestMapping#value()}.
+     */
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path()}.
+     */
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params()}.
+     */
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers()}.
+     */
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes()}.
+     */
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces()}.
+     */
+    String[] produces() default {};
+}

+ 55 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/PathVariable.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.SOURCE)
+public @interface PathVariable {
+
+    /**
+     * Alias for {@link #name()}.
+     */
+    String value() default "";
+
+    /**
+     * The name of the path variable to bind to.
+     */
+    String name() default "";
+
+    /**
+     * Whether the path is required.
+     *
+     * <p>Defaults to {@code true}, leading to an exception being thrown if the path is missing in the request.
+     *
+     * <p>Alternatively, provide a {@link #defaultValue()}, which implicitly sets this flag to {@code false}.
+     */
+    boolean required() default true;
+
+    /**
+     * The default value to use as a fallback.
+     *
+     * <p>Supplying a default value implicitly sets {@link #required()} to {@code false}.
+     */
+    String defaultValue() default "";
+}

+ 59 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/PostMapping.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/4.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PostMapping {
+
+    /**
+     * Alias for {@link RequestMapping#value()}.
+     */
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path()}.
+     */
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params()}.
+     */
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers()}.
+     */
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes()}.
+     */
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces()}.
+     */
+    String[] produces() default {};
+}

+ 59 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/PutMapping.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/4.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PutMapping {
+
+    /**
+     * Alias for {@link RequestMapping#value()}.
+     */
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path()}.
+     */
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params()}.
+     */
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers()}.
+     */
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes()}.
+     */
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces()}.
+     */
+    String[] produces() default {};
+}

+ 55 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/QueryParam.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/13.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.SOURCE)
+public @interface QueryParam {
+
+    /**
+     * Alias for {@link #name()}.
+     */
+    String value() default "";
+
+    /**
+     * The name of the request parameter to bind to.
+     */
+    String name() default "";
+
+    /**
+     * Whether the parameter is required.
+     *
+     * <p>Defaults to {@code true}, leading to an exception being thrown if the parameter is missing in the request.
+     *
+     * <p>Alternatively, provide a {@link #defaultValue()}, which implicitly sets this flag to {@code false}.
+     */
+    boolean required() default true;
+
+    /**
+     * The default value to use as a fallback.
+     *
+     * <p>Supplying a default value implicitly sets {@link #required()} to {@code false}.
+     */
+    String defaultValue() default "";
+}

+ 36 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestBody.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.SOURCE)
+public @interface RequestBody {
+
+    /**
+     * Whether body content is required.
+     *
+     * <p>Default is {@code true}, leading to an exception thrown in case there is no body content.
+     */
+    boolean required() default true;
+}

+ 55 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestHeader.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.SOURCE)
+public @interface RequestHeader {
+
+    /**
+     * Alias for {@link #name()}.
+     */
+    String value() default "";
+
+    /**
+     * The name of the request header to bind to.
+     */
+    String name() default "";
+
+    /**
+     * Whether the header is required.
+     *
+     * <p>Defaults to {@code true}, leading to an exception being thrown if the header is missing in the request.
+     *
+     * <p>Alternatively, provide a {@link #defaultValue()}, which implicitly sets this flag to {@code false}.
+     */
+    boolean required() default true;
+
+    /**
+     * The default value to use as a fallback.
+     *
+     * <p>Supplying a default value implicitly sets {@link #required()} to {@code false}.
+     */
+    String defaultValue() default "";
+}

+ 125 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMapping.java

@@ -0,0 +1,125 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface RequestMapping {
+
+    /**
+     * Alias for {@link #path()}.
+     */
+    String[] value() default {};
+
+    /**
+     * The primary mapping expressed by this annotation. For example {@code @RequestMapping ("/foo")} is equivalent to
+     * {@code @RequestMapping (path="/foo")}.
+     *
+     * <p><b>Supported at the type level as well as at the method level.</b> When used at the type level, all
+     * method-level mappings inherit this primary mapping, narrowing it for a specific handler method.
+     */
+    String[] path() default {};
+
+    /**
+     * The HTTP request methods to map to, narrowing the primary mapping: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE,
+     * TRACE.
+     *
+     * <p><b>Supported at the type level as well as at the method level.</b> When used at the type level, all
+     * method-level mappings inherit this HTTP method restriction (i.e. the type-level restriction gets checked before the
+     * handler method is even resolved).
+     */
+    RequestMethod[] method() default {};
+
+    /**
+     * The parameters of the mapped request, narrowing the primary mapping.
+     *
+     * <p>A sequence of "myParam=myValue" style expressions, with a request only mapped if each such parameter is found
+     * to have the given value. Expressions can be negated by using the "!=" operator, as in "myParam!=myValue". "myParam"
+     * style expressions are also supported, with such parameters having to be present in the request (allowed to have any
+     * value). Finally, "!myParam" style expressions indicate that the specified parameter is not supposed to be present in
+     * the request.
+     *
+     * <p><b>Supported at the type level as well as at the method level.</b> When used at the type level, all
+     * method-level mappings inherit this parameter restriction (i.e. the type-level restriction gets checked before the
+     * handler method is even resolved).
+     */
+    String[] params() default {};
+
+    /**
+     * The headers of the mapped request, narrowing the primary mapping.
+     *
+     * <p>A sequence of "My-Header=myValue" style expressions, with a request only mapped if each such header is found
+     * to have the given value. Expressions can be negated by using the "!=" operator, as in "My-Header!=myValue".
+     * "My-Header" style expressions are also supported, with such headers having to be present in the request (allowed to
+     * have any value). Finally, "!My-Header" style expressions indicate that the specified header is <i>not</i> supposed to
+     * be present in the request.
+     *
+     * <p>Also supports media type wildcards (*), for headers such as Accept and Content-Type. For instance,
+     *
+     * <pre class="code"> &#064;RequestMapping(value = "/something", headers = "content-type=text/*") </pre>
+     *
+     * will match requests with a Content-Type of "text/html", "text/plain", etc.
+     *
+     * <p><b>Supported at the type level as well as at the method level.</b> When used at the type level, all
+     * method-level mappings inherit this header restriction (i .e. the type-level restriction gets checked before the
+     * handler method is even resolved).
+     */
+    String[] headers() default {};
+
+    /**
+     * The consumable media types of the mapped request, narrowing the primary mapping.
+     *
+     * <p>The format is a single media type or a sequence of media types, with a request only mapped if the {@code
+     * Content-Type} matches one of these media types. Examples:
+     *
+     * <pre class="code"> consumes = "text/plain" consumes = {"text/plain", "application/*"} </pre>
+     *
+     * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches all requests with a {@code
+     * Content-Type} other than "text/plain".
+     *
+     * <p><b>Supported at the type level as well as at the method level.</b> When used at the type level, all
+     * method-level mappings override this consumes restriction.
+     */
+    String[] consumes() default {};
+
+    /**
+     * The producible media types of the mapped request, narrowing the primary mapping.
+     *
+     * <p>The format is a single media type or a sequence of media types, with a request only mapped if the {@code
+     * Accept} matches one of these media types. Examples:
+     *
+     * <pre class="code"> produces = "text/plain" produces = {"text/plain", "application/*"} produces =
+     * "application/json; charset=UTF-8" </pre>
+     *
+     * <p>It affects the actual content type written, for example to produce a JSON response with UTF-8 encoding, {@code
+     * "application/json; charset=UTF-8"} should be used.
+     *
+     * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches all requests with a
+     * {@code Accept} other than "text/plain".
+     *
+     * <p><b>Supported at the type level as well as at the method level.</b> When used at the type level, all
+     * method-level mappings override this produces restriction.
+     */
+    String[] produces() default {};
+}

+ 45 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMethod.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+public enum RequestMethod {
+    GET("GET"),
+    HEAD("HEAD"),
+    POST("POST"),
+    PUT("PUT"),
+    PATCH("PATCH"),
+    DELETE("DELETE"),
+    OPTIONS("OPTIONS"),
+    TRACE("TRACE");
+
+    private String value;
+
+    RequestMethod(String value) {
+        this.value = value;
+    }
+
+    public String value() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}

+ 55 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestParam.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.SOURCE)
+public @interface RequestParam {
+
+    /**
+     * Alias for {@link #name()}.
+     */
+    String value() default "";
+
+    /**
+     * The name of the request parameter to bind to.
+     */
+    String name() default "";
+
+    /**
+     * Whether the parameter is required.
+     *
+     * <p>Defaults to {@code true}, leading to an exception being thrown if the parameter is missing in the request.
+     *
+     * <p>Alternatively, provide a {@link #defaultValue()}, which implicitly sets this flag to {@code false}.
+     */
+    boolean required() default true;
+
+    /**
+     * The default value to use as a fallback.
+     *
+     * <p>Supplying a default value implicitly sets {@link #required()} to {@code false}.
+     */
+    String defaultValue() default "";
+}

+ 34 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/Resolver.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/11.
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Resolver {
+
+    /**
+     * Group name.
+     */
+    String value() default "default";
+}

+ 28 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/ResponseBody.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.SOURCE)
+public @interface ResponseBody {}

+ 34 - 0
annotation/src/main/java/com/yanzhenjie/andserver/annotation/RestController.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/3.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface RestController {
+
+    /**
+     * Group name.
+     */
+    String value() default "default";
+}

+ 1 - 0
api/.gitignore

@@ -0,0 +1 @@
+/build

+ 35 - 0
api/build.gradle

@@ -0,0 +1,35 @@
+apply plugin: plugin.androidLibrary
+
+android {
+    compileSdkVersion androidBuild.compileSdkVersion
+
+    defaultConfig {
+        minSdkVersion androidBuild.libraryMinSdkVersion
+        targetSdkVersion androidBuild.libraryTargetSdkVersion
+        consumerProguardFiles 'proguard-rules.txt'
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    buildTypes {
+        release {
+            buildConfigField "java.lang.String", "PROJECT_VERSION", "\"${POM_VERSION}\""
+        }
+        debug {
+            buildConfigField "java.lang.String", "PROJECT_VERSION", "\"${POM_VERSION}\""
+        }
+    }
+}
+
+dependencies {
+    api project(':annotation')
+
+    implementation deps.apache.httpcore
+    implementation deps.apache.fileupload
+    compileOnly deps.android.annotation
+}
+
+apply from: publishScript

+ 3 - 0
api/gradle.properties

@@ -0,0 +1,3 @@
+POM_NAME=andserver-api
+POM_ARTIFACT_ID=api
+POM_PACKAGING=aar

+ 1 - 0
api/proguard-rules.txt

@@ -0,0 +1 @@
+-keep public class * implements com.yanzhenjie.andserver.register.OnRegister {*;}

+ 17 - 0
api/src/main/AndroidManifest.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2018 Zhenjie Yan.
+
+    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.
+-->
+<manifest package="com.yanzhenjie.andserver"/>

+ 83 - 0
api/src/main/java/com/yanzhenjie/andserver/AndServer.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.server.ProxyServer;
+import com.yanzhenjie.andserver.server.WebServer;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/9.
+ */
+public class AndServer {
+
+    public static final String TAG = "AndServer";
+    public static final String INFO = String.format("AndServer/%1$s", BuildConfig.PROJECT_VERSION);
+
+    /**
+     * Create a builder for the web server.
+     *
+     * @return {@link Server.Builder}.
+     */
+    @NonNull
+    public static Server.Builder webServer(@NonNull Context context) {
+        return WebServer.newBuilder(context, "default");
+    }
+
+    /**
+     * Create a builder for the web server.
+     *
+     * @param group group name.
+     *
+     * @return {@link Server.Builder}.
+     */
+    @NonNull
+    public static Server.Builder webServer(@NonNull Context context,
+        @NonNull String group) {
+        return WebServer.newBuilder(context, group);
+    }
+
+    /**
+     * Create a builder for the reverse proxy server.
+     *
+     * @return {@link Server.ProxyBuilder}.
+     */
+    @NonNull
+    public static Server.ProxyBuilder proxyServer() {
+        return ProxyServer.newBuilder();
+    }
+
+    /**
+     * @deprecated use {@link #webServer(Context)} instead.
+     */
+    @NonNull
+    @Deprecated
+    public static Server.Builder serverBuilder(@NonNull Context context) {
+        return webServer(context);
+    }
+
+    /**
+     * @deprecated use {@link #webServer(Context, String)} instead.
+     */
+    @NonNull
+    @Deprecated
+    public static Server.Builder serverBuilder(@NonNull Context context, @NonNull String group) {
+        return webServer(context, group);
+    }
+}

+ 87 - 0
api/src/main/java/com/yanzhenjie/andserver/ComponentRegister.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Zhenjie Yan
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+
+import com.yanzhenjie.andserver.register.OnRegister;
+import com.yanzhenjie.andserver.register.Register;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/23.
+ */
+public class ComponentRegister {
+
+    private static final String ANDSERVER_REGISTER_SUFFIX = ".andserver";
+    private static final String PROCESSOR_PACKAGE = ".andserver.processor.generator.";
+    private static final List<String> REGISTER_LIST = new ArrayList<>();
+
+    static {
+        REGISTER_LIST.add("AdapterRegister");
+        REGISTER_LIST.add("ConfigRegister");
+        REGISTER_LIST.add("ConverterRegister");
+        REGISTER_LIST.add("InterceptorRegister");
+        REGISTER_LIST.add("ResolverRegister");
+    }
+
+    private Context mContext;
+
+    public ComponentRegister(Context context) {
+        this.mContext = context;
+    }
+
+    public void register(Register register, String group)
+        throws InstantiationException, IllegalAccessException {
+        AssetManager manager = mContext.getAssets();
+        String[] pathList = null;
+        try {
+            pathList = manager.list("");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        if (pathList == null || pathList.length == 0) {
+            return;
+        }
+
+        for (String path: pathList) {
+            if (path.endsWith(ANDSERVER_REGISTER_SUFFIX)) {
+                String packageName = path.substring(0, path.lastIndexOf(ANDSERVER_REGISTER_SUFFIX));
+                for (String clazz: REGISTER_LIST) {
+                    String className = String.format("%s%s%s", packageName, PROCESSOR_PACKAGE, clazz);
+                    registerClass(register, group, className);
+                }
+            }
+        }
+    }
+
+    private void registerClass(Register register, String group, String className)
+        throws InstantiationException, IllegalAccessException {
+        try {
+            Class<?> clazz = Class.forName(className);
+            if (OnRegister.class.isAssignableFrom(clazz)) {
+                OnRegister load = (OnRegister) clazz.newInstance();
+                load.onRegister(mContext, group, register);
+            }
+        } catch (ClassNotFoundException ignored) {
+        }
+    }
+}

+ 276 - 0
api/src/main/java/com/yanzhenjie/andserver/DispatcherHandler.java

@@ -0,0 +1,276 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.error.ServerInternalException;
+import com.yanzhenjie.andserver.framework.ExceptionResolver;
+import com.yanzhenjie.andserver.framework.HandlerInterceptor;
+import com.yanzhenjie.andserver.framework.MessageConverter;
+import com.yanzhenjie.andserver.framework.ModifiedInterceptor;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.framework.config.Multipart;
+import com.yanzhenjie.andserver.framework.handler.HandlerAdapter;
+import com.yanzhenjie.andserver.framework.handler.RequestHandler;
+import com.yanzhenjie.andserver.framework.view.View;
+import com.yanzhenjie.andserver.framework.view.ViewResolver;
+import com.yanzhenjie.andserver.http.HttpContext;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.RequestDispatcher;
+import com.yanzhenjie.andserver.http.RequestWrapper;
+import com.yanzhenjie.andserver.http.StandardContext;
+import com.yanzhenjie.andserver.http.StandardRequest;
+import com.yanzhenjie.andserver.http.StandardResponse;
+import com.yanzhenjie.andserver.http.StatusCode;
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+import com.yanzhenjie.andserver.http.multipart.MultipartRequest;
+import com.yanzhenjie.andserver.http.multipart.MultipartResolver;
+import com.yanzhenjie.andserver.http.multipart.StandardMultipartResolver;
+import com.yanzhenjie.andserver.http.session.Session;
+import com.yanzhenjie.andserver.http.session.SessionManager;
+import com.yanzhenjie.andserver.http.session.StandardSessionManager;
+import com.yanzhenjie.andserver.register.Register;
+import com.yanzhenjie.andserver.util.Assert;
+
+import org.apache.httpcore.protocol.HttpRequestHandler;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/8.
+ */
+public class DispatcherHandler implements HttpRequestHandler, Register {
+
+    private final Context mContext;
+
+    private SessionManager mSessionManager;
+    private MessageConverter mConverter;
+    private ViewResolver mViewResolver;
+    private ExceptionResolver mResolver;
+    private Multipart mMultipart;
+
+    private List<HandlerAdapter> mAdapterList = new LinkedList<>();
+    private List<HandlerInterceptor> mInterceptorList = new LinkedList<>();
+
+    public DispatcherHandler(Context context) {
+        this.mContext = context;
+        this.mSessionManager = new StandardSessionManager(context);
+        this.mViewResolver = new ViewResolver();
+        this.mResolver = new ExceptionResolver.ResolverWrapper(ExceptionResolver.DEFAULT);
+
+        this.mInterceptorList.add(new ModifiedInterceptor());
+    }
+
+    @Override
+    public void addAdapter(@NonNull HandlerAdapter adapter) {
+        Assert.notNull(adapter, "The adapter cannot be null.");
+
+        if (!mAdapterList.contains(adapter)) {
+            mAdapterList.add(adapter);
+        }
+    }
+
+    @Override
+    public void addInterceptor(@NonNull HandlerInterceptor interceptor) {
+        Assert.notNull(interceptor, "The interceptor cannot be null.");
+
+        if (!mInterceptorList.contains(interceptor)) {
+            mInterceptorList.add(interceptor);
+        }
+    }
+
+    @Override
+    public void setConverter(MessageConverter converter) {
+        this.mConverter = converter;
+        this.mViewResolver = new ViewResolver(converter);
+    }
+
+    @Override
+    public void setResolver(@NonNull ExceptionResolver resolver) {
+        Assert.notNull(resolver, "The exceptionResolver cannot be null.");
+
+        this.mResolver = new ExceptionResolver.ResolverWrapper(resolver);
+    }
+
+    @Override
+    public void setMultipart(Multipart multipart) {
+        this.mMultipart = multipart;
+    }
+
+    @Override
+    public void handle(org.apache.httpcore.HttpRequest req, org.apache.httpcore.HttpResponse res,
+                       org.apache.httpcore.protocol.HttpContext con) {
+        HttpRequest request = new StandardRequest(req, new StandardContext(con), this, mSessionManager);
+        HttpResponse response = new StandardResponse(res);
+        handle(request, response);
+    }
+
+    private void handle(HttpRequest request, HttpResponse response) {
+        MultipartResolver multipartResolver = new StandardMultipartResolver();
+        try {
+            if (multipartResolver.isMultipart(request)) {
+                configMultipart(multipartResolver);
+                request = multipartResolver.resolveMultipart(request);
+            }
+
+            // Determine adapter for the current request.
+            HandlerAdapter ha = getHandlerAdapter(request);
+            if (ha == null) {
+                throw new NotFoundException(request.getPath());
+            }
+
+            // Determine handler for the current request.
+            RequestHandler handler = ha.getHandler(request);
+            if (handler == null) {
+                throw new NotFoundException(request.getPath());
+            }
+
+            // Pre processor, e.g. interceptor.
+            if (preHandle(request, response, handler)) {
+                return;
+            }
+
+            // Actually invoke the handler.
+            request.setAttribute(HttpContext.ANDROID_CONTEXT, mContext);
+            request.setAttribute(HttpContext.HTTP_MESSAGE_CONVERTER, mConverter);
+            View view = handler.handle(request, response);
+            mViewResolver.resolve(view, request, response);
+            processSession(request, response);
+        } catch (Throwable err) {
+            try {
+                mResolver.onResolve(request, response, err);
+            } catch (Exception e) {
+                e = new ServerInternalException(e);
+                response.setStatus(StatusCode.SC_INTERNAL_SERVER_ERROR);
+                response.setBody(new StringBody(e.getMessage()));
+            }
+            processSession(request, response);
+        } finally {
+            if (request instanceof MultipartRequest) {
+                multipartResolver.cleanupMultipart((MultipartRequest) request);
+            }
+        }
+    }
+
+    private void configMultipart(MultipartResolver multipartResolver) {
+        if (mMultipart != null) {
+            long allFileMaxSize = mMultipart.getAllFileMaxSize();
+            if (allFileMaxSize == -1 || allFileMaxSize > 0) {
+                multipartResolver.setAllFileMaxSize(allFileMaxSize);
+            }
+
+            long fileMaxSize = mMultipart.getFileMaxSize();
+            if (fileMaxSize == -1 || fileMaxSize > 0) {
+                multipartResolver.setFileMaxSize(fileMaxSize);
+            }
+
+            int maxInMemorySize = mMultipart.getMaxInMemorySize();
+            if (maxInMemorySize > 0) {
+                multipartResolver.setMaxInMemorySize(maxInMemorySize);
+            }
+
+            File uploadTempDir = mMultipart.getUploadTempDir();
+            if (uploadTempDir != null) {
+                multipartResolver.setUploadTempDir(uploadTempDir);
+            }
+        }
+    }
+
+    /**
+     * Return the {@link RequestHandler} for this request.
+     *
+     * @param request current HTTP request.
+     *
+     * @return the {@link RequestHandler}, or {@code null} if no handler could be found.
+     */
+    private HandlerAdapter getHandlerAdapter(HttpRequest request) {
+        for (HandlerAdapter ha: mAdapterList) {
+            if (ha.intercept(request)) {
+                return ha;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Intercept the execution of a handler.
+     *
+     * @param request current request.
+     * @param response current response.
+     * @param handler the corresponding handler of the current request.
+     *
+     * @return true if the interceptor has processed the request and responded.
+     */
+    private boolean preHandle(HttpRequest request, HttpResponse response, RequestHandler handler) throws Exception {
+        for (HandlerInterceptor interceptor: mInterceptorList) {
+            if (interceptor.onIntercept(request, response, handler)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Nullable
+    public RequestDispatcher getRequestDispatcher(final HttpRequest request, final String path) {
+        HttpRequest copyRequest = request;
+        while (copyRequest instanceof RequestWrapper) {
+            RequestWrapper wrapper = (RequestWrapper) request;
+            copyRequest = wrapper.getRequest();
+        }
+
+        StandardRequest newRequest = (StandardRequest) copyRequest;
+        newRequest.setPath(path);
+
+        HandlerAdapter ha = getHandlerAdapter(copyRequest);
+        if (ha == null) {
+            throw new NotFoundException(request.getPath());
+        }
+
+        return new RequestDispatcher() {
+            @Override
+            public void forward(@NonNull HttpRequest request, @NonNull HttpResponse response) {
+                handle(request, response);
+            }
+        };
+    }
+
+    private void processSession(HttpRequest request, HttpResponse response) {
+        Object objSession = request.getAttribute(HttpContext.REQUEST_CREATED_SESSION);
+        if (objSession instanceof Session) {
+            Session session = (Session) objSession;
+            try {
+                mSessionManager.add(session);
+            } catch (IOException e) {
+                Log.e(AndServer.TAG, "Session persistence failed.", e);
+            }
+
+            Cookie cookie = new Cookie(HttpRequest.SESSION_NAME, session.getId());
+            cookie.setPath("/");
+            cookie.setHttpOnly(true);
+            response.addCookie(cookie);
+        }
+    }
+}

+ 181 - 0
api/src/main/java/com/yanzhenjie/andserver/ProxyHandler.java

@@ -0,0 +1,181 @@
+/*
+ * Copyright 2020 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver;
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.server.ProxyServer;
+import com.yanzhenjie.andserver.util.IOUtils;
+
+import org.apache.httpcore.HttpException;
+import org.apache.httpcore.HttpHeaders;
+import org.apache.httpcore.HttpHost;
+import org.apache.httpcore.HttpRequest;
+import org.apache.httpcore.HttpResponse;
+import org.apache.httpcore.entity.StringEntity;
+import org.apache.httpcore.impl.DefaultBHttpClientConnection;
+import org.apache.httpcore.impl.DefaultConnectionReuseStrategy;
+import org.apache.httpcore.protocol.HttpContext;
+import org.apache.httpcore.protocol.HttpCoreContext;
+import org.apache.httpcore.protocol.HttpProcessor;
+import org.apache.httpcore.protocol.HttpRequestExecutor;
+import org.apache.httpcore.protocol.HttpRequestHandler;
+import org.apache.httpcore.protocol.ImmutableHttpProcessor;
+import org.apache.httpcore.protocol.RequestConnControl;
+import org.apache.httpcore.protocol.RequestContent;
+import org.apache.httpcore.protocol.RequestExpectContinue;
+import org.apache.httpcore.protocol.RequestTargetHost;
+import org.apache.httpcore.protocol.RequestUserAgent;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import static com.yanzhenjie.andserver.server.ProxyServer.PROXY_CONN_CLIENT;
+
+/**
+ * Created by Zhenjie Yan on 3/7/20.
+ */
+public class ProxyHandler implements HttpRequestHandler {
+
+    private static final int BUFFER = 8 * 1024;
+
+    private final static Set<String> HOP_BY_HOP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+        HttpHeaders.HOST,
+        HttpHeaders.CONTENT_LENGTH,
+        HttpHeaders.TRANSFER_ENCODING,
+        HttpHeaders.CONNECTION,
+        HttpHeaders.PROXY_AUTHENTICATE,
+        HttpHeaders.TE,
+        HttpHeaders.TRAILER,
+        HttpHeaders.UPGRADE
+    )));
+
+    private final Map<String, HttpHost> mHostList;
+
+    private final SSLSocketFactory mSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
+
+    private final HttpRequestExecutor mHttpExecutor = new HttpRequestExecutor();
+    private final HttpProcessor mRequestProcessor = new ImmutableHttpProcessor(
+        new RequestContent(),
+        new RequestTargetHost(),
+        new RequestConnControl(),
+        new RequestUserAgent(AndServer.INFO),
+        new RequestExpectContinue(true));
+
+    public ProxyHandler(Map<String, HttpHost> hostList) {
+        this.mHostList = hostList;
+    }
+
+    @Override
+    public void handle(HttpRequest request, HttpResponse response, HttpContext context)
+        throws HttpException, IOException {
+        String hostHeader = request.getFirstHeader(HttpHeaders.HOST).getValue();
+        String hostName = HttpHost.create(hostHeader).getHostName();
+        HttpHost host = mHostList.get(hostName.toLowerCase(Locale.ROOT));
+        if (host == null) {
+            NotFoundException e = new NotFoundException(request.getRequestLine().getUri());
+            response.setStatusCode(e.getStatusCode());
+            response.setEntity(new StringEntity(e.getMessage()));
+            return;
+        }
+
+        // Remove hop-by-hop headers.
+        for (String name: HOP_BY_HOP) {
+            request.removeHeaders(name);
+        }
+
+        DefaultBHttpClientConnection conn = (DefaultBHttpClientConnection) context.getAttribute(PROXY_CONN_CLIENT);
+        if (!conn.isOpen() || conn.isStale()) {
+            Socket socket = createSocket(host);
+            conn.bind(socket);
+        }
+
+        context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
+        context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, host);
+
+        mHttpExecutor.preProcess(request, mRequestProcessor, context);
+        HttpResponse outResponse = mHttpExecutor.execute(request, conn, context);
+        mHttpExecutor.postProcess(response, mRequestProcessor, context);
+
+        // Remove hop-by-hop headers.
+        for (String name: HOP_BY_HOP) {
+            outResponse.removeHeaders(name);
+        }
+
+        response.setStatusLine(outResponse.getStatusLine());
+        response.setHeaders(outResponse.getAllHeaders());
+        response.setEntity(outResponse.getEntity());
+
+        boolean keepAlive = DefaultConnectionReuseStrategy.INSTANCE.keepAlive(response, context);
+        context.setAttribute(ProxyServer.PROXY_CONN_ALIVE, keepAlive);
+    }
+
+    private Socket createSocket(HttpHost host) throws IOException {
+        Socket socket = new Socket();
+        socket.setSoTimeout(60 * 1000);
+        socket.setReuseAddress(true);
+        socket.setTcpNoDelay(true);
+        socket.setKeepAlive(true);
+        socket.setReceiveBufferSize(BUFFER);
+        socket.setSendBufferSize(BUFFER);
+        socket.setSoLinger(true, 0);
+
+        String scheme = host.getSchemeName();
+        String hostName = host.getHostName();
+        int port = host.getPort();
+
+        InetSocketAddress address = resolveAddress(scheme, hostName, port);
+        socket.connect(address, 10 * 1000);
+
+        if ("https".equalsIgnoreCase(scheme)) {
+            SSLSocket sslSocket = (SSLSocket) mSocketFactory.createSocket(socket, hostName, port, true);
+            try {
+                sslSocket.startHandshake();
+                final SSLSession session = sslSocket.getSession();
+                if (session == null) {
+                    throw new SSLHandshakeException("SSL session not available.");
+                }
+            } catch (final IOException ex) {
+                IOUtils.closeQuietly(sslSocket);
+                throw ex;
+            }
+            return sslSocket;
+        }
+        return socket;
+    }
+
+    private InetSocketAddress resolveAddress(String scheme, String hostName, int port) {
+        if (port < 0) {
+            if ("http".equalsIgnoreCase(scheme)) {
+                port = 80;
+            } else if ("https".equalsIgnoreCase(scheme)) {
+                port = 443;
+            }
+        }
+        return new InetSocketAddress(hostName, port);
+    }
+}

+ 29 - 0
api/src/main/java/com/yanzhenjie/andserver/SSLSocketInitializer.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver;
+
+import androidx.annotation.NonNull;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocket;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public interface SSLSocketInitializer {
+
+    void onCreated(@NonNull SSLServerSocket socket) throws SSLException;
+}

+ 179 - 0
api/src/main/java/com/yanzhenjie/andserver/Server.java

@@ -0,0 +1,179 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public interface Server {
+
+    /**
+     * Server running status.
+     *
+     * @return return true, not return false.
+     */
+    boolean isRunning();
+
+    /**
+     * Start the server.
+     */
+    void startup();
+
+    /**
+     * Quit the server.
+     */
+    void shutdown();
+
+    /**
+     * Get the local address of this server socket.
+     *
+     * @return {@link InetAddress}.
+     *
+     * @throws IllegalStateException if the server is not started, an IllegalStateException is thrown.
+     * @see ServerSocket#getInetAddress()
+     */
+    InetAddress getInetAddress();
+
+    /**
+     * Returns the port number on which this socket is listening.
+     *
+     * @return the local port number to which this socket is bound or -1 if the socket is not bound yet.
+     *
+     * @throws IllegalStateException if the server is not started, an IllegalStateException is thrown.
+     * @see Socket#getLocalPort()
+     */
+    int getPort();
+
+    interface Builder<T extends Builder, S extends Server> {
+
+        /**
+         * Specified server need to monitor the ip address.
+         */
+        T inetAddress(InetAddress inetAddress);
+
+        /**
+         * Specify the port on which the server listens.
+         */
+        T port(int port);
+
+        /**
+         * Connection and response timeout.
+         */
+        T timeout(int timeout, TimeUnit timeUnit);
+
+        /**
+         * Assigns {@link ServerSocketFactory} instance.
+         */
+        T serverSocketFactory(ServerSocketFactory factory);
+
+        /**
+         * Assigns {@link SSLContext} instance.
+         */
+        T sslContext(SSLContext sslContext);
+
+        /**
+         * Assigns {@link SSLSocketInitializer} instance.
+         */
+        T sslSocketInitializer(SSLSocketInitializer initializer);
+
+        /**
+         * Set the server listener.
+         */
+        T listener(Server.ServerListener listener);
+
+        /**
+         * Create a server.
+         */
+        S build();
+    }
+
+    interface ProxyBuilder<T extends ProxyBuilder, S extends Server> {
+
+        /**
+         * Add host address to proxy.
+         *
+         * @param hostName such as: {@code www.example.com}, {@code api.example.com}, {@code 192.168.1.111}.
+         * @param proxyHost such as: {@code http://127.0.0.1:8080}, {@code http://localhost:8181}
+         */
+        T addProxy(String hostName, String proxyHost);
+
+        /**
+         * Specified server need to monitor the ip address.
+         */
+        T inetAddress(InetAddress inetAddress);
+
+        /**
+         * Specify the port on which the server listens.
+         */
+        T port(int port);
+
+        /**
+         * Connection and response timeout.
+         */
+        T timeout(int timeout, TimeUnit timeUnit);
+
+        /**
+         * Assigns {@link ServerSocketFactory} instance.
+         */
+        T serverSocketFactory(ServerSocketFactory factory);
+
+        /**
+         * Assigns {@link SSLContext} instance.
+         */
+        T sslContext(SSLContext sslContext);
+
+        /**
+         * Assigns {@link SSLSocketInitializer} instance.
+         */
+        T sslSocketInitializer(SSLSocketInitializer initializer);
+
+        /**
+         * Set the server listener.
+         */
+        T listener(Server.ServerListener listener);
+
+        /**
+         * Create a server.
+         */
+        S build();
+    }
+
+    interface ServerListener {
+
+        /**
+         * When the server is started.
+         */
+        void onStarted();
+
+        /**
+         * When the server stops running.
+         */
+        void onStopped();
+
+        /**
+         * An error occurred while starting the server.
+         */
+        void onException(Exception e);
+    }
+}

+ 37 - 0
api/src/main/java/com/yanzhenjie/andserver/error/BasicException.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+/**
+ * Created by Zhenjie Yan on 11/22/20.
+ *
+ * @deprecated use {@link HttpException} instead.
+ */
+@Deprecated
+public class BasicException extends HttpException {
+
+    public BasicException(int statusCode, String message) {
+        super(statusCode, message);
+    }
+
+    public BasicException(int statusCode, String message, Throwable cause) {
+        super(statusCode, message, cause);
+    }
+
+    public BasicException(int statusCode, Throwable cause) {
+        super(statusCode, cause);
+    }
+}

+ 35 - 0
api/src/main/java/com/yanzhenjie/andserver/error/BodyMissingException.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public class BodyMissingException extends HttpException {
+
+    private static final String MESSAGE = "RequestBody is missing.";
+
+    public BodyMissingException() {
+        super(StatusCode.SC_BAD_REQUEST, MESSAGE);
+    }
+
+    public BodyMissingException(Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, MESSAGE, cause);
+    }
+
+}

+ 34 - 0
api/src/main/java/com/yanzhenjie/andserver/error/ContentNotAcceptableException.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/8.
+ */
+public class ContentNotAcceptableException extends HttpException {
+
+    private static final String MESSAGE = "Could not find acceptable representation.";
+
+    public ContentNotAcceptableException() {
+        super(StatusCode.SC_NOT_ACCEPTABLE, MESSAGE);
+    }
+
+    public ContentNotAcceptableException(String message, Throwable cause) {
+        super(StatusCode.SC_NOT_ACCEPTABLE, message, cause);
+    }
+}

+ 35 - 0
api/src/main/java/com/yanzhenjie/andserver/error/ContentNotSupportedException.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.util.MediaType;
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/8.
+ */
+public class ContentNotSupportedException extends HttpException {
+
+    private static final String MESSAGE = "The content type [%s] is not supported.";
+
+    public ContentNotSupportedException(MediaType mediaType) {
+        super(StatusCode.SC_UNSUPPORTED_MEDIA_TYPE, String.format(MESSAGE, mediaType));
+    }
+
+    public ContentNotSupportedException(MediaType mediaType, Throwable cause) {
+        super(StatusCode.SC_UNSUPPORTED_MEDIA_TYPE, String.format(MESSAGE, mediaType), cause);
+    }
+}

+ 38 - 0
api/src/main/java/com/yanzhenjie/andserver/error/CookieMissingException.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public class CookieMissingException extends HttpException {
+
+    private static final String MESSAGE = "Missing cookie [%s] for method parameter.";
+
+    public CookieMissingException(String name) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name));
+    }
+
+    public CookieMissingException(String name, Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name), cause);
+    }
+
+    public CookieMissingException(Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, ""), cause);
+    }
+}

+ 38 - 0
api/src/main/java/com/yanzhenjie/andserver/error/HeaderMissingException.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public class HeaderMissingException extends HttpException {
+
+    private static final String MESSAGE = "Missing header [%s] for method parameter.";
+
+    public HeaderMissingException(String name) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name));
+    }
+
+    public HeaderMissingException(String name, Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name), cause);
+    }
+
+    public HeaderMissingException(Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, ""), cause);
+    }
+}

+ 36 - 0
api/src/main/java/com/yanzhenjie/andserver/error/HeaderValidateException.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public class HeaderValidateException extends HttpException {
+
+    public HeaderValidateException(String message) {
+        super(StatusCode.SC_FORBIDDEN, message);
+    }
+
+    public HeaderValidateException(String message, Throwable cause) {
+        super(StatusCode.SC_FORBIDDEN, message, cause);
+    }
+
+    public HeaderValidateException(Throwable cause) {
+        super(StatusCode.SC_FORBIDDEN, cause);
+    }
+}

+ 46 - 0
api/src/main/java/com/yanzhenjie/andserver/error/HttpException.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/19.
+ */
+public class HttpException extends RuntimeException {
+
+    /**
+     * Status code.
+     */
+    private int mStatusCode;
+
+    public HttpException(int statusCode, String message) {
+        super(message);
+        this.mStatusCode = statusCode;
+    }
+
+    public HttpException(int statusCode, String message, Throwable cause) {
+        super(message, cause);
+        this.mStatusCode = statusCode;
+    }
+
+    public HttpException(int statusCode, Throwable cause) {
+        super(cause);
+        this.mStatusCode = statusCode;
+    }
+
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+}

+ 51 - 0
api/src/main/java/com/yanzhenjie/andserver/error/InvalidMediaTypeException.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/10.
+ */
+public class InvalidMediaTypeException extends IllegalArgumentException {
+
+    private String mMediaType;
+
+    /**
+     * Create a new InvalidMediaTypeException for the given media type.
+     *
+     * @param mediaType the offending media type.
+     * @param message a detail message indicating the invalid part.
+     */
+    public InvalidMediaTypeException(String mediaType, String message) {
+        super("Invalid media type \"" + mediaType + "\": " + message);
+        this.mMediaType = mediaType;
+    }
+
+    /**
+     * Constructor that allows wrapping {@link InvalidMimeTypeException}.
+     */
+    public InvalidMediaTypeException(InvalidMimeTypeException ex) {
+        super(ex.getMessage(), ex);
+        this.mMediaType = ex.getMimeType();
+    }
+
+    /**
+     * Return the offending media type.
+     */
+    public String getMediaType() {
+        return this.mMediaType;
+    }
+
+}

+ 42 - 0
api/src/main/java/com/yanzhenjie/andserver/error/InvalidMimeTypeException.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/10.
+ */
+public class InvalidMimeTypeException extends IllegalArgumentException {
+
+    private final String mMimeType;
+
+    /**
+     * Create a new InvalidContentTypeException for the given content type.
+     *
+     * @param mimeType the offending media type.
+     * @param message a detail message indicating the invalid part.
+     */
+    public InvalidMimeTypeException(String mimeType, String message) {
+        super("Invalid mime type \"" + mimeType + "\": " + message);
+        this.mMimeType = mimeType;
+    }
+
+    /**
+     * Return the offending content type.
+     */
+    public String getMimeType() {
+        return this.mMimeType;
+    }
+}

+ 53 - 0
api/src/main/java/com/yanzhenjie/andserver/error/MaxUploadSizeExceededException.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/9.
+ */
+public class MaxUploadSizeExceededException extends HttpException {
+
+    private final long mMaxSize;
+
+    /**
+     * Constructor for MaxUploadSizeExceededException.
+     *
+     * @param maxUploadSize the maximum upload size allowed.
+     */
+    public MaxUploadSizeExceededException(long maxUploadSize) {
+        this(maxUploadSize, null);
+    }
+
+    /**
+     * Constructor for MaxUploadSizeExceededException.
+     *
+     * @param maxSize the maximum upload size allowed.
+     * @param ex root cause from multipart parsing API in use.
+     */
+    public MaxUploadSizeExceededException(long maxSize, Throwable ex) {
+        super(StatusCode.SC_REQUEST_ENTITY_TOO_LARGE, "Maximum upload size of " + maxSize + " bytes exceeded", ex);
+        this.mMaxSize = maxSize;
+    }
+
+    /**
+     * Return the maximum upload size allowed.
+     */
+    public long getMaxSize() {
+        return this.mMaxSize;
+    }
+}

+ 47 - 0
api/src/main/java/com/yanzhenjie/andserver/error/MethodNotSupportException.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.HttpMethod;
+import com.yanzhenjie.andserver.http.StatusCode;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/19.
+ */
+public class MethodNotSupportException extends HttpException {
+
+    private static final String MESSAGE = "The request method [%s] is not supported.";
+
+    private List<HttpMethod> mMethods;
+
+    public MethodNotSupportException(HttpMethod method) {
+        super(StatusCode.SC_METHOD_NOT_ALLOWED, String.format(MESSAGE, method.value()));
+    }
+
+    public MethodNotSupportException(HttpMethod method, Throwable cause) {
+        super(StatusCode.SC_METHOD_NOT_ALLOWED, String.format(MESSAGE, method.value()), cause);
+    }
+
+    public List<HttpMethod> getMethods() {
+        return mMethods;
+    }
+
+    public void setMethods(List<HttpMethod> methods) {
+        mMethods = methods;
+    }
+}

+ 43 - 0
api/src/main/java/com/yanzhenjie/andserver/error/MultipartException.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/9.
+ */
+public class MultipartException extends HttpException {
+
+    /**
+     * Constructor for MultipartException.
+     *
+     * @param msg the detail message.
+     */
+    public MultipartException(String msg) {
+        super(StatusCode.SC_BAD_REQUEST, msg);
+    }
+
+    /**
+     * Constructor for MultipartException.
+     *
+     * @param msg the detail message
+     * @param cause the root cause from the multipart parsing API in use
+     */
+    public MultipartException(String msg, Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, msg, cause);
+    }
+}

+ 42 - 0
api/src/main/java/com/yanzhenjie/andserver/error/NotFoundException.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/19.
+ */
+public class NotFoundException extends HttpException {
+
+    private static final String MESSAGE = "The resource [%s] is not found.";
+
+    public NotFoundException() {
+        super(StatusCode.SC_NOT_FOUND, String.format(MESSAGE, ""));
+    }
+
+    public NotFoundException(String path) {
+        super(StatusCode.SC_NOT_FOUND, String.format(MESSAGE, path));
+    }
+
+    public NotFoundException(String path, Throwable cause) {
+        super(StatusCode.SC_NOT_FOUND, String.format(MESSAGE, path), cause);
+    }
+
+    public NotFoundException(Throwable cause) {
+        super(StatusCode.SC_NOT_FOUND, String.format(MESSAGE, ""), cause);
+    }
+}

+ 38 - 0
api/src/main/java/com/yanzhenjie/andserver/error/ParamMissingException.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public class ParamMissingException extends HttpException {
+
+    private static final String MESSAGE = "Missing param [%s] for method parameter.";
+
+    public ParamMissingException(String name) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name));
+    }
+
+    public ParamMissingException(String name, Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name), cause);
+    }
+
+    public ParamMissingException(Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, ""), cause);
+    }
+}

+ 36 - 0
api/src/main/java/com/yanzhenjie/andserver/error/ParamValidateException.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/8.
+ */
+public class ParamValidateException extends HttpException {
+
+    public ParamValidateException(String message) {
+        super(StatusCode.SC_FORBIDDEN, message);
+    }
+
+    public ParamValidateException(String message, Throwable cause) {
+        super(StatusCode.SC_FORBIDDEN, message, cause);
+    }
+
+    public ParamValidateException(Throwable cause) {
+        super(StatusCode.SC_FORBIDDEN, cause);
+    }
+}

+ 38 - 0
api/src/main/java/com/yanzhenjie/andserver/error/PathMissingException.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public class PathMissingException extends HttpException {
+
+    private static final String MESSAGE = "Missing param [%s] for path parameter.";
+
+    public PathMissingException(String name) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name));
+    }
+
+    public PathMissingException(String name, Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, name), cause);
+    }
+
+    public PathMissingException(Throwable cause) {
+        super(StatusCode.SC_BAD_REQUEST, String.format(MESSAGE, ""), cause);
+    }
+}

+ 38 - 0
api/src/main/java/com/yanzhenjie/andserver/error/ServerInternalException.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.error;
+
+import com.yanzhenjie.andserver.http.StatusCode;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/4.
+ */
+public class ServerInternalException extends HttpException {
+
+    private static final String MESSAGE = "Server internal error";
+
+    public ServerInternalException(String subMessage) {
+        super(StatusCode.SC_INTERNAL_SERVER_ERROR, String.format("%s, %s.", MESSAGE, subMessage));
+    }
+
+    public ServerInternalException(String subMessage, Throwable cause) {
+        super(StatusCode.SC_INTERNAL_SERVER_ERROR, String.format("%s, %s.", MESSAGE, subMessage), cause);
+    }
+
+    public ServerInternalException(Throwable cause) {
+        super(StatusCode.SC_INTERNAL_SERVER_ERROR, String.format("%s.", MESSAGE), cause);
+    }
+}

+ 33 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/ETag.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.http.HttpRequest;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/31.
+ */
+public interface ETag {
+
+    /**
+     * Get the {@code ETag} requesting the specified resource.
+     *
+     * <p>Can simply return {@code null} if there's no support.
+     */
+    String getETag(@NonNull HttpRequest request) throws Throwable;
+}

+ 79 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/ExceptionResolver.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.error.HttpException;
+import com.yanzhenjie.andserver.error.MethodNotSupportException;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.http.HttpHeaders;
+import com.yanzhenjie.andserver.http.HttpMethod;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.StatusCode;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/8.
+ */
+public interface ExceptionResolver {
+
+    class ResolverWrapper implements ExceptionResolver {
+
+        private final ExceptionResolver mResolver;
+
+        public ResolverWrapper(ExceptionResolver resolver) {
+            this.mResolver = resolver;
+        }
+
+        @Override
+        public void onResolve(@NonNull HttpRequest request, @NonNull HttpResponse response, @NonNull Throwable e) {
+            if (e instanceof MethodNotSupportException) {
+                List<HttpMethod> methods = ((MethodNotSupportException) e).getMethods();
+                if (methods != null && methods.size() > 0) {
+                    response.setHeader(HttpHeaders.ALLOW, TextUtils.join(", ", methods));
+                }
+            }
+            mResolver.onResolve(request, response, e);
+        }
+    }
+
+    ExceptionResolver DEFAULT = new ExceptionResolver() {
+        @Override
+        public void onResolve(@NonNull HttpRequest request, @NonNull HttpResponse response, @NonNull Throwable e) {
+            if (e instanceof HttpException) {
+                HttpException ex = (HttpException) e;
+                response.setStatus(ex.getStatusCode());
+            } else {
+                response.setStatus(StatusCode.SC_INTERNAL_SERVER_ERROR);
+            }
+            response.setBody(new StringBody(e.getMessage()));
+        }
+    };
+
+    /**
+     * Resolve exceptions that occur in the program, replacing the default output information for the exception.
+     *
+     * @param request current request.
+     * @param response current response.
+     * @param e an exception occurred in the program.
+     */
+    void onResolve(@NonNull HttpRequest request, @NonNull HttpResponse response, @NonNull Throwable e);
+}

+ 40 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/HandlerInterceptor.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.framework.handler.RequestHandler;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/8.
+ */
+public interface HandlerInterceptor {
+
+    /**
+     * Intercept the execution of a handler.
+     *
+     * @param request current request.
+     * @param response current response.
+     * @param handler the corresponding handler of the current request.
+     *
+     * @return true if the interceptor has processed the request and responded.
+     */
+    boolean onIntercept(@NonNull HttpRequest request, @NonNull HttpResponse response, @NonNull RequestHandler handler)
+        throws Exception;
+}

+ 38 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/LastModified.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.http.HttpRequest;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/29.
+ */
+public interface LastModified {
+
+    /**
+     * The return value will be sent to the HTTP client as {@code Last-Modified} header, and compared with {@code
+     * If-Modified-Since} headers that the client sends back. The content will only get regenerated if there has been a
+     * modification.
+     *
+     * @param request current request
+     *
+     * @return the time the underlying resource was last modified, or -1 meaning that the content must always be
+     *     regenerated.
+     */
+    long getLastModified(@NonNull HttpRequest request) throws Throwable;
+}

+ 56 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/MessageConverter.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.framework.view.ViewResolver;
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/6.
+ */
+public interface MessageConverter {
+
+    /**
+     * Convert a specific output to the response body. Some of the return values of handlers that cannot be recognized
+     * by
+     * {@link ViewResolver} require a message converter to be converted to a response body.
+     *
+     * @param output output of handle.
+     * @param mediaType the content media type specified by the handler.
+     */
+    ResponseBody convert(@Nullable Object output, @Nullable MediaType mediaType);
+
+    /**
+     * Convert RequestBody to a object.
+     *
+     * @param stream {@link InputStream}.
+     * @param mediaType he content media type.
+     * @param type type of object.
+     * @param <T> type of object.
+     *
+     * @return object.
+     */
+    @Nullable
+    <T> T convert(@NonNull InputStream stream, @Nullable MediaType mediaType, Type type) throws IOException;
+}

+ 56 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/ModifiedInterceptor.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.AndServer;
+import com.yanzhenjie.andserver.framework.handler.RequestHandler;
+import com.yanzhenjie.andserver.http.HttpMethod;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.Modified;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/14.
+ */
+public class ModifiedInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean onIntercept(@NonNull HttpRequest request, @NonNull HttpResponse response,
+        @NonNull RequestHandler handler) {
+        // Process cache header, if supported by the handler.
+        HttpMethod method = request.getMethod();
+        if (method == HttpMethod.GET || method == HttpMethod.HEAD) {
+            String eTag = null;
+            try {
+                eTag = handler.getETag(request);
+            } catch (Throwable e) {
+                Log.w(AndServer.TAG, e);
+            }
+            long lastModified = -1;
+            try {
+                lastModified = handler.getLastModified(request);
+            } catch (Throwable e) {
+                Log.w(AndServer.TAG, e);
+            }
+            return new Modified(request, response).process(eTag, lastModified);
+        }
+        return false;
+    }
+}

+ 72 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/body/FileBody.java

@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.body;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.IOUtils;
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/6.
+ */
+public class FileBody implements ResponseBody {
+
+    private File mBody;
+
+    public FileBody(File body) {
+        if (body == null) {
+            throw new IllegalArgumentException("The file cannot be null.");
+        }
+        this.mBody = body;
+    }
+
+    @Override
+    public boolean isRepeatable() {
+        return true;
+    }
+
+    @Override
+    public boolean isChunked() {
+        return false;
+    }
+
+    @Override
+    public long contentLength() {
+        return mBody.length();
+    }
+
+    @Nullable
+    @Override
+    public MediaType contentType() {
+        return MediaType.getFileMediaType(mBody.getName());
+    }
+
+    @Override
+    public void writeTo(@NonNull OutputStream output) throws IOException {
+        InputStream is = new FileInputStream(mBody);
+        IOUtils.write(is, output);
+        IOUtils.closeQuietly(is);
+    }
+}

+ 42 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/body/JsonBody.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.body;
+
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.util.MediaType;
+
+import org.json.JSONObject;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/8.
+ */
+public class JsonBody extends StringBody {
+
+    public JsonBody(String body) {
+        super(body);
+    }
+
+    public JsonBody(JSONObject object) {
+        super(object.toString());
+    }
+
+    @Nullable
+    @Override
+    public MediaType contentType() {
+        return MediaType.APPLICATION_JSON_UTF8;
+    }
+}

+ 97 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/body/StreamBody.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.body;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.IOUtils;
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/7.
+ */
+public class StreamBody implements ResponseBody {
+
+    private InputStream mStream;
+    private boolean mChunked;
+    private long mLength;
+    private MediaType mMediaType;
+
+    public StreamBody(InputStream stream) {
+        this(stream, MediaType.APPLICATION_OCTET_STREAM);
+    }
+
+    public StreamBody(InputStream stream, long length) {
+        this(stream, length, MediaType.APPLICATION_OCTET_STREAM);
+    }
+
+    public StreamBody(InputStream stream, MediaType mediaType) {
+        this(stream, true, 0, mediaType);
+    }
+
+    public StreamBody(InputStream stream, long length, MediaType mediaType) {
+        this(stream, false, length, mediaType);
+    }
+
+    public StreamBody(InputStream stream, boolean chunked, long length, MediaType mediaType) {
+        this.mStream = stream;
+        this.mChunked = chunked;
+        this.mLength = length;
+        this.mMediaType = mediaType;
+    }
+
+    @Override
+    public boolean isRepeatable() {
+        return false;
+    }
+
+    @Override
+    public boolean isChunked() {
+        return mChunked;
+    }
+
+    @Override
+    public long contentLength() {
+        if (mLength == 0 && mStream instanceof FileInputStream) {
+            try {
+                mLength = ((FileInputStream) mStream).getChannel().size();
+                return mLength;
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return mLength;
+    }
+
+    @Nullable
+    @Override
+    public MediaType contentType() {
+        return mMediaType;
+    }
+
+    @Override
+    public void writeTo(@NonNull OutputStream output) throws IOException {
+        IOUtils.write(mStream, output);
+        IOUtils.closeQuietly(mStream);
+    }
+}

+ 90 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/body/StringBody.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.body;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.IOUtils;
+import com.yanzhenjie.andserver.util.MediaType;
+
+import org.apache.commons.io.Charsets;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/6.
+ */
+public class StringBody implements ResponseBody {
+
+    private byte[] mBody;
+    private MediaType mMediaType;
+
+    public StringBody(String body) {
+        this(body, MediaType.TEXT_PLAIN);
+    }
+
+    public StringBody(String body, MediaType mediaType) {
+        if (body == null) {
+            throw new IllegalArgumentException("The content cannot be null.");
+        }
+
+        this.mMediaType = mediaType;
+        if (mMediaType == null) {
+            mMediaType = new MediaType(MediaType.TEXT_PLAIN, Charsets.toCharset("utf-8"));
+        }
+
+        Charset charset = mMediaType.getCharset();
+        if (charset == null) {
+            charset = Charsets.toCharset("utf-8");
+        }
+        this.mBody = body.getBytes(charset);
+    }
+
+    @Override
+    public boolean isRepeatable() {
+        return true;
+    }
+
+    @Override
+    public boolean isChunked() {
+        return false;
+    }
+
+    @Override
+    public long contentLength() {
+        return mBody.length;
+    }
+
+    @Nullable
+    @Override
+    public MediaType contentType() {
+        Charset charset = mMediaType.getCharset();
+        if (charset == null) {
+            charset = Charsets.toCharset("utf-8");
+            return new MediaType(mMediaType.getType(), mMediaType.getSubtype(), charset);
+        }
+        return mMediaType;
+    }
+
+    @Override
+    public void writeTo(@NonNull OutputStream output) throws IOException {
+        IOUtils.write(output, mBody);
+    }
+}

+ 56 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/config/Delegate.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2019 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.config;
+
+import com.yanzhenjie.andserver.framework.website.Website;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2019-06-30.
+ */
+public class Delegate implements WebConfig.Delegate {
+
+    public static Delegate newInstance() {
+        return new Delegate();
+    }
+
+    private Multipart mMultipart;
+    private List<Website> mWebsites;
+
+    private Delegate() {
+        mWebsites = new ArrayList<>();
+    }
+
+    public Multipart getMultipart() {
+        return mMultipart;
+    }
+
+    @Override
+    public void setMultipart(Multipart multipart) {
+        mMultipart = multipart;
+    }
+
+    public List<Website> getWebsites() {
+        return mWebsites;
+    }
+
+    @Override
+    public void addWebsite(Website website) {
+        mWebsites.add(website);
+    }
+}

+ 119 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/config/Multipart.java

@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2019 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.config;
+
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+
+import java.io.File;
+
+/**
+ * Created by Zhenjie Yan on 2019-06-28.
+ */
+public class Multipart {
+
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    private final long allFileMaxSize;
+    private final long fileMaxSize;
+    private final int maxInMemorySize;
+    private final File uploadTempDir;
+
+    private Multipart(Builder builder) {
+        this.allFileMaxSize = builder.allFileMaxSize;
+        this.fileMaxSize = builder.fileMaxSize;
+        this.maxInMemorySize = builder.maxInMemorySize;
+        this.uploadTempDir = builder.uploadTempDir;
+    }
+
+    public long getAllFileMaxSize() {
+        return allFileMaxSize;
+    }
+
+    public long getFileMaxSize() {
+        return fileMaxSize;
+    }
+
+    public int getMaxInMemorySize() {
+        return maxInMemorySize;
+    }
+
+    public File getUploadTempDir() {
+        return uploadTempDir;
+    }
+
+    public static class Builder {
+
+        private long allFileMaxSize;
+        private long fileMaxSize;
+        private int maxInMemorySize;
+        private File uploadTempDir;
+
+        private Builder() {
+        }
+
+        /**
+         * Set the maximum size (in bytes) allowed for uploading. -1 indicates no limit (the default).
+         *
+         * @param allFileMaxSize the maximum upload size allowed.
+         *
+         * @see FileUpload#setSizeMax(long)
+         */
+        public Builder allFileMaxSize(long allFileMaxSize) {
+            this.allFileMaxSize = allFileMaxSize;
+            return this;
+        }
+
+        /**
+         * Set the maximum size (in bytes) allowed for each individual file. -1 indicates no limit (the default).
+         *
+         * @param fileMaxSize the maximum upload size per file.
+         *
+         * @see FileUpload#setFileSizeMax(long)
+         */
+        public Builder fileMaxSize(long fileMaxSize) {
+            this.fileMaxSize = fileMaxSize;
+            return this;
+        }
+
+        /**
+         * Set the maximum allowed size (in bytes) before uploads are written to disk, default is 10240.
+         *
+         * @param maxInMemorySize the maximum in memory size allowed.
+         *
+         * @see DiskFileItemFactory#setSizeThreshold(int)
+         */
+        public Builder maxInMemorySize(int maxInMemorySize) {
+            this.maxInMemorySize = maxInMemorySize;
+            return this;
+        }
+
+        /**
+         * Set the temporary directory where uploaded files get stored.
+         */
+        public Builder uploadTempDir(File uploadTempDir) {
+            this.uploadTempDir = uploadTempDir;
+            return this;
+        }
+
+        public Multipart build() {
+            return new Multipart(this);
+        }
+
+    }
+}

+ 58 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/config/WebConfig.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2019 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.config;
+
+import android.content.Context;
+
+import androidx.annotation.WorkerThread;
+
+import com.yanzhenjie.andserver.framework.website.Website;
+
+/**
+ * Created by Zhenjie Yan on 2019-06-28.
+ * <pre>
+ * <code>@Config</code>
+ * public class AppConfig implements WebConfig {
+ *
+ *     <code>@Override</code>
+ *     public void onConfig(Context context, Delegate delegate) {
+ *         Website website = ...;
+ *         delegate.addWebsite(website);
+ *
+ *         Multipart multipart = Multipart.newBuilder()...build();
+ *         delegate.setMultipart(multipart);
+ *     }
+ * }
+ * </pre>
+ */
+public interface WebConfig {
+
+    @WorkerThread
+    void onConfig(Context context, Delegate delegate);
+
+    interface Delegate {
+
+        /**
+         *
+         */
+        void setMultipart(Multipart multipart);
+
+        /**
+         *
+         */
+        void addWebsite(Website website);
+    }
+}

+ 90 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/cross/CrossOrigin.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2020 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.cross;
+
+import androidx.annotation.NonNull;
+
+import com.yanzhenjie.andserver.http.HttpMethod;
+
+/**
+ * Created by Zhenjie Yan on 10/16/20.
+ */
+public class CrossOrigin {
+
+    private String[] origins;
+    private String[] allowedHeaders;
+    private String[] exposedHeaders;
+    private HttpMethod[] methods;
+    private boolean allowCredentials;
+    private long maxAge;
+
+    public CrossOrigin() {
+    }
+
+    @NonNull
+    public String[] getOrigins() {
+        return origins;
+    }
+
+    public void setOrigins(String[] origins) {
+        this.origins = origins;
+    }
+
+    @NonNull
+    public String[] getAllowedHeaders() {
+        return allowedHeaders;
+    }
+
+    public void setAllowedHeaders(String[] allowedHeaders) {
+        this.allowedHeaders = allowedHeaders;
+    }
+
+    @NonNull
+    public String[] getExposedHeaders() {
+        return exposedHeaders;
+    }
+
+    public void setExposedHeaders(String[] exposedHeaders) {
+        this.exposedHeaders = exposedHeaders;
+    }
+
+    @NonNull
+    public HttpMethod[] getMethods() {
+        return methods;
+    }
+
+    public void setMethods(HttpMethod[] methods) {
+        this.methods = methods;
+    }
+
+    @NonNull
+    public boolean isAllowCredentials() {
+        return allowCredentials;
+    }
+
+    public void setAllowCredentials(boolean allowCredentials) {
+        this.allowCredentials = allowCredentials;
+    }
+
+    @NonNull
+    public long getMaxAge() {
+        return maxAge;
+    }
+
+    public void setMaxAge(long maxAge) {
+        this.maxAge = maxAge;
+    }
+}

+ 49 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/handler/HandlerAdapter.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.http.HttpRequest;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/4.
+ */
+public interface HandlerAdapter {
+
+    /**
+     * Whether to intercept the current request.
+     *
+     * @param request current request.
+     *
+     * @return returns true, otherwise false.
+     */
+    boolean intercept(@NonNull HttpRequest request);
+
+    /**
+     * Get the handler that handles the current request.
+     *
+     * @param request current request.
+     *
+     * @return the handler to handle current request.
+     *
+     * @throws NotFoundException if current request cannot find the corresponding handler.
+     */
+    @Nullable
+    RequestHandler getHandler(@NonNull HttpRequest request);
+}

+ 338 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingAdapter.java

@@ -0,0 +1,338 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.handler;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.error.ContentNotAcceptableException;
+import com.yanzhenjie.andserver.error.ContentNotSupportedException;
+import com.yanzhenjie.andserver.error.HeaderValidateException;
+import com.yanzhenjie.andserver.error.MethodNotSupportException;
+import com.yanzhenjie.andserver.error.ParamValidateException;
+import com.yanzhenjie.andserver.framework.mapping.Mapping;
+import com.yanzhenjie.andserver.framework.mapping.Mime;
+import com.yanzhenjie.andserver.framework.mapping.Pair;
+import com.yanzhenjie.andserver.framework.mapping.Path;
+import com.yanzhenjie.andserver.http.HttpContext;
+import com.yanzhenjie.andserver.http.HttpMethod;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.util.MediaType;
+import com.yanzhenjie.andserver.util.Patterns;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/8.
+ */
+public abstract class MappingAdapter implements HandlerAdapter, Patterns {
+
+    @Override
+    public boolean intercept(@NonNull HttpRequest request) {
+        List<Path.Segment> pathSegments = Path.pathToList(request.getPath());
+
+        List<Mapping> mappings = getExactMappings(pathSegments);
+        if (mappings.isEmpty()) {
+            mappings = getBlurredMappings(pathSegments);
+        }
+        if (mappings.isEmpty()) {
+            return false;
+        }
+
+        HttpMethod method = request.getMethod();
+        if (method.equals(HttpMethod.OPTIONS)) {
+            return true;
+        }
+
+        Mapping mapping = MappingAdapter.findMappingByMethod(mappings, method);
+        if (mapping == null) {
+            MethodNotSupportException exception = new MethodNotSupportException(method);
+            List<HttpMethod> methods = MappingAdapter.findSupportMethods(mappings);
+            exception.setMethods(methods);
+            throw exception;
+        }
+
+        Pair param = mapping.getParam();
+        if (param != null) {
+            validateParams(param, request);
+        }
+
+        Pair header = mapping.getHeader();
+        if (header != null) {
+            validateHeaders(header, request);
+        }
+
+        Mime consume = mapping.getConsume();
+        if (consume != null) {
+            validateConsume(consume, request);
+        }
+
+        Mime produce = mapping.getProduce();
+        if (produce != null) {
+            validateProduce(produce, request);
+        }
+
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public RequestHandler getHandler(@NonNull HttpRequest request) {
+        List<Path.Segment> pathSegments = Path.pathToList(request.getPath());
+
+        List<Mapping> mappings = getExactMappings(pathSegments);
+        if (mappings.isEmpty()) {
+            mappings = getBlurredMappings(pathSegments);
+        }
+
+        HttpMethod method = request.getMethod();
+        Mapping mapping = MappingAdapter.findMappingByMethod(mappings, method);
+
+        if (method.equals(HttpMethod.OPTIONS) && mapping == null) {
+            return new OptionsHandler(request, mappings, getMappingMap());
+        }
+
+        if (mapping == null) {
+            return null;
+        }
+
+        Mime mime = mapping.getProduce();
+        if (mime != null) {
+            List<Mime.Rule> produces = mime.getRuleList();
+            MediaType mediaType = null;
+            for (Mime.Rule produce: produces) {
+                String text = produce.toString();
+                if (!text.startsWith("!")) {
+                    mediaType = produce;
+                    break;
+                }
+            }
+            request.setAttribute(HttpContext.RESPONSE_PRODUCE_TYPE, mediaType);
+        }
+
+        return getMappingMap().get(mapping);
+    }
+
+    private List<Mapping> getExactMappings(List<Path.Segment> httpSegments) {
+        List<Mapping> mappings = new ArrayList<>();
+
+        Map<Mapping, RequestHandler> mappingMap = getMappingMap();
+        for (Mapping mapping: mappingMap.keySet()) {
+            Path path = mapping.getPath();
+            List<Path.Rule> rules = path.getRuleList();
+            for (Path.Rule rule: rules) {
+                if (matchExactPath(rule.getSegments(), httpSegments)) {
+                    mappings.add(mapping);
+                }
+            }
+        }
+        return mappings;
+    }
+
+    private boolean matchExactPath(List<Path.Segment> segments, List<Path.Segment> httpSegments) {
+        if (httpSegments.size() != segments.size()) {
+            return false;
+        }
+
+        if (Path.listToPath(segments).equals(Path.listToPath(httpSegments))) {
+            return true;
+        }
+        return false;
+    }
+
+    private List<Mapping> getBlurredMappings(List<Path.Segment> httpSegments) {
+        List<Mapping> mappings = new ArrayList<>();
+
+        Map<Mapping, RequestHandler> mappingMap = getMappingMap();
+        for (Mapping mapping: mappingMap.keySet()) {
+            Path path = mapping.getPath();
+            List<Path.Rule> rules = path.getRuleList();
+            for (Path.Rule rule: rules) {
+                if (matchBlurredPath(rule.getSegments(), httpSegments)) {
+                    mappings.add(mapping);
+                }
+            }
+        }
+        return mappings;
+    }
+
+    private boolean matchBlurredPath(List<Path.Segment> segments, List<Path.Segment> httpSegments) {
+        if (httpSegments.size() != segments.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < segments.size(); i++) {
+            Path.Segment segment = segments.get(i);
+            if (!segment.equals(httpSegments.get(i)) && !segment.isBlurred()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void validateParams(Pair param, HttpRequest request) {
+        List<Pair.Rule> rules = param.getRuleList();
+        for (Pair.Rule rule: rules) {
+            String key = rule.getKey();
+            List<String> keys = request.getParameterNames();
+            String value = rule.getValue();
+            List<String> values = request.getParameters(key);
+            if (rule.isNoKey()) {
+                if (keys.contains(key)) {
+                    throw new ParamValidateException(String.format("The parameter [%s] is not allowed.", key));
+                }
+            } else if (rule.isNoValue()) {
+                if (values.contains(value)) {
+                    throw new ParamValidateException(
+                        String.format("The value of parameter %s cannot be %s.", key, value));
+                }
+            } else if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
+                if (!keys.contains(key) || !values.contains(value)) {
+                    throw new ParamValidateException(
+                        String.format("The value of parameter %s is missing or wrong.", key));
+                }
+            } else if (!TextUtils.isEmpty(key) && TextUtils.isEmpty(value)) {
+                if (!keys.contains(key)) {
+                    throw new ParamValidateException(String.format("The parameter %s is missing.", key));
+                }
+            }
+        }
+    }
+
+    private void validateHeaders(Pair header, HttpRequest request) {
+        List<Pair.Rule> rules = header.getRuleList();
+        for (Pair.Rule rule: rules) {
+            String key = rule.getKey();
+            List<String> keys = request.getHeaderNames();
+            String value = rule.getValue();
+            List<String> values = request.getHeaders(key);
+            if (rule.isNoKey()) {
+                if (keys.contains(key)) {
+                    throw new HeaderValidateException(String.format("The header [%s] is not allowed.", key));
+                }
+            } else if (rule.isNoValue()) {
+                if (values.contains(value)) {
+                    throw new HeaderValidateException(
+                        String.format("The value of header %s cannot be %s.", key, value));
+                }
+            } else if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value) &&
+                (!keys.contains(key) || !values.contains(value))) {
+                throw new HeaderValidateException(String.format("The value of header %s is missing or wrong.", key));
+            } else if (!TextUtils.isEmpty(key) && TextUtils.isEmpty(value)) {
+                if (!keys.contains(key)) {
+                    throw new HeaderValidateException(String.format("The header %s is missing.", key));
+                }
+            }
+        }
+    }
+
+    private void validateConsume(Mime mime, HttpRequest request) {
+        List<Mime.Rule> rules = mime.getRuleList();
+        MediaType contentType = request.getContentType();
+
+        List<MediaType> includeType = new ArrayList<>();
+        for (Mime.Rule rule: rules) {
+            String type = rule.getType();
+            boolean nonContent = type.startsWith("!");
+            if (nonContent) {
+                type = type.substring(1);
+            }
+            MediaType consume = new MediaType(type, rule.getSubtype());
+
+            if (nonContent) {
+                if (consume.equalsExcludeParameter(contentType)) {
+                    throw new ContentNotSupportedException(contentType);
+                }
+            } else {
+                includeType.add(consume);
+            }
+        }
+
+        boolean included = false;
+        for (MediaType mediaType: includeType) {
+            if (mediaType.includes(contentType)) {
+                included = true;
+                break;
+            }
+        }
+        if (!included) {
+            throw new ContentNotSupportedException(contentType);
+        }
+    }
+
+    private void validateProduce(Mime mime, HttpRequest request) {
+        List<Mime.Rule> rules = mime.getRuleList();
+        List<MediaType> accepts = request.getAccepts();
+        for (Mime.Rule rule: rules) {
+            String type = rule.getType();
+            boolean nonContent = type.startsWith("!");
+            if (nonContent) {
+                type = type.substring(1);
+            }
+            MediaType produce = new MediaType(type, rule.getSubtype());
+
+            boolean exclude = false;
+            for (MediaType accept: accepts) {
+                if (accept.includes(produce)) {
+                    exclude = true;
+                }
+            }
+            if (nonContent && exclude) {
+                throw new ContentNotAcceptableException();
+            }
+            if (!nonContent && !exclude) {
+                throw new ContentNotAcceptableException();
+            }
+        }
+    }
+
+    /**
+     * Get all the mappings for this adapter.
+     *
+     * @return all mappings, non-null, non-empty.
+     */
+    @NonNull
+    protected abstract Map<Mapping, RequestHandler> getMappingMap();
+
+    /**
+     * Get the host of the {@code HandlerAdapter}.
+     *
+     * @return the host of the adapter.
+     */
+    @NonNull
+    protected abstract Object getHost();
+
+    public static List<HttpMethod> findSupportMethods(List<Mapping> mappings) {
+        List<HttpMethod> methods = new ArrayList<>();
+        for (Mapping child: mappings) {
+            methods.addAll(child.getMethod().getRuleList());
+        }
+        return methods;
+    }
+
+    public static Mapping findMappingByMethod(List<Mapping> mappings, HttpMethod method) {
+        for (Mapping child: mappings) {
+            if (child.getMethod().getRuleList().contains(method)) {
+                return child;
+            }
+        }
+        return null;
+    }
+}

+ 186 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingHandler.java

@@ -0,0 +1,186 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.handler;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.framework.ETag;
+import com.yanzhenjie.andserver.framework.LastModified;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.framework.cross.CrossOrigin;
+import com.yanzhenjie.andserver.framework.mapping.Addition;
+import com.yanzhenjie.andserver.framework.mapping.Mapping;
+import com.yanzhenjie.andserver.framework.mapping.Path;
+import com.yanzhenjie.andserver.framework.view.BodyView;
+import com.yanzhenjie.andserver.framework.view.View;
+import com.yanzhenjie.andserver.http.HttpHeaders;
+import com.yanzhenjie.andserver.http.HttpMethod;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+
+import org.apache.httpcore.HttpStatus;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public abstract class MappingHandler implements MethodHandler {
+
+    private final Object mHost;
+    private final Mapping mMapping;
+    private final Addition mAddition;
+    private final CrossOrigin mCrossOrigin;
+
+    public MappingHandler(@NonNull Object host, @NonNull Mapping mapping, @NonNull Addition addition,
+                          @Nullable CrossOrigin crossOrigin) {
+        this.mHost = host;
+        this.mMapping = mapping;
+        this.mAddition = addition;
+        this.mCrossOrigin = crossOrigin;
+    }
+
+    @Override
+    public String getETag(@NonNull HttpRequest request) throws Throwable {
+        Object o = getHost();
+        if (o instanceof ETag) {
+            return ((ETag) o).getETag(request);
+        }
+        return null;
+    }
+
+    @Override
+    public long getLastModified(@NonNull HttpRequest request) throws Throwable {
+        Object o = getHost();
+        if (o instanceof LastModified) {
+            return ((LastModified) o).getLastModified(request);
+        }
+        return -1;
+    }
+
+    @NonNull
+    @Override
+    public Addition getAddition() {
+        return mAddition;
+    }
+
+    @Nullable
+    @Override
+    public CrossOrigin getCrossOrigin() {
+        return mCrossOrigin;
+    }
+
+    @NonNull
+    @Override
+    public Mapping getMapping() {
+        return mMapping;
+    }
+
+    @NonNull
+    protected Object getHost() {
+        return mHost;
+    }
+
+    /**
+     * Get the path to match the request.
+     *
+     * @param httpPath http path.
+     *
+     * @return the path of handler.
+     */
+    @NonNull
+    protected Map<String, String> getPathVariable(@NonNull String httpPath) {
+        List<Path.Segment> httpSegments = Path.pathToList(httpPath);
+        List<Path.Rule> ruleList = mMapping.getPath().getRuleList();
+        for (Path.Rule rule: ruleList) {
+            List<Path.Segment> segments = rule.getSegments();
+            if (httpSegments.size() != segments.size()) {
+                continue;
+            }
+
+            String path = Path.listToPath(segments);
+            if (path.equals(httpPath)) {
+                return Collections.emptyMap();
+            }
+
+            boolean matches = true;
+            boolean isBlurred = false;
+            for (int i = 0; i < segments.size(); i++) {
+                Path.Segment segment = segments.get(i);
+                boolean blurred = segment.isBlurred();
+                isBlurred = isBlurred || blurred;
+                if (!segment.equals(httpSegments.get(i)) && !blurred) {
+                    matches = false;
+                    break;
+                }
+            }
+
+            if (matches && isBlurred) {
+                Map<String, String> map = new HashMap<>();
+                for (int i = 0; i < segments.size(); i++) {
+                    Path.Segment segment = segments.get(i);
+                    if (segment.isBlurred()) {
+                        Path.Segment httpSegment = httpSegments.get(i);
+
+                        String key = segment.getValue();
+                        key = key.substring(1, key.length() - 1);
+                        map.put(key, httpSegment.getValue());
+                    }
+                }
+                return map;
+            }
+        }
+
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public View handle(@NonNull HttpRequest request, @NonNull HttpResponse response) throws Throwable {
+        String origin = request.getHeader(HttpHeaders.ORIGIN);
+        if (!TextUtils.isEmpty(origin) && mCrossOrigin != null) {
+            HttpMethod method = request.getMethod();
+
+            List<HttpMethod> allowMethods = Arrays.asList(mCrossOrigin.getMethods());
+            if (!allowMethods.isEmpty() && !allowMethods.contains(method)) {
+                return invalidCORS(response);
+            }
+
+            response.setHeader(HttpHeaders.Access_Control_Allow_Origin, origin);
+            boolean credentials = mCrossOrigin.isAllowCredentials();
+            response.setHeader(HttpHeaders.Access_Control_Allow_Credentials, Boolean.toString(credentials));
+            response.setHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
+        }
+
+        return onHandle(request, response);
+    }
+
+    private View invalidCORS(HttpResponse response, HttpMethod... methods) {
+        response.setStatus(HttpStatus.SC_FORBIDDEN);
+        if (methods != null && methods.length > 0) {
+            response.setHeader(HttpHeaders.ALLOW, TextUtils.join(", ", methods));
+        }
+        return new BodyView(new StringBody(OptionsHandler.INVALID_CORS_REQUEST));
+    }
+
+    protected abstract View onHandle(HttpRequest request, HttpResponse response) throws Throwable;
+}

+ 53 - 0
api/src/main/java/com/yanzhenjie/andserver/framework/handler/MethodHandler.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 Zhenjie Yan.
+ *
+ * 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.
+ */
+package com.yanzhenjie.andserver.framework.handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.yanzhenjie.andserver.framework.cross.CrossOrigin;
+import com.yanzhenjie.andserver.framework.mapping.Addition;
+import com.yanzhenjie.andserver.framework.mapping.Mapping;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/16.
+ */
+public interface MethodHandler extends RequestHandler {
+
+    /**
+     * Get addition configuration, addition provides some added value.
+     *
+     * @return {@link Addition}.
+     */
+    @NonNull
+    Addition getAddition();
+
+    /**
+     * Get cross origin information.
+     *
+     * @return {@link CrossOrigin}
+     */
+    @Nullable
+    CrossOrigin getCrossOrigin();
+
+    /**
+     * Get mapping configuration, mapping provides all the annotation information for this method.
+     *
+     * @return {@link Mapping}.
+     */
+    @NonNull
+    Mapping getMapping();
+}

部分文件因文件數量過多而無法顯示