diff --git a/.gitignore b/.gitignore
deleted file mode 100755
index 473209c5a3a7825d3445f439231a16f2f884fbbd..0000000000000000000000000000000000000000
--- a/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea
-.DS_Store
-/build
-/captures
-.externalNativeBuild
-app/release
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..5e3adcb2d83c7f3d134b37b9cf31d8b0fe94dafe
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1-SNAPSHOT
+ohos 实现文件传输功能
+
diff --git a/README.OPENSOURCE b/README.OPENSOURCE
new file mode 100644
index 0000000000000000000000000000000000000000..d38217aeff3564abb7411e0ff2c2b15582949e10
--- /dev/null
+++ b/README.OPENSOURCE
@@ -0,0 +1,20 @@
+[
+
+ {
+
+ "Name": "FileTransfer",
+
+ "License": "GNU GENERAL PUBLIC License",
+
+ "License File": "FileTransfer/README.md",
+
+ "Version Number": "Master",
+
+ "Upstream URL": "https://github.com/CPPAlien/FileTransfer",
+
+ "Description": " web端与app端文件传输 "
+
+
+ }
+
+]
\ No newline at end of file
diff --git a/README.md b/README.md
index 756c4b3d0a9cbefe713822323db2181d8166e249..fe5d01aadc1e323f18a097dc0a1a694ffb4e1998 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,77 @@
# FileTransfer
-
-Transfer any files from PC to your phone easily.
+#### 项目介绍
-https://www.youtube.com/watch?v=NUNaORa1YzM
+- 项目名称:FileTransfer
+- 所属系列:openharmony的第三方组件适配移植
+- 功能:web端与app端文件传输
+- 项目移植状态:完成
+- 调用差异:因网络库限制 暂不支持断点续传功能以及文件传输速度无法控制。
-[](https://play.google.com/store/apps/details?id=me.pengtao.filetransfer)
+#### 使用说明:
-**If you are banned by GFW. You can watch the video or download apk by ways below:**
+```java
+Server server = AndServer.webServer(context)
+ .port(8080)
+ .timeout(10, TimeUnit.SECONDS)
+ .build();
-Video: https://v.qq.com/x/page/x0618rp8w9x.html
+// 开启服务.
+server.startup();
-APK: http://d.6short.com/transfer
+...
-### What Next
-1, Two-way transfer between PC and Phone.
+// 关闭服务
+server.shutdown();
+```
+
+http 动态接口的示例如下:
+
+```java
+@RestController
+@RequestMapping(path = "/user")
+public class UserController {
+
+ @PostMapping(path = "/upload", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ String upload(@RequestParam(name = "avatar") MultipartFile file) throws IOException {
+ File localFile = ...FileUtils.createRandomFile(file);
+
+ return localFile.getAbsolutePath();
+ }
+
+ @PostMapping(path = "/delete", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
+ String delete(@RequestParam(name = "name") @RequestBody String name) throws IOException {
+ File file = new File(MainAbilitySlice.path + "/" + name);
+
+ return file;
+ }
+
+ @RequestMapping(
+ path = "/fileList",
+ method = {RequestMethod.PUT, RequestMethod.POST, RequestMethod.GET},
+ produces = MediaType.APPLICATION_JSON_UTF8_VALUE)ResponseBody download(HttpRequest request, HttpResponse response) throws IOException {
+
+ return new StreamBody(FileUtils.fileToinputstream(fileEntity));
+
+ }
+}
+```
+#### 测试信息
+
+CodeCheck代码测试无异常
-2, Support plain text send.
+CloudTest代码测试无异常
-### We Need you
-If you have any ideas for this app, welcome to submit an issue.
+火绒安全病毒安全检测通过
-If you want to add some features, welcome to pull request.
+当前版本demo功能与原组件基本无差异
-## Thanks
-The idea and some code of this app are from [WifiTransfer](https://github.com/baidusoso/WifiTransfer).
-Thanks for [baidusoso](https://github.com/baidusoso).
+#### 版本迭代
+
+- v1.0.0
+
+#### 版权和许可信息
-## LICENSE
```
Copyright (c) 2018 CPPAlien
@@ -43,4 +87,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
-
diff --git a/app/.gitignore b/annotation/.gitignore
old mode 100755
new mode 100644
similarity index 100%
rename from app/.gitignore
rename to annotation/.gitignore
diff --git a/annotation/build.gradle b/annotation/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..88b4e2c921ce2c1e8ae92434257b7a78457799f0
--- /dev/null
+++ b/annotation/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'java-library'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+}
+
+sourceCompatibility = "8"
+targetCompatibility = "8"
+
+ohos {
+ compileSdkVersion = 5
+}
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Addition.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Addition.java
new file mode 100644
index 0000000000000000000000000000000000000000..16430b9718eb794ed2fe6bd3c6f684b9ba29ff9a
--- /dev/null
+++ b/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 {};
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/AppInfo.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/AppInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa9dffacb4d55c694273096ddb9fba195476e36c
--- /dev/null
+++ b/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 "";
+
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Config.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..adb04ee4d09e2c4245ab750ac79cf51ae9247bdf
--- /dev/null
+++ b/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.
+ *
+ * @Config
+ * public class AppConfig implements WebConfig {
+ *
+ * @Override
+ * public void onConfig(Context context, Delegate delegate) {
+ * Website website = ...;
+ * delegate.addWebsite(website);
+ *
+ * Multipart multipart = Multipart.newBuilder()...build();
+ * delegate.setMultipart(multipart);
+ * }
+ * }
+ *
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Config {
+
+ /**
+ * Group name.
+ */
+ String value() default "default";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Controller.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Controller.java
new file mode 100644
index 0000000000000000000000000000000000000000..52cec7ef9e97d13f04df5fc36ac1f3a06e5ad567
--- /dev/null
+++ b/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";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Converter.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Converter.java
new file mode 100644
index 0000000000000000000000000000000000000000..13b0e06d8294ba2a96b65386aeb7b26f653febe8
--- /dev/null
+++ b/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";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/CookieValue.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/CookieValue.java
new file mode 100644
index 0000000000000000000000000000000000000000..c61278d3f7a168de3c3ed16ec8549c671a2b027f
--- /dev/null
+++ b/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.
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
Supplying a default value implicitly sets {@link #required()} to {@code false}.
+ */
+ String defaultValue() default "";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/CrossOrigin.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/CrossOrigin.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7acfadb503a6a4796e9ab28f225a892cc8f1550
--- /dev/null
+++ b/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"}.
+ *
+ *
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.
+ *
+ *
If undefined, all origins are allowed.
+ *
+ * @see #value()
+ */
+ String[] origins() default {};
+
+ /**
+ * List of request headers that can be used during the actual request.
+ *
+ *
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.
+ *
+ *
If undefined, all requested headers are allowed.
+ */
+ String[] allowedHeaders() default {};
+
+ /**
+ * List of response headers that the user-agent will allow the client to access.
+ *
+ *
This property controls the value of actual response's
+ * {@code Access-Control-Expose-Headers} header.
+ *
+ *
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}"}.
+ *
+ *
Methods specified here override those specified via {@code RequestMapping}.
+ *
+ *
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.
+ *
+ *
Set to {@code "false"} if such cookies should not included.
+ * An empty string ({@code ""}) means undefined.
+ * {@code "true"} means that the pre-flight response will include the header
+ * {@code Access-Control-Allow-Credentials=true}.
+ *
+ *
If undefined, credentials are allowed.
+ */
+ String allowCredentials() default "";
+
+ /**
+ * The maximum age (in seconds) of the cache duration for pre-flight responses.
+ *
+ *
This property controls the value of the {@code Access-Control-Max-Age}
+ * header in the pre-flight response.
+ *
+ *
Setting this to a reasonable value can reduce the number of pre-flight
+ * request/response interactions required by the browser.
+ * A negative value means undefined.
+ *
+ *
If undefined, max age is set to {@code 1800} seconds (i.e., 30 minutes).
+ */
+ long maxAge() default -1;
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/DeleteMapping.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/DeleteMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1d878498bc719fe709557a259fed42382c986cc
--- /dev/null
+++ b/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 {};
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/FormPart.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/FormPart.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c32976ba13fccd05358d7e60181d2103f44db2b
--- /dev/null
+++ b/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;
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/GetMapping.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/GetMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..441b0baa1e7c9c262727512638178582d1fb4e9e
--- /dev/null
+++ b/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 {};
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Interceptor.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Interceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c3834f216b07edb6896e200e5747510c381ad3a
--- /dev/null
+++ b/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";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PatchMapping.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PatchMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed2fbe50717f062beac41a3117eb3f0ec14d742c
--- /dev/null
+++ b/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 {};
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PathVariable.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PathVariable.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae1930780f48d80cb455d4ccd9ba5aee205998fd
--- /dev/null
+++ b/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.
+ *
+ *
Defaults to {@code true}, leading to an exception being thrown if the path is missing in the request.
+ *
+ *
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.
+ *
+ *
Supplying a default value implicitly sets {@link #required()} to {@code false}.
+ */
+ String defaultValue() default "";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PostMapping.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PostMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..01257d58f3eb1767c0122ed7a20b497a4cb36439
--- /dev/null
+++ b/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 {};
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PutMapping.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/PutMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bebf6da6e63e16078cc8af88dea92013fdaa43b
--- /dev/null
+++ b/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 {};
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/QueryParam.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/QueryParam.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb1d955912c29022fefc684c1975390d563c2eed
--- /dev/null
+++ b/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.
+ *
+ *
Defaults to {@code true}, leading to an exception being thrown if the parameter is missing in the request.
+ *
+ *
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.
+ *
+ *
Supplying a default value implicitly sets {@link #required()} to {@code false}.
+ */
+ String defaultValue() default "";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestBody.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..2353ce056638519c0ac4474eda3dbea9f5d2f812
--- /dev/null
+++ b/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.
+ *
+ *
Default is {@code true}, leading to an exception thrown in case there is no body content.
+ */
+ boolean required() default true;
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestHeader.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..a02df95c8714ee68800dff0919687463bd2d11f7
--- /dev/null
+++ b/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.
+ *
+ *
Defaults to {@code true}, leading to an exception being thrown if the header is missing in the request.
+ *
+ *
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.
+ *
+ *
Supplying a default value implicitly sets {@link #required()} to {@code false}.
+ */
+ String defaultValue() default "";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMapping.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..967ad9dec118f7f51c95ff9da77144dbe7812a7b
--- /dev/null
+++ b/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")}.
+ *
+ *
Supported at the type level as well as at the method level. 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.
+ *
+ *
Supported at the type level as well as at the method level. 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.
+ *
+ *
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.
+ *
+ *
Supported at the type level as well as at the method level. 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.
+ *
+ *
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 not supposed to
+ * be present in the request.
+ *
+ *
Also supports media type wildcards (*), for headers such as Accept and Content-Type. For instance,
+ *
+ *
@RequestMapping(value = "/something", headers = "content-type=text/*")
+ *
+ * will match requests with a Content-Type of "text/html", "text/plain", etc.
+ *
+ * Supported at the type level as well as at the method level. 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.
+ *
+ *
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:
+ *
+ *
consumes = "text/plain" consumes = {"text/plain", "application/*"}
+ *
+ * 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".
+ *
+ * Supported at the type level as well as at the method level. 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.
+ *
+ *
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:
+ *
+ *
produces = "text/plain" produces = {"text/plain", "application/*"} produces =
+ * "application/json; charset=UTF-8"
+ *
+ * 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.
+ *
+ *
Expressions can be negated by using the "!" operator, as in "!text/plain", which matches all requests with a
+ * {@code Accept} other than "text/plain".
+ *
+ *
Supported at the type level as well as at the method level. When used at the type level, all
+ * method-level mappings override this produces restriction.
+ */
+ String[] produces() default {};
+}
\ No newline at end of file
diff --git a/app/src/main/java/me/pengtao/filetransfer/receiver/PackageStateReceiver.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMethod.java
similarity index 43%
rename from app/src/main/java/me/pengtao/filetransfer/receiver/PackageStateReceiver.java
rename to annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMethod.java
index d8913f40a3b3ce9723bcf60d0baade725fff98db..dee8d42e1418d31b04b8030e2ead7f841dab1c2b 100644
--- a/app/src/main/java/me/pengtao/filetransfer/receiver/PackageStateReceiver.java
+++ b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestMethod.java
@@ -1,11 +1,11 @@
/*
- * Copyright (c) 2018 CPPAlien
+ * Copyright 2018 Zhenjie Yan.
*
- * Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007 (the "License");
+ * 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
*
- * https://github.com/CPPAlien/FileTransfer/blob/master/LICENSE
+ * 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,
@@ -13,22 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package me.pengtao.filetransfer.receiver;
+package com.yanzhenjie.andserver.annotation;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
+/**
+ * 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");
-import com.hwangjr.rxbus.RxBus;
+ private String value;
-import me.pengtao.filetransfer.Constants;
+ RequestMethod(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
-/**
- * @author CPPAlien
- */
-public class PackageStateReceiver extends BroadcastReceiver {
@Override
- public void onReceive(Context context, Intent intent) {
- RxBus.get().post(Constants.RxBusEventType.LOAD_BOOK_LIST, 0);
+ public String toString() {
+ return value;
}
}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestParam.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RequestParam.java
new file mode 100644
index 0000000000000000000000000000000000000000..b4f55ebfd8d9942a0e641a2b5f6fa2954eb014ef
--- /dev/null
+++ b/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.
+ *
+ *
Defaults to {@code true}, leading to an exception being thrown if the parameter is missing in the request.
+ *
+ *
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.
+ *
+ *
Supplying a default value implicitly sets {@link #required()} to {@code false}.
+ */
+ String defaultValue() default "";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Resolver.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/Resolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a91a118045663d58735f2aef1606db00acceda5
--- /dev/null
+++ b/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";
+}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/ResponseBody.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/ResponseBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..1daf53240388cb5109bfbbbd9ad8bf01d45ada55
--- /dev/null
+++ b/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 {}
\ No newline at end of file
diff --git a/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RestController.java b/annotation/src/main/java/com/yanzhenjie/andserver/annotation/RestController.java
new file mode 100644
index 0000000000000000000000000000000000000000..48daff90352332f4ca6866bf39bd8bb8966933b2
--- /dev/null
+++ b/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";
+}
\ No newline at end of file
diff --git a/api/.gitignore b/api/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9
--- /dev/null
+++ b/api/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..4f80da35d252c89478ad5ff21b539769a970293e
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'com.huawei.ohos.library'
+ohos {
+ compileSdkVersion 5
+ defaultConfig {
+ compatibleSdkVersion 5
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ testCompile 'junit:junit:4.12'
+ api project(':annotation')
+ api 'com.yanzhenjie.apache:fileupload:1.4'
+ api 'com.yanzhenjie.apache:httpcore:4.4.13.2'
+ // implementation 'androidx.annotation:annotation:1.1.0'
+}
diff --git a/api/src/main/config.json b/api/src/main/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..e271453c136cb6b5056ffd31cd6ba3b9c0c2656e
--- /dev/null
+++ b/api/src/main/config.json
@@ -0,0 +1,32 @@
+{
+ "app": {
+ "bundleName": "com.yanzhenjie.andserver",
+ "vendor": "yanzhenjie",
+ "version": {
+ "code": 1,
+ "name": "1.0.1"
+ },
+ "apiVersion": {
+ "compatible": 5,
+ "target": 5,
+ "releaseType": "Beta1"
+ }
+ },
+ "deviceConfig": {},
+ "module": {
+ "package": "com.yanzhenjie.andserver",
+ "deviceType": [
+ "phone"
+ ],
+ "distro": {
+ "deliveryWithInstall": true,
+ "moduleName": "api",
+ "moduleType": "har"
+ },
+ "reqPermissions": [
+ {
+ "name": "ohos.permission.INTERNET"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/AndServer.java b/api/src/main/java/com/yanzhenjie/andserver/AndServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a1551796bdede1a959726c002cf0ee0016a2dd4
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/AndServer.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.yanzhenjie.andserver.server.ProxyServer;
+import com.yanzhenjie.andserver.server.WebServer;
+import ohos.agp.render.render3d.BuildConfig;
+import ohos.app.Context;
+
+/**
+ * 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.VERSION_CODE);
+
+
+ /**
+ * Create a builder for the web server.
+ *
+ * @return {@link Server.Builder}.
+ */
+ public static Server.Builder webServer(Context context) {
+ return WebServer.newBuilder(context, "default");
+ }
+
+ /**
+ * Create a builder for the web server.
+ *
+ * @param group group name.
+ *
+ * @return {@link Server.Builder}.
+ */
+
+ public static Server.Builder webServer( Context context,
+ String group) {
+ return WebServer.newBuilder(context, group);
+ }
+
+ /**
+ * Create a builder for the reverse proxy server.
+ *
+ * @return {@link Server.ProxyBuilder}.
+ */
+
+ public static Server.ProxyBuilder proxyServer() {
+ return ProxyServer.newBuilder();
+ }
+
+ /**
+ * @deprecated use {@link #webServer(Context)} instead.
+ */
+
+ @Deprecated
+ public static Server.Builder serverBuilder( Context context) {
+ return webServer(context);
+ }
+
+ /**
+ * @deprecated use {@link #webServer(Context, String)} instead.
+ */
+
+ @Deprecated
+ public static Server.Builder serverBuilder( Context context, String group) {
+ return webServer(context, group);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/ComponentRegister.java b/api/src/main/java/com/yanzhenjie/andserver/ComponentRegister.java
new file mode 100644
index 0000000000000000000000000000000000000000..435a6eefd860668c82cb1747484bf2f6b89e7b2d
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/ComponentRegister.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 com.yanzhenjie.andserver.register.OnRegister;
+import com.yanzhenjie.andserver.register.Register;
+import ohos.app.Context;
+import ohos.global.resource.Entry;
+import ohos.global.resource.ResourceManager;
+
+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 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 {
+ ResourceManager manager = mContext.getResourceManager();
+ Entry[] pathList = null;
+ try {
+ pathList = manager.getRawFileEntry("").getEntries();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (pathList == null || pathList.length == 0) {
+ return;
+ }
+
+ String packageName = "com.yanzhenjie.andserver.hos";
+ 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) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/DispatcherHandler.java b/api/src/main/java/com/yanzhenjie/andserver/DispatcherHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..b337ad96b66e25bc648166f2536971ee913ce0b3
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/DispatcherHandler.java
@@ -0,0 +1,262 @@
+/*
+ * 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 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.*;
+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 ohos.app.Context;
+import ohos.hiviewdfx.HiLog;
+import ohos.hiviewdfx.HiLogLabel;
+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 mAdapterList = new LinkedList<>();
+ private List 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(HandlerAdapter adapter) {
+ Assert.notNull(adapter, "The adapter cannot be null.");
+
+ if (!mAdapterList.contains(adapter)) {
+ mAdapterList.add(adapter);
+ }
+ }
+
+ @Override
+ public void addInterceptor( 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( 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;
+ }
+
+
+ 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( HttpRequest request, 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) {
+ HiLog.error(new HiLogLabel(0,1, AndServer.TAG), "Session persistence failed.", e);
+ }
+
+ Cookie cookie = new Cookie(HttpRequest.SESSION_NAME, session.getId());
+ cookie.setPath("/");
+ cookie.setHttpOnly(true);
+ response.addCookie(cookie);
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/ProxyHandler.java b/api/src/main/java/com/yanzhenjie/andserver/ProxyHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..4236432ec8ea1df602d2d5ee5389346cf6204a51
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/ProxyHandler.java
@@ -0,0 +1,160 @@
+/*
+ * 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.*;
+import org.apache.httpcore.entity.StringEntity;
+import org.apache.httpcore.impl.DefaultBHttpClientConnection;
+import org.apache.httpcore.impl.DefaultConnectionReuseStrategy;
+import org.apache.httpcore.protocol.*;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.*;
+
+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 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 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 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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/SSLSocketInitializer.java b/api/src/main/java/com/yanzhenjie/andserver/SSLSocketInitializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..3aea58c697d4a2afa6a1d8cc243196a00869643a
--- /dev/null
+++ b/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 javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocket;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public interface SSLSocketInitializer {
+
+ void onCreated(SSLServerSocket socket) throws SSLException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/Server.java b/api/src/main/java/com/yanzhenjie/andserver/Server.java
new file mode 100644
index 0000000000000000000000000000000000000000..b5fb156835d178650752b3787656e6b841065dfe
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/Server.java
@@ -0,0 +1,178 @@
+/*
+ * 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 javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 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 {
+
+ /**
+ * 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(ServerListener listener);
+
+ /**
+ * Create a server.
+ */
+ S build();
+ }
+
+ interface ProxyBuilder {
+
+ /**
+ * 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(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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/BasicException.java b/api/src/main/java/com/yanzhenjie/andserver/error/BasicException.java
new file mode 100644
index 0000000000000000000000000000000000000000..c716c3b70d77e91eb702f381cc271f02f6d6cb56
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/BodyMissingException.java b/api/src/main/java/com/yanzhenjie/andserver/error/BodyMissingException.java
new file mode 100644
index 0000000000000000000000000000000000000000..80f633af53e86525446b026e4b9b246eaf4599ef
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/error/BodyMissingException.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/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);
+ }
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/ContentNotAcceptableException.java b/api/src/main/java/com/yanzhenjie/andserver/error/ContentNotAcceptableException.java
new file mode 100644
index 0000000000000000000000000000000000000000..80ac03ba3ffdaa3648060b0fc26e4c4e9f45008f
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/ContentNotSupportedException.java b/api/src/main/java/com/yanzhenjie/andserver/error/ContentNotSupportedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..504e396cdfd43b0ac45d47aab2e97809d0c5bda7
--- /dev/null
+++ b/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.http.StatusCode;
+import com.yanzhenjie.andserver.util.MediaType;
+
+/**
+ * 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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/CookieMissingException.java b/api/src/main/java/com/yanzhenjie/andserver/error/CookieMissingException.java
new file mode 100644
index 0000000000000000000000000000000000000000..abcf6ac2e2598944e299a83cd7d86898dcad05f0
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/error/CookieMissingException.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.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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/HeaderMissingException.java b/api/src/main/java/com/yanzhenjie/andserver/error/HeaderMissingException.java
new file mode 100644
index 0000000000000000000000000000000000000000..28eeff8d01bb3f267cf31a6113c841640ac88d32
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/HeaderValidateException.java b/api/src/main/java/com/yanzhenjie/andserver/error/HeaderValidateException.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1cb3b73ed5bc7b7aa17ee46a76476873c737148
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/HttpException.java b/api/src/main/java/com/yanzhenjie/andserver/error/HttpException.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ab17be8e9e9d6c0d5502cec2ea729851e0bbe38
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/InvalidMediaTypeException.java b/api/src/main/java/com/yanzhenjie/andserver/error/InvalidMediaTypeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..28a37a0d31aa49348d2af4dfa8f2074e11208661
--- /dev/null
+++ b/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;
+ }
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/InvalidMimeTypeException.java b/api/src/main/java/com/yanzhenjie/andserver/error/InvalidMimeTypeException.java
new file mode 100644
index 0000000000000000000000000000000000000000..86dc4f52ef657392364de9b855aeb12926ff4cdc
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/MaxUploadSizeExceededException.java b/api/src/main/java/com/yanzhenjie/andserver/error/MaxUploadSizeExceededException.java
new file mode 100644
index 0000000000000000000000000000000000000000..beda78dfdb0cdfc106dd9020e9e5adef85510b1f
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/error/MaxUploadSizeExceededException.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/MethodNotSupportException.java b/api/src/main/java/com/yanzhenjie/andserver/error/MethodNotSupportException.java
new file mode 100644
index 0000000000000000000000000000000000000000..392670c1685257c51a8393100ada054841160548
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/error/MethodNotSupportException.java
@@ -0,0 +1,48 @@
+/*
+ * 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 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 getMethods() {
+ return mMethods;
+ }
+
+ public void setMethods(List methods) {
+ mMethods = methods;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/MultipartException.java b/api/src/main/java/com/yanzhenjie/andserver/error/MultipartException.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf6bcb18eb542d8470dede7b1824ed313ad24216
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/error/MultipartException.java
@@ -0,0 +1,44 @@
+/*
+ * 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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/NotFoundException.java b/api/src/main/java/com/yanzhenjie/andserver/error/NotFoundException.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c6de0d960b55ffbcfe53facfa892e6b5ea9e8a8
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/ParamMissingException.java b/api/src/main/java/com/yanzhenjie/andserver/error/ParamMissingException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5dad959d833c95cf087ff0da6dda805b6225aef5
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/ParamValidateException.java b/api/src/main/java/com/yanzhenjie/andserver/error/ParamValidateException.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f8246fff4750cf29e59250adf2c128e1b80ae7b
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/error/ParamValidateException.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/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/PathMissingException.java b/api/src/main/java/com/yanzhenjie/andserver/error/PathMissingException.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0405a81924d6e454f182121c80560d0c80c3244
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/error/ServerInternalException.java b/api/src/main/java/com/yanzhenjie/andserver/error/ServerInternalException.java
new file mode 100644
index 0000000000000000000000000000000000000000..058004b30d01b9f21646856cf8a679a9ba83a56d
--- /dev/null
+++ b/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);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/ETag.java b/api/src/main/java/com/yanzhenjie/andserver/framework/ETag.java
new file mode 100644
index 0000000000000000000000000000000000000000..74491a37c199cdc60e00e2a41a1a070856122eaf
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/ETag.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.yanzhenjie.andserver.http.HttpRequest;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/31.
+ */
+public interface ETag {
+
+ /**
+ * Get the {@code ETag} requesting the specified resource.
+ *
+ * Can simply return {@code null} if there's no support.
+ */
+ String getETag(HttpRequest request) throws Throwable;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/ExceptionResolver.java b/api/src/main/java/com/yanzhenjie/andserver/framework/ExceptionResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..e59e2a99d53fbe6f376da078264fd110545e0468
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/ExceptionResolver.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.yanzhenjie.andserver.error.HttpException;
+import com.yanzhenjie.andserver.error.MethodNotSupportException;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.http.*;
+import com.yanzhenjie.andserver.util.TextUtils;
+
+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(HttpRequest request, HttpResponse response, Throwable e) {
+ if (e instanceof MethodNotSupportException) {
+ List 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( HttpRequest request, HttpResponse response, 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(HttpRequest request, HttpResponse response, Throwable e);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/HandlerInterceptor.java b/api/src/main/java/com/yanzhenjie/andserver/framework/HandlerInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed8aff069b6584ba3b91751597582a7a8d510f44
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/HandlerInterceptor.java
@@ -0,0 +1,39 @@
+/*
+ * 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 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(HttpRequest request, HttpResponse response, RequestHandler handler)
+ throws Exception;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/LastModified.java b/api/src/main/java/com/yanzhenjie/andserver/framework/LastModified.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a6c366524a886d694218adc9e6c7bcd10ddebae
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/LastModified.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.framework;
+
+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(HttpRequest request) throws Throwable;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/MessageConverter.java b/api/src/main/java/com/yanzhenjie/andserver/framework/MessageConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c5637722da1073e7bedfc2450e021ff8f28bc95
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/MessageConverter.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.framework;
+
+
+
+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(Object output, MediaType mediaType);
+
+ /**
+ * Convert RequestBody to a object.
+ *
+ * @param stream {@link InputStream}.
+ * @param mediaType he content media type.
+ * @param type type of object.
+ * @param type of object.
+ *
+ * @return object.
+ */
+
+ T convert(InputStream stream, MediaType mediaType, Type type) throws IOException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/ModifiedInterceptor.java b/api/src/main/java/com/yanzhenjie/andserver/framework/ModifiedInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..d98e54448174f3b25a219b20d7229eef56c8c031
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/ModifiedInterceptor.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.framework;
+
+
+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;
+import ohos.hiviewdfx.HiLog;
+import ohos.hiviewdfx.HiLogLabel;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/14.
+ */
+public class ModifiedInterceptor implements HandlerInterceptor {
+
+ @Override
+ public boolean onIntercept(HttpRequest request, HttpResponse response,
+ 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) {
+ HiLog.warn(new HiLogLabel(0, 1, AndServer.TAG),"", e);
+ }
+ long lastModified = -1;
+ try {
+ lastModified = handler.getLastModified(request);
+ } catch (Throwable e) {
+ HiLog.warn(new HiLogLabel(0, 1, AndServer.TAG),"", e);
+ }
+ return new Modified(request, response).process(eTag, lastModified);
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/body/FileBody.java b/api/src/main/java/com/yanzhenjie/andserver/framework/body/FileBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..3272edb7edff41c29e8ef3188e3c46a1c364cc1b
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/body/FileBody.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.IOUtils;
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.io.*;
+
+/**
+ * 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 long contentLength() {
+ return mBody.length();
+ }
+
+
+ @Override
+ public MediaType contentType() {
+ return MediaType.getFileMediaType(mBody.getName());
+ }
+
+ @Override
+ public void writeTo(OutputStream output) throws IOException {
+ InputStream is = new FileInputStream(mBody);
+ IOUtils.write(is, output);
+ IOUtils.closeQuietly(is);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/body/JsonBody.java b/api/src/main/java/com/yanzhenjie/andserver/framework/body/JsonBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..756f06aac5d475f75fec7b34bdb8df95575bb186
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/body/JsonBody.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.yanzhenjie.andserver.util.MediaType;
+import ohos.utils.zson.ZSONObject;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/8.
+ */
+public class JsonBody extends StringBody {
+
+ public JsonBody(String body) {
+ super(body);
+ }
+
+ public JsonBody(ZSONObject object) {
+ super(object.toString());
+ }
+
+ @Override
+ public MediaType contentType() {
+ return MediaType.APPLICATION_JSON_UTF8;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/body/StreamBody.java b/api/src/main/java/com/yanzhenjie/andserver/framework/body/StreamBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..a64a4739986b39bbf5633f68545aeb93b2c36d91
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/body/StreamBody.java
@@ -0,0 +1,86 @@
+/*
+ * 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 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 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, 0, mediaType);
+ }
+
+ public StreamBody(InputStream stream, long length, MediaType mediaType) {
+ this.mStream = stream;
+ this.mLength = length;
+ this.mMediaType = mediaType;
+ }
+
+ @Override
+ public boolean isRepeatable() {
+ return false;
+ }
+
+ @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;
+ }
+
+
+ @Override
+ public MediaType contentType() {
+ return mMediaType;
+ }
+
+ @Override
+ public void writeTo(OutputStream output) throws IOException {
+ IOUtils.write(mStream, output);
+ IOUtils.closeQuietly(mStream);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/body/StringBody.java b/api/src/main/java/com/yanzhenjie/andserver/framework/body/StringBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..95dda0a44f68dc88588788c1a639bc2f97775dec
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/body/StringBody.java
@@ -0,0 +1,82 @@
+/*
+ * 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 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;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 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, StandardCharsets.UTF_8);
+ }
+
+ Charset charset = mMediaType.getCharset();
+ if (charset == null) {
+ charset = StandardCharsets.UTF_8;
+ }
+ this.mBody = body.getBytes(charset);
+ }
+
+ @Override
+ public boolean isRepeatable() {
+ return true;
+ }
+
+ @Override
+ public long contentLength() {
+ return mBody.length;
+ }
+
+
+ @Override
+ public MediaType contentType() {
+ Charset charset = mMediaType.getCharset();
+ if (charset == null) {
+ charset = StandardCharsets.UTF_8;
+ return new MediaType(mMediaType.getType(), mMediaType.getSubtype(), charset);
+ }
+ return mMediaType;
+ }
+
+ @Override
+ public void writeTo(OutputStream output) throws IOException {
+ IOUtils.write(output, mBody);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/config/Delegate.java b/api/src/main/java/com/yanzhenjie/andserver/framework/config/Delegate.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f4c837f138237f2d6da6dd3364a73481c622759
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/config/Delegate.java
@@ -0,0 +1,57 @@
+/*
+ * 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 mWebsites;
+
+ private Delegate() {
+ mWebsites = new ArrayList<>();
+ }
+
+ public Multipart getMultipart() {
+ return mMultipart;
+ }
+
+ @Override
+ public void setMultipart(Multipart multipart) {
+ mMultipart = multipart;
+ }
+
+ public List getWebsites() {
+ return mWebsites;
+ }
+
+ @Override
+ public void addWebsite(Website website) {
+ mWebsites.add(website);
+ }
+}
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/config/Multipart.java b/api/src/main/java/com/yanzhenjie/andserver/framework/config/Multipart.java
new file mode 100644
index 0000000000000000000000000000000000000000..fed0090ec1e1df67660248bc80ab05a63e839942
--- /dev/null
+++ b/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);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/config/WebConfig.java b/api/src/main/java/com/yanzhenjie/andserver/framework/config/WebConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb1a92bb09eca8dad9d2139ae86f7fef18d9a190
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/config/WebConfig.java
@@ -0,0 +1,54 @@
+/*
+ * 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 ohos.app.Context;
+
+/**
+ * Created by Zhenjie Yan on 2019-06-28.
+ *
+ * @Config
+ * public class AppConfig implements WebConfig {
+ *
+ * @Override
+ * public void onConfig(Context context, Delegate delegate) {
+ * Website website = ...;
+ * delegate.addWebsite(website);
+ *
+ * Multipart multipart = Multipart.newBuilder()...build();
+ * delegate.setMultipart(multipart);
+ * }
+ * }
+ *
+ */
+public interface WebConfig {
+
+ void onConfig(Context context, Delegate delegate);
+
+ interface Delegate {
+
+ /**
+ *
+ */
+ void setMultipart(Multipart multipart);
+
+ /**
+ *
+ */
+ void addWebsite(Website website);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/cross/CrossOrigin.java b/api/src/main/java/com/yanzhenjie/andserver/framework/cross/CrossOrigin.java
new file mode 100644
index 0000000000000000000000000000000000000000..69209b264aa3a3c49d0633e2a96b6af437d0269e
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/cross/CrossOrigin.java
@@ -0,0 +1,89 @@
+/*
+ * 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 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() {
+ }
+
+
+ public String[] getOrigins() {
+ return origins;
+ }
+
+ public void setOrigins(String[] origins) {
+ this.origins = origins;
+ }
+
+
+ public String[] getAllowedHeaders() {
+ return allowedHeaders;
+ }
+
+ public void setAllowedHeaders(String[] allowedHeaders) {
+ this.allowedHeaders = allowedHeaders;
+ }
+
+
+ public String[] getExposedHeaders() {
+ return exposedHeaders;
+ }
+
+ public void setExposedHeaders(String[] exposedHeaders) {
+ this.exposedHeaders = exposedHeaders;
+ }
+
+
+ public HttpMethod[] getMethods() {
+ return methods;
+ }
+
+ public void setMethods(HttpMethod[] methods) {
+ this.methods = methods;
+ }
+
+
+ public boolean isAllowCredentials() {
+ return allowCredentials;
+ }
+
+ public void setAllowCredentials(boolean allowCredentials) {
+ this.allowCredentials = allowCredentials;
+ }
+
+
+ public long getMaxAge() {
+ return maxAge;
+ }
+
+ public void setMaxAge(long maxAge) {
+ this.maxAge = maxAge;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/handler/HandlerAdapter.java b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/HandlerAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..301566dda44fe3eb8fe8c059b9cc93935860df42
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/HandlerAdapter.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.framework.handler;
+
+
+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(HttpRequest request);
+
+ /**
+ * Get the handler that handles the current request.
+ *
+ * @param request current request.
+ *
+ * @return the handler to handle current request.
+ *
+ * @throws if current request cannot find the corresponding handler.
+ */
+ RequestHandler getHandler(HttpRequest request);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingAdapter.java b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..9d57f6ea01e9e04aa8cd3673c50f6b00c3562a0e
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingAdapter.java
@@ -0,0 +1,330 @@
+/*
+ * 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 com.yanzhenjie.andserver.error.*;
+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(HttpRequest request) {
+ List pathSegments = Path.pathToList(request.getPath());
+
+ List 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 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;
+ }
+
+
+ @Override
+ public RequestHandler getHandler( HttpRequest request) {
+ List pathSegments = Path.pathToList(request.getPath());
+
+ List 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 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 getExactMappings(List httpSegments) {
+ List mappings = new ArrayList<>();
+
+ Map mappingMap = getMappingMap();
+ for (Mapping mapping: mappingMap.keySet()) {
+ Path path = mapping.getPath();
+ List rules = path.getRuleList();
+ for (Path.Rule rule: rules) {
+ if (matchExactPath(rule.getSegments(), httpSegments)) {
+ mappings.add(mapping);
+ }
+ }
+ }
+ return mappings;
+ }
+
+ private boolean matchExactPath(List segments, List httpSegments) {
+ if (httpSegments.size() != segments.size()) {
+ return false;
+ }
+
+ if (Path.listToPath(segments).equals(Path.listToPath(httpSegments))) {
+ return true;
+ }
+ return false;
+ }
+
+ private List getBlurredMappings(List httpSegments) {
+ List mappings = new ArrayList<>();
+
+ Map mappingMap = getMappingMap();
+ for (Mapping mapping: mappingMap.keySet()) {
+ Path path = mapping.getPath();
+ List rules = path.getRuleList();
+ for (Path.Rule rule: rules) {
+ if (matchBlurredPath(rule.getSegments(), httpSegments)) {
+ mappings.add(mapping);
+ }
+ }
+ }
+ return mappings;
+ }
+
+ private boolean matchBlurredPath(List segments, List 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 rules = param.getRuleList();
+ for (Pair.Rule rule: rules) {
+ String key = rule.getKey();
+ List keys = request.getParameterNames();
+ String value = rule.getValue();
+ List 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 (!key.isEmpty() && !value.isEmpty()) {
+ if (!keys.contains(key) || !values.contains(value)) {
+ throw new ParamValidateException(
+ String.format("The value of parameter %s is missing or wrong.", key));
+ }
+ } else if (!key.isEmpty() && value.isEmpty()) {
+ if (!keys.contains(key)) {
+ throw new ParamValidateException(String.format("The parameter %s is missing.", key));
+ }
+ }
+ }
+ }
+
+ private void validateHeaders(Pair header, HttpRequest request) {
+ List rules = header.getRuleList();
+ for (Pair.Rule rule: rules) {
+ String key = rule.getKey();
+ List keys = request.getHeaderNames();
+ String value = rule.getValue();
+ List 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 (!key.isEmpty() && !value.isEmpty() &&
+ (!keys.contains(key) || !values.contains(value))) {
+ throw new HeaderValidateException(String.format("The value of header %s is missing or wrong.", key));
+ } else if (!key.isEmpty() && value.isEmpty()) {
+ if (!keys.contains(key)) {
+ throw new HeaderValidateException(String.format("The header %s is missing.", key));
+ }
+ }
+ }
+ }
+
+ private void validateConsume(Mime mime, HttpRequest request) {
+ List rules = mime.getRuleList();
+ MediaType contentType = request.getContentType();
+
+ List 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 rules = mime.getRuleList();
+ List 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.
+ */
+
+ protected abstract Map getMappingMap();
+
+ /**
+ * Get the host of the {@code HandlerAdapter}.
+ *
+ * @return the host of the adapter.
+ */
+
+ protected abstract Object getHost();
+
+ public static List findSupportMethods(List mappings) {
+ List methods = new ArrayList<>();
+ for (Mapping child: mappings) {
+ methods.addAll(child.getMethod().getRuleList());
+ }
+ return methods;
+ }
+
+ public static Mapping findMappingByMethod(List mappings, HttpMethod method) {
+ for (Mapping child: mappings) {
+ if (child.getMethod().getRuleList().contains(method)) {
+ return child;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingHandler.java b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd344f0a1abea964deb23d6260cae5bfc92b854e
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MappingHandler.java
@@ -0,0 +1,178 @@
+/*
+ * 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 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 com.yanzhenjie.andserver.util.StringUtils;
+import com.yanzhenjie.andserver.util.TextUtils;
+import org.apache.httpcore.HttpStatus;
+
+import java.util.*;
+
+/**
+ * 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(Object host, Mapping mapping, Addition addition,
+ CrossOrigin crossOrigin) {
+ this.mHost = host;
+ this.mMapping = mapping;
+ this.mAddition = addition;
+ this.mCrossOrigin = crossOrigin;
+ }
+
+ @Override
+ public String getETag( HttpRequest request) throws Throwable {
+ Object o = getHost();
+ if (o instanceof ETag) {
+ return ((ETag) o).getETag(request);
+ }
+ return null;
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ Object o = getHost();
+ if (o instanceof LastModified) {
+ return ((LastModified) o).getLastModified(request);
+ }
+ return -1;
+ }
+
+
+ @Override
+ public Addition getAddition() {
+ return mAddition;
+ }
+
+
+ @Override
+ public CrossOrigin getCrossOrigin() {
+ return mCrossOrigin;
+ }
+
+
+ @Override
+ public Mapping getMapping() {
+ return mMapping;
+ }
+
+
+ protected Object getHost() {
+ return mHost;
+ }
+
+ /**
+ * Get the path to match the request.
+ *
+ * @param httpPath http path.
+ *
+ * @return the path of handler.
+ */
+
+ protected Map getPathVariable( String httpPath) {
+ List httpSegments = Path.pathToList(httpPath);
+ List ruleList = mMapping.getPath().getRuleList();
+ for (Path.Rule rule: ruleList) {
+ List 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 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(HttpRequest request, HttpResponse response) throws Throwable {
+ String origin = request.getHeader(HttpHeaders.ORIGIN);
+ if (!StringUtils.isEmpty(origin) && mCrossOrigin != null) {
+ HttpMethod method = request.getMethod();
+
+ List 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;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MethodHandler.java b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MethodHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..49317b9537934234cebd87beec5001daa944dc33
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/MethodHandler.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.framework.handler;
+
+
+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}.
+ */
+
+ Addition getAddition();
+
+ /**
+ * Get cross origin information.
+ *
+ * @return {@link CrossOrigin}
+ */
+
+ CrossOrigin getCrossOrigin();
+
+ /**
+ * Get mapping configuration, mapping provides all the annotation information for this method.
+ *
+ * @return {@link Mapping}.
+ */
+
+ Mapping getMapping();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/handler/OptionsHandler.java b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/OptionsHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb551407e7876ac53db0f48bfbbc35ece08f2167
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/OptionsHandler.java
@@ -0,0 +1,198 @@
+/*
+ * 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.handler;
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+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.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 com.yanzhenjie.andserver.util.TextUtils;
+import org.apache.httpcore.HttpStatus;
+
+import java.util.*;
+
+/**
+ * Created by Zhenjie Yan on 10/17/20.
+ */
+public class OptionsHandler implements MethodHandler {
+
+ public static final String INVALID_CORS_REQUEST = "Invalid CORS request.";
+
+ private List mMappings;
+ private Map mMappingMap;
+
+ private Mapping mMapping;
+ private MethodHandler mHandler;
+
+ public OptionsHandler(HttpRequest optionsRequest, List mappings, Map mappingMap) {
+ this.mMappings = mappings;
+ this.mMappingMap = mappingMap;
+
+ mMapping = mMappings.get(0);
+ String requestMethod = optionsRequest.getHeader(HttpHeaders.Access_Control_Request_Method);
+ if (!requestMethod.isEmpty()) {
+ HttpMethod method = HttpMethod.reverse(requestMethod);
+ Mapping exactMapping = MappingAdapter.findMappingByMethod(mMappings, method);
+ if (exactMapping != null) {
+ mMapping = exactMapping;
+ }
+ }
+
+ mHandler = (MethodHandler) mMappingMap.get(mMapping);
+ }
+
+
+ @Override
+ public Addition getAddition() {
+ return mHandler.getAddition();
+ }
+
+
+ @Override
+ public CrossOrigin getCrossOrigin() {
+ return mHandler.getCrossOrigin();
+ }
+
+
+ @Override
+ public Mapping getMapping() {
+ return mMapping;
+ }
+
+ @Override
+ public View handle( HttpRequest request, HttpResponse response) throws Throwable {
+ String requestOrigin = request.getHeader(HttpHeaders.ORIGIN);
+ if (requestOrigin.isEmpty()) {
+ return invalidCORS(response);
+ }
+
+ String requestMethodText = request.getHeader(HttpHeaders.Access_Control_Request_Method);
+ if (requestMethodText.isEmpty()) {
+ return invalidCORS(response);
+ }
+
+ HttpMethod requestMethod = HttpMethod.reverse(requestMethodText);
+ Mapping mapping = MappingAdapter.findMappingByMethod(mMappings, requestMethod);
+ if (mapping == null) {
+ return invalidCORS(response);
+ }
+
+ MethodHandler handler = (MethodHandler) mMappingMap.get(mapping);
+ if (handler == null) {
+ throw new NotFoundException();
+ }
+
+ CrossOrigin crossOrigin = handler.getCrossOrigin();
+ if (crossOrigin == null) {
+ return invalidCORS(response);
+ }
+
+ List allowMethods = new ArrayList<>();
+ Collections.addAll(allowMethods, crossOrigin.getMethods());
+ List mappingMethods = mapping.getMethod().getRuleList();
+ if (allowMethods.isEmpty()) {
+ allowMethods.addAll(mappingMethods);
+ }
+ if (!allowMethods.contains(requestMethod)) {
+ return invalidCORS(response);
+ }
+
+ List allowOrigins = Arrays.asList(crossOrigin.getOrigins());
+ if (!allowOrigins.isEmpty() && !allowOrigins.contains("*") && !allowOrigins.contains(requestOrigin)) {
+ return invalidCORS(response);
+ }
+
+ List allowedHeaders = Arrays.asList(crossOrigin.getAllowedHeaders());
+ List outHeaders = new ArrayList<>();
+ String headerHeadersText = request.getHeader(HttpHeaders.Access_Control_Request_Headers);
+ List requestHeaders = new ArrayList<>();
+ if (!headerHeadersText.isEmpty()) {
+ StringTokenizer st = new StringTokenizer(headerHeadersText, ",");
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ token = token.trim();
+ if (token.length() > 0) {
+ requestHeaders.add(token);
+ }
+ }
+ }
+ if (allowedHeaders.contains("*")) {
+ if (requestHeaders.size() > 0) {
+ outHeaders.addAll(requestHeaders);
+ }
+ } else if (allowedHeaders.size() > 0) {
+ if (requestHeaders.size() > 0) {
+ for (String allowedHeader: allowedHeaders) {
+ for (String requestHeader: requestHeaders) {
+ if (allowedHeader.equalsIgnoreCase(requestHeader)) {
+ outHeaders.add(requestHeader);
+ }
+ }
+ }
+ if (outHeaders.isEmpty()) {
+ return invalidCORS(response);
+ }
+ }
+ } else if (requestHeaders.size() > 0) {
+ outHeaders.addAll(requestHeaders);
+ }
+
+ String[] exposeHeaders = crossOrigin.getExposedHeaders();
+
+ response.setHeader(HttpHeaders.Access_Control_Allow_Origin, requestOrigin);
+ response.setHeader(HttpHeaders.Access_Control_Allow_Methods, TextUtils.join(", ", allowMethods));
+ if (outHeaders.size() > 0) {
+ response.setHeader(HttpHeaders.Access_Control_Allow_Headers, TextUtils.join(", ", outHeaders));
+ }
+ if (exposeHeaders.length > 0) {
+ response.setHeader(HttpHeaders.Access_Control_Expose_Headers, String.join(", ", exposeHeaders));
+ }
+
+ boolean credentials = crossOrigin.isAllowCredentials();
+ response.setHeader(HttpHeaders.Access_Control_Allow_Credentials, Boolean.toString(credentials));
+
+ long maxAge = crossOrigin.getMaxAge();
+ response.setHeader(HttpHeaders.Access_Control_Max_Age, Long.toString(maxAge));
+
+ response.setHeader(HttpHeaders.ALLOW, TextUtils.join(", ", HttpMethod.values()));
+ response.setHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
+
+ return new BodyView(new StringBody("OK"));
+ }
+
+ private View invalidCORS(HttpResponse response) {
+ response.setStatus(HttpStatus.SC_FORBIDDEN);
+ response.setHeader(HttpHeaders.ALLOW, TextUtils.join(", ", HttpMethod.values()));
+ return new BodyView(new StringBody(INVALID_CORS_REQUEST));
+ }
+
+ @Override
+ public String getETag( HttpRequest request) throws Throwable {
+ return mHandler.getETag(request);
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ return mHandler.getLastModified(request);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/handler/RequestHandler.java b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/RequestHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..ead5ddce5aca8c53a49eefee9ebc12c461bb93b3
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/handler/RequestHandler.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.yanzhenjie.andserver.framework.ETag;
+import com.yanzhenjie.andserver.framework.LastModified;
+import com.yanzhenjie.andserver.framework.view.View;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/28.
+ */
+public interface RequestHandler extends ETag, LastModified {
+
+ /**
+ * Use the given handler to handle this request.
+ *
+ * @param request current request.
+ * @param response current response.
+ *
+ * @return the impression sent to the client.
+ */
+ View handle(HttpRequest request, HttpResponse response) throws Throwable;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Addition.java b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Addition.java
new file mode 100644
index 0000000000000000000000000000000000000000..750c37be09c4204c753d4082c2545b6f9a38c883
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Addition.java
@@ -0,0 +1,118 @@
+/*
+ * 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.mapping;
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public class Addition {
+
+ private String[] stringType;
+ private boolean[] booleanType;
+ private int[] intType;
+ private long[] longType;
+ private short[] shortType;
+ private float[] floatType;
+ private double[] doubleType;
+ private byte[] byteType;
+ private char[] charType;
+
+ public Addition() {
+ }
+
+
+ public String[] getStringType() {
+ return stringType;
+ }
+
+ public void setStringType(String[] stringType) {
+ this.stringType = stringType;
+ }
+
+
+ public boolean[] getBooleanType() {
+ return booleanType;
+ }
+
+ public void setBooleanType(boolean[] booleanType) {
+ this.booleanType = booleanType;
+ }
+
+
+ public int[] getIntType() {
+ return intType;
+ }
+
+ public void setIntType(int[] intType) {
+ this.intType = intType;
+ }
+
+
+ public long[] getLongType() {
+ return longType;
+ }
+
+ public void setLongType(long[] longType) {
+ this.longType = longType;
+ }
+
+
+ public short[] getShortType() {
+ return shortType;
+ }
+
+ public void setShortType(short[] shortType) {
+ this.shortType = shortType;
+ }
+
+
+ public float[] getFloatType() {
+ return floatType;
+ }
+
+ public void setFloatType(float[] floatType) {
+ this.floatType = floatType;
+ }
+
+
+ public double[] getDoubleType() {
+ return doubleType;
+ }
+
+ public void setDoubleType(double[] doubleType) {
+ this.doubleType = doubleType;
+ }
+
+
+ public byte[] getByteType() {
+ return byteType;
+ }
+
+ public void setByteType(byte[] byteType) {
+ this.byteType = byteType;
+ }
+
+
+ public char[] getCharType() {
+ return charType;
+ }
+
+ public void setCharType(char[] charType) {
+ this.charType = charType;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Mapping.java b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Mapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca70981cfc2d807e6b5d78fead12165253b2ad33
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Mapping.java
@@ -0,0 +1,82 @@
+/*
+ * 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.mapping;
+
+/**
+ * Save the request mapping configuration.
+ *
+ * Created by Zhenjie Yan on 2018/6/13.
+ */
+public class Mapping {
+
+ private Path mPath;
+ private Method mMethod;
+ private Pair mParam;
+ private Pair mHeader;
+ private Mime mConsume;
+ private Mime mProduce;
+
+ public Mapping() {
+ }
+
+ public Path getPath() {
+ return mPath;
+ }
+
+ public void setPath(Path path) {
+ mPath = path;
+ }
+
+ public Method getMethod() {
+ return mMethod;
+ }
+
+ public void setMethod(Method method) {
+ mMethod = method;
+ }
+
+ public Pair getParam() {
+ return mParam;
+ }
+
+ public void setParam(Pair param) {
+ mParam = param;
+ }
+
+ public Pair getHeader() {
+ return mHeader;
+ }
+
+ public void setHeader(Pair header) {
+ mHeader = header;
+ }
+
+ public Mime getConsume() {
+ return mConsume;
+ }
+
+ public void setConsume(Mime consume) {
+ mConsume = consume;
+ }
+
+ public Mime getProduce() {
+ return mProduce;
+ }
+
+ public void setProduce(Mime produce) {
+ mProduce = produce;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Method.java b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Method.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c4f2daf15a9b620827e331c6ca8c782c73c1da7
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Method.java
@@ -0,0 +1,41 @@
+/*
+ * 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.mapping;
+
+import com.yanzhenjie.andserver.http.HttpMethod;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/14.
+ */
+public class Method {
+
+ private List mRuleList = new LinkedList<>();
+
+ public Method() {
+ }
+
+
+ public List getRuleList() {
+ return mRuleList;
+ }
+
+ public void addRule( String ruleText) {
+ mRuleList.add(HttpMethod.reverse(ruleText));
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Mime.java b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Mime.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fd067bf4aecc931ce0048eb475e7dbf50e8dace
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Mime.java
@@ -0,0 +1,52 @@
+/*
+ * 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.mapping;
+
+
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/14.
+ */
+public class Mime {
+
+ private List mRuleList = new LinkedList<>();
+
+ public Mime() {
+ }
+
+
+ public List getRuleList() {
+ return mRuleList;
+ }
+
+ public void addRule( String ruleText) {
+ MediaType mimeType = MediaType.valueOf(ruleText);
+ Rule rule = new Rule(mimeType.getType(), mimeType.getSubtype(), mimeType.getParameters());
+ mRuleList.add(rule);
+ }
+
+ public static class Rule extends MediaType {
+
+ public Rule(String type, String subtype, Map parameters) {
+ super(type, subtype, parameters);
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Pair.java b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Pair.java
new file mode 100644
index 0000000000000000000000000000000000000000..717acbe71db14f2166f4f7ace6269412c06052e2
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Pair.java
@@ -0,0 +1,109 @@
+/*
+ * 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.mapping;
+
+
+import com.yanzhenjie.andserver.util.Patterns;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/14.
+ */
+public class Pair implements Patterns {
+
+ private List mRuleList = new LinkedList<>();
+
+ public Pair() {
+ }
+
+
+ public List getRuleList() {
+ return mRuleList;
+ }
+
+ public void addRule( String ruleText) {
+ if (ruleText.matches(PAIR_NO_VALUE)) {
+ String[] keyValue = ruleText.split("=");
+ Rule rule = new Rule();
+ String key = keyValue[0];
+ rule.setKey(key.substring(0, key.length() - 1));
+ rule.setValue(keyValue[1]);
+ rule.setNoValue(true);
+ mRuleList.add(rule);
+ } else if (ruleText.matches(PAIR_KEY_VALUE)) {
+ String[] keyValue = ruleText.split("=");
+
+ Rule rule = new Rule();
+ rule.setKey(keyValue[0]);
+ rule.setValue(keyValue[1]);
+ mRuleList.add(rule);
+ } else if (ruleText.matches(PAIR_NO_KEY)) {
+ Rule rule = new Rule();
+ rule.setKey(ruleText.substring(1));
+ rule.setNoKey(true);
+ mRuleList.add(rule);
+ } else if (ruleText.matches(PAIR_KEY)) {
+ Rule rule = new Rule();
+ rule.setKey(ruleText);
+ mRuleList.add(rule);
+ }
+ }
+
+ public static class Rule {
+
+ private String key;
+ private String value;
+ private boolean noKey;
+ private boolean noValue;
+
+ public Rule() {
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public boolean isNoKey() {
+ return noKey;
+ }
+
+ public void setNoKey(boolean noKey) {
+ this.noKey = noKey;
+ }
+
+ public boolean isNoValue() {
+ return noValue;
+ }
+
+ public void setNoValue(boolean noValue) {
+ this.noValue = noValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Path.java b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Path.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfa78e36a34ecfcfe9a29470d786d5a920d6c24b
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/mapping/Path.java
@@ -0,0 +1,156 @@
+/*
+ * 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.mapping;
+
+
+import com.yanzhenjie.andserver.util.Patterns;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/14.
+ */
+public class Path implements Patterns {
+
+ private List mRuleList = new LinkedList<>();
+
+ public Path() {
+ }
+
+
+ public List getRuleList() {
+ return mRuleList;
+ }
+
+ public void addRule( String ruleText) {
+ Rule rule = new Rule();
+ rule.setSegments(pathToList(ruleText));
+ mRuleList.add(rule);
+ }
+
+ public static class Rule {
+
+ private List mSegments;
+
+ public Rule() {
+ }
+
+ public List getSegments() {
+ return mSegments;
+ }
+
+ public void setSegments(List segments) {
+ mSegments = segments;
+ }
+ }
+
+ public static class Segment {
+
+ private final String value;
+ private final boolean isBlurred;
+
+ public Segment(String value, boolean isBlurred) {
+ this.value = value;
+ this.isBlurred = isBlurred;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public boolean isBlurred() {
+ return isBlurred;
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj == null ){
+ return false;
+ }
+ if(this == obj){
+ return true ;
+ }
+ if (!(obj instanceof Segment)) {
+ return false;
+ }
+ return value.equals(((Segment) obj).value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+
+ public static List pathToList( String path) {
+ List segmentList = new LinkedList<>();
+ if (!path.isEmpty()) {
+ while (path.startsWith("/"))
+ path = path.substring(1);
+ while (path.endsWith("/"))
+ path = path.substring(0, path.length() - 1);
+ String[] pathArray = path.split("/");
+ for (String segmentText: pathArray) {
+ Segment segment = new Segment(segmentText, segmentText.contains("{"));
+ segmentList.add(segment);
+ }
+ }
+ return Collections.unmodifiableList(segmentList);
+ }
+
+
+ public static String listToPath( List segments) {
+ StringBuilder builder = new StringBuilder("");
+ if (segments.isEmpty()) {
+ builder.append("/");
+ }
+ for (Segment segment: segments) {
+ builder.append("/").append(segment.getValue());
+ }
+ return builder.toString();
+ }
+
+ public static boolean matches( String path1, String path2) {
+ if (path1.equals(path2)) {
+ return true;
+ }
+
+ List segments1 = pathToList(path1);
+ List segments2 = pathToList(path2);
+
+ if (segments1.size() != segments2.size()) {
+ return false;
+ }
+
+ boolean matches = true;
+ for (int i = 0; i < segments1.size(); i++) {
+ Segment segment = segments1.get(i);
+ if (!segment.equals(segments2.get(i)) && !segment.isBlurred()) {
+ matches = false;
+ break;
+ }
+ }
+ return matches;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/view/BodyView.java b/api/src/main/java/com/yanzhenjie/andserver/framework/view/BodyView.java
new file mode 100644
index 0000000000000000000000000000000000000000..21b109a0f6d81730439867190db1cff84cf66282
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/view/BodyView.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.view;
+
+
+import com.yanzhenjie.andserver.http.ResponseBody;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/7.
+ */
+public class BodyView implements View {
+
+ private ResponseBody mBody;
+
+ public BodyView( ResponseBody body) {
+ this.mBody = body;
+ }
+
+ @Override
+ public boolean rest() {
+ return true;
+ }
+
+
+ @Override
+ public Object output() {
+ return mBody;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/view/ObjectView.java b/api/src/main/java/com/yanzhenjie/andserver/framework/view/ObjectView.java
new file mode 100644
index 0000000000000000000000000000000000000000..e122d8ebd17eeb97e54c3d42d11e2b261daa9908
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/view/ObjectView.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.framework.view;
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/9/9.
+ */
+public class ObjectView implements View {
+
+ private final boolean isRest;
+ private final Object output;
+
+ public ObjectView(boolean isRest, Object output) {
+ this.isRest = isRest;
+ this.output = output;
+ }
+
+ @Override
+ public boolean rest() {
+ return isRest;
+ }
+
+
+ @Override
+ public Object output() {
+ return output;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/view/View.java b/api/src/main/java/com/yanzhenjie/andserver/framework/view/View.java
new file mode 100644
index 0000000000000000000000000000000000000000..8cae4af6db571604d311bc673969fa5086c985d6
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/view/View.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.view;
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/8/29.
+ */
+public interface View {
+
+ /**
+ * Is it a rest style view?
+ *
+ * @return true, otherwise is false.
+ */
+ boolean rest();
+
+ /**
+ * Get the output.
+ *
+ * @return output, e.g. {@code "redirect:/user/list"}, {@code "forward:/user/list"}, {@code "/user/list"}, String,
+ * JSONObject, Object, Basic data type(int, short, long, double, float, byte, boolean char).
+ */
+
+ Object output();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/view/ViewResolver.java b/api/src/main/java/com/yanzhenjie/andserver/framework/view/ViewResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..c673d78b8ed944d0aff7fe75c38425fa586d72eb
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/view/ViewResolver.java
@@ -0,0 +1,126 @@
+/*
+ * 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.view;
+
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.error.ServerInternalException;
+import com.yanzhenjie.andserver.framework.MessageConverter;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.http.*;
+import com.yanzhenjie.andserver.util.MediaType;
+import com.yanzhenjie.andserver.util.Patterns;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/31.
+ */
+public class ViewResolver implements Patterns, StatusCode, HttpHeaders {
+
+ private MessageConverter mConverter;
+
+ public ViewResolver() {
+ }
+
+ public ViewResolver(MessageConverter converter) {
+ this.mConverter = converter;
+ }
+
+ /**
+ * Solve the view and convert the view to http package content.
+ *
+ * @param view current view.
+ * @param request current request.
+ * @param response current response.
+ */
+ public void resolve(View view, HttpRequest request, HttpResponse response) {
+ if (view == null) {
+ return;
+ }
+
+ Object output = view.output();
+
+ if (view.rest()) {
+ resolveRest(output, request, response);
+ } else {
+ resolvePath(output, request, response);
+ }
+ }
+
+ private void resolveRest(Object output, HttpRequest request, HttpResponse response) {
+ if (output instanceof ResponseBody) {
+ response.setBody((ResponseBody) output);
+ } else if (mConverter != null) {
+ response.setBody(mConverter.convert(output, obtainProduce(request)));
+ } else if (output == null) {
+ response.setBody(new StringBody(""));
+ } else if (output instanceof String) {
+ response.setBody(new StringBody(output.toString(), obtainProduce(request)));
+ } else {
+ response.setBody(new StringBody(output.toString()));
+ }
+ }
+
+
+ private MediaType obtainProduce(HttpRequest request) {
+ final Object mtAttribute = request.getAttribute(HttpContext.RESPONSE_PRODUCE_TYPE);
+ if (mtAttribute instanceof MediaType) {
+ return (MediaType) mtAttribute;
+ }
+ return null;
+ }
+
+ private void resolvePath(Object output, HttpRequest request, HttpResponse response) {
+ if (output instanceof CharSequence) {
+ final String action = output.toString();
+ if (action.isEmpty()) {
+ return;
+ }
+
+ // "redirect:(.)*"
+ if (action.matches(REDIRECT)) {
+ response.setStatus(SC_FOUND);
+ if (action.length() >= 9) {
+ final String path = action.substring(9);
+ response.setHeader(LOCATION, path);
+ }
+ }
+ // "forward:(.)*"
+ else if (action.matches(FORWARD)) {
+ final String path = action.substring(8);
+ RequestDispatcher dispatcher = request.getRequestDispatcher(path);
+ if (dispatcher != null) {
+ dispatcher.forward(request, response);
+ } else {
+ throw new NotFoundException(path);
+ }
+ }
+ // "/user/kevin"
+ else if (action.matches(PATH)) {
+ final String path = action + ".html";
+ RequestDispatcher dispatcher = request.getRequestDispatcher(path);
+ if (dispatcher != null) {
+ dispatcher.forward(request, response);
+ } else {
+ throw new NotFoundException(path);
+ }
+ } else {
+ throw new NotFoundException(action);
+ }
+ } else {
+ throw new ServerInternalException(String.format("The return value of [%s] is not supported", output));
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/website/AssetsWebsite.java b/api/src/main/java/com/yanzhenjie/andserver/framework/website/AssetsWebsite.java
new file mode 100644
index 0000000000000000000000000000000000000000..9121a2456901d4b13df939793392808d6d17ff43
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/website/AssetsWebsite.java
@@ -0,0 +1,263 @@
+/*
+ * 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.website;
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.framework.body.StreamBody;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.*;
+import ohos.app.Context;
+import ohos.bundle.BundleInfo;
+import ohos.bundle.IBundleManager;
+import ohos.global.resource.Entry;
+import ohos.global.resource.ResourceManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/7.
+ */
+public class AssetsWebsite extends BasicWebsite implements Patterns {
+
+ private final AssetsReader mAssetsReader;
+ private final String mRootPath;
+ private final BundleInfo mPackageInfo;
+
+ /**
+ * Create a website object.
+ *
+ * @param rootPath website root directory.
+ */
+ public AssetsWebsite( Context context, String rootPath) {
+ this(context, rootPath, DEFAULT_INDEX);
+ }
+
+ /**
+ * Create a website object.
+ *
+ * @param rootPath website root directory.
+ * @param indexFileName the default file name for each directory, e.g. index.html.
+ */
+ public AssetsWebsite( Context context, String rootPath, String indexFileName) {
+ super(indexFileName);
+ Assert.isTrue(!rootPath.isEmpty(), "The rootPath cannot be empty.");
+ Assert.isTrue(!indexFileName.isEmpty(), "The indexFileName cannot be empty.");
+
+ if (!rootPath.matches(PATH)) {
+ String message = "The format of [%s] is wrong, it should be like [/root/project] or [/root/project/].";
+ String format = String.format(message, rootPath);
+ throw new IllegalArgumentException(format);
+ }
+
+ this.mAssetsReader = new AssetsReader(context.getResourceManager());
+ this.mRootPath = trimSlash(rootPath);
+
+ IBundleManager packageManager = context.getBundleManager();
+ try {
+ mPackageInfo = packageManager.getBundleInfo(context.getBundleName(), 0);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean intercept(HttpRequest request) {
+ String httpPath = request.getPath();
+ InputStream stream = findPathSteam(httpPath);
+ IOUtils.closeQuietly(stream);
+ return stream != null;
+ }
+
+ @Override
+ public String getETag(HttpRequest request) throws Throwable {
+ String httpPath = request.getPath();
+ InputStream stream = findPathSteam(httpPath);
+ if (stream != null) {
+ try {
+ return DigestUtils.md5DigestAsHex(stream);
+ } finally {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public long getLastModified(HttpRequest request) throws Throwable {
+ String httpPath = request.getPath();
+ InputStream stream = findPathSteam(httpPath);
+ IOUtils.closeQuietly(stream);
+ return stream != null ? mPackageInfo.getUpdateTime() : -1;
+ }
+
+
+ @Override
+ public ResponseBody getBody(HttpRequest request, HttpResponse response) throws IOException {
+ String httpPath = request.getPath();
+ String objectPath = mRootPath + httpPath;
+ InputStream stream = mAssetsReader.getInputStream(objectPath);
+ if (stream != null) {
+ MediaType mediaType = MediaType.getFileMediaType(objectPath);
+ return new StreamBody(stream, stream.available(), mediaType);
+ }
+
+ String indexPath = addEndSlash(objectPath) + getIndexFileName();
+ InputStream indexStream = mAssetsReader.getInputStream(indexPath);
+ if (indexStream != null) {
+ if (!httpPath.endsWith(File.separator)) {
+ IOUtils.closeQuietly(indexStream);
+ String redirectPath = addEndSlash(httpPath);
+ String query = queryString(request);
+ response.sendRedirect(redirectPath + "?" + query);
+ return new StringBody("");
+ }
+
+ final MediaType mediaType = MediaType.getFileMediaType(indexPath);
+ return new StreamBody(indexStream, indexStream.available(), mediaType);
+ }
+ throw new NotFoundException(httpPath);
+
+ }
+
+ private InputStream findPathSteam(String httpPath) {
+ String targetPath = mRootPath + httpPath;
+ InputStream targetStream = mAssetsReader.getInputStream(targetPath);
+ if (targetStream != null) {
+ return targetStream;
+ }
+
+ String indexPath = addEndSlash(targetPath) + getIndexFileName();
+ InputStream indexStream = mAssetsReader.getInputStream(indexPath);
+ if (indexStream != null) {
+ return indexStream;
+ }
+
+ return null;
+ }
+
+ public static class AssetsReader {
+
+ /**
+ * {@link ResourceManager}.
+ */
+ private ResourceManager mAssetManager;
+
+ /**
+ * Create {@link AssetsReader}.
+ *
+ * @param manager {@link ResourceManager}.
+ */
+ public AssetsReader( ResourceManager manager) {
+ this.mAssetManager = manager;
+ }
+
+ /**
+ * Get stream file.
+ *
+ * @param filePath assets in the absolute path.
+ *
+ * @return {@link InputStream} or null.
+ */
+
+ public InputStream getInputStream( String filePath) {
+ try {
+ return mAssetManager.getRawFileEntry(filePath).openRawFile();
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+
+ /**
+ * Specify whether the destination is a file.
+ *
+ * @param fileName assets in the absolute path.
+ *
+ * @return true, other wise is false.
+ */
+ public boolean isFile( String fileName) {
+ InputStream stream = null;
+ try {
+ stream = getInputStream(fileName);
+ return stream != null;
+ } finally {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+
+ /**
+ * Scanning subFolders and files under the specified path.
+ *
+ * @param path the specified path.
+ *
+ * @return String[] Array of strings, one for each asset. May be null.
+ */
+
+ public List list( String path) {
+ List fileList = new ArrayList<>();
+ try {
+ Entry[] entries = mAssetManager.getRawFileEntry(path).getEntries();
+ if(entries.length > 0){
+ String[] files = new String[entries.length];
+ for(int i = 0;i < entries.length;i++){
+ files[i] = entries[i].getPath();
+ }
+ Collections.addAll(fileList, files);
+ }
+ } catch (Throwable ignored) {
+ }
+ return fileList;
+ }
+
+ /**
+ * Scan all files in the inPath.
+ *
+ * @param path path in the path.
+ *
+ * @return under inPath absolute path.
+ */
+
+ public List scanFile( String path) {
+ Assert.isTrue(!path.isEmpty(), "The path cannot be empty.");
+
+ List pathList = new ArrayList<>();
+ if (isFile(path)) {
+ pathList.add(path);
+ } else {
+ List files = list(path);
+ for (String file: files) {
+ String realPath = path + File.separator + file;
+ if (isFile(realPath)) {
+ pathList.add(realPath);
+ } else {
+ List childList = scanFile(realPath);
+ if (childList.size() > 0) {
+ pathList.addAll(childList);
+ }
+ }
+ }
+ }
+ return pathList;
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/website/BasicWebsite.java b/api/src/main/java/com/yanzhenjie/andserver/framework/website/BasicWebsite.java
new file mode 100644
index 0000000000000000000000000000000000000000..90fb6a31b08649cc6098da104cd3463998b01262
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/website/BasicWebsite.java
@@ -0,0 +1,163 @@
+/*
+ * 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.website;
+
+
+
+
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.util.Assert;
+import com.yanzhenjie.andserver.util.MultiValueMap;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/6.
+ */
+public abstract class BasicWebsite extends Website {
+
+ public static final String DEFAULT_INDEX = "index.html";
+
+ private final String mIndexFileName;
+
+ public BasicWebsite() {
+ this(DEFAULT_INDEX);
+ }
+
+ /**
+ * Create a website object.
+ *
+ * @param indexFileName the default file name for each directory, e.g. index.html.
+ */
+ public BasicWebsite( String indexFileName) {
+ Assert.isTrue(!indexFileName.isEmpty(), "The indexFileName cannot be empty.");
+ this.mIndexFileName = indexFileName;
+ }
+
+ @Override
+ public String getETag( HttpRequest request) throws Throwable {
+ return null;
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ return -1;
+ }
+
+ /**
+ * Get the name of the indexFile.
+ *
+ * @return file name, does not include the path.
+ */
+
+ protected final String getIndexFileName() {
+ return mIndexFileName;
+ }
+
+ /**
+ * Add the '/' to the beginning.
+ *
+ * @param target target string.
+ *
+ * @return rule result.
+ */
+ protected String addStartSlash( String target) {
+ if (!target.startsWith(File.separator)) {
+ target = File.separator + target;
+ }
+ return target;
+ }
+
+ /**
+ * Add '/' at the ending.
+ *
+ * @param target target string.
+ *
+ * @return rule result.
+ */
+ protected String addEndSlash( String target) {
+ if (!target.endsWith(File.separator)) {
+ target = target + File.separator;
+ }
+ return target;
+ }
+
+ /**
+ * Remove '/' at the beginning.
+ *
+ * @param target target string.
+ *
+ * @return rule result.
+ */
+ protected String trimStartSlash( String target) {
+ while (target.startsWith(File.separator))
+ target = target.substring(1);
+ return target;
+ }
+
+ /**
+ * Remove '/' at the ending.
+ *
+ * @param target target string.
+ *
+ * @return rule result.
+ */
+ protected String trimEndSlash( String target) {
+ while (target.endsWith(File.separator))
+ target = target.substring(0, target.length() - 1);
+ return target;
+ }
+
+ /**
+ * Remove the '/' at the beginning and ending.
+ *
+ * @param target target string.
+ *
+ * @return rule result.
+ */
+ protected String trimSlash( String target) {
+ target = trimStartSlash(target);
+ target = trimEndSlash(target);
+ return target;
+ }
+
+ protected String queryString(HttpRequest request) {
+ MultiValueMap query = request.getQuery();
+ if (query.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder queryString = new StringBuilder();
+ for (Map.Entry> entry: query.entrySet()) {
+ String key = entry.getKey();
+ List values = entry.getValue();
+ if (values != null && !values.isEmpty()) {
+ for (int i = 0; i < values.size(); i++) {
+ queryString.append("&")
+ .append(key)
+ .append("=")
+ .append(values.get(i));
+ }
+ }
+ }
+ if (queryString.length() > 0) {
+ queryString.deleteCharAt(0);
+ }
+ return queryString.toString();
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/website/FileBrowser.java b/api/src/main/java/com/yanzhenjie/andserver/framework/website/FileBrowser.java
new file mode 100644
index 0000000000000000000000000000000000000000..2457546e42506e3f2bc322d8b91bdf09bfd7a4b4
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/website/FileBrowser.java
@@ -0,0 +1,155 @@
+/*
+ * 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.website;
+
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.framework.body.FileBody;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.*;
+import org.apache.commons.io.Charsets;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/7.
+ */
+public class FileBrowser extends BasicWebsite implements Patterns {
+
+ private static final String FOLDER_HTML_PREFIX = " " +
+ "%1$s%2$s
";
+ private static final String FOLDER_ITEM = "- %2$s
";
+ private static final String FOLDER_HTML_SUFFIX = "
";
+
+ private final String mRootPath;
+
+ public FileBrowser(String rootPath) {
+ Assert.isTrue(!rootPath.isEmpty(), "The rootPath cannot be empty.");
+ Assert.isTrue(rootPath.matches(PATH), "The format of [%s] is wrong, it should be like [/root/project].");
+ this.mRootPath = rootPath;
+ }
+
+ @Override
+ public boolean intercept(HttpRequest request) {
+ String httpPath = request.getPath();
+ File file = findPathFile(httpPath);
+ return file != null;
+ }
+
+ @Override
+ public String getETag(HttpRequest request) throws Throwable {
+ String httpPath = request.getPath();
+ File file = findPathFile(httpPath);
+ if (file != null) {
+ String tag = file.getAbsolutePath() + file.lastModified();
+ return DigestUtils.md5DigestAsHex(tag);
+ }
+ return null;
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ String httpPath = request.getPath();
+ File file = findPathFile(httpPath);
+ if (file != null) {
+ return file.lastModified();
+ }
+ return -1;
+ }
+
+
+ @Override
+ public ResponseBody getBody(HttpRequest request, HttpResponse response) throws IOException {
+ String httpPath = request.getPath();
+ File file = new File(mRootPath, httpPath);
+ if (!file.exists()) {
+ throw new NotFoundException(httpPath);
+ }
+
+ if (file.isDirectory()) {
+ if (!httpPath.endsWith(File.separator)) {
+ String redirectPath = addEndSlash(httpPath);
+ response.sendRedirect(redirectPath);
+ return new StringBody("");
+ }
+
+ File tempFile = File.createTempFile("file_browser", ".html");
+ OutputStream outputStream = new FileOutputStream(tempFile);
+
+ String folderName = file.getName();
+ String prefix = String.format(FOLDER_HTML_PREFIX, folderName, folderName);
+ outputStream.write(prefix.getBytes("utf-8"));
+
+ File[] children = file.listFiles();
+ if (children != null && children.length > 0) {
+ for (File child: children) {
+ String filePath = child.getAbsolutePath();
+ int rootIndex = filePath.indexOf(mRootPath);
+ String subHttpPath = filePath.substring(rootIndex + mRootPath.length());
+ subHttpPath = addStartSlash(subHttpPath);
+ String fileItem = String.format(FOLDER_ITEM, subHttpPath, child.getName());
+ outputStream.write(fileItem.getBytes("utf-8"));
+ }
+ }
+
+ outputStream.write(FOLDER_HTML_SUFFIX.getBytes("utf-8"));
+ IOUtils.closeQuietly(outputStream);
+
+ return new FileBody(tempFile) {
+
+ @Override
+ public MediaType contentType() {
+ MediaType mimeType = super.contentType();
+ if (mimeType != null) {
+ mimeType = new MediaType(mimeType.getType(), mimeType.getSubtype(), StandardCharsets.UTF_8);
+ }
+ return mimeType;
+ }
+ };
+ } else {
+ return new FileBody(file);
+ }
+ }
+
+ /**
+ * Find the path specified resource.
+ *
+ * @param httpPath path.
+ *
+ * @return return if the file is found.
+ */
+ private File findPathFile( String httpPath) {
+ File targetFile = new File(mRootPath, httpPath);
+ if (targetFile.exists()) {
+ return targetFile;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/website/StorageWebsite.java b/api/src/main/java/com/yanzhenjie/andserver/framework/website/StorageWebsite.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8072e6760cf4ce87e76df6db566efb8e669adbd
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/website/StorageWebsite.java
@@ -0,0 +1,132 @@
+/*
+ * 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.website;
+
+import com.yanzhenjie.andserver.error.NotFoundException;
+import com.yanzhenjie.andserver.framework.body.FileBody;
+import com.yanzhenjie.andserver.framework.body.StringBody;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.ResponseBody;
+import com.yanzhenjie.andserver.util.Assert;
+import com.yanzhenjie.andserver.util.DigestUtils;
+import com.yanzhenjie.andserver.util.Patterns;
+import com.yanzhenjie.andserver.util.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/7.
+ */
+public class StorageWebsite extends BasicWebsite implements Patterns {
+
+ private final String mRootPath;
+
+ /**
+ * Create a website object.
+ *
+ * @param rootPath website root directory.
+ */
+ public StorageWebsite( String rootPath) {
+ this(rootPath, DEFAULT_INDEX);
+ }
+
+ /**
+ * Create a website object.
+ *
+ * @param rootPath website root directory.
+ * @param indexFileName the default file name for each directory, e.g. index.html.
+ */
+ public StorageWebsite( String rootPath, String indexFileName) {
+ super(indexFileName);
+ Assert.isTrue(!StringUtils.isEmpty(rootPath), "The rootPath cannot be empty.");
+ Assert.isTrue(rootPath.matches(PATH), "The format of [%s] is wrong, it should be like [/root/project].");
+
+ this.mRootPath = rootPath;
+ }
+
+ @Override
+ public boolean intercept(HttpRequest request) {
+ String httpPath = request.getPath();
+ File file = findPathFile(httpPath);
+ return file != null;
+ }
+
+ @Override
+ public String getETag( HttpRequest request) throws Throwable {
+ String httpPath = request.getPath();
+ File file = findPathFile(httpPath);
+ if (file != null) {
+ String tag = file.getAbsolutePath() + file.lastModified();
+ return DigestUtils.md5DigestAsHex(tag);
+ }
+ return null;
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ String httpPath = request.getPath();
+ File file = findPathFile(httpPath);
+ if (file != null) {
+ return file.lastModified();
+ }
+ return -1;
+ }
+
+
+ @Override
+ public ResponseBody getBody(HttpRequest request, HttpResponse response) throws IOException {
+ String httpPath = request.getPath();
+ File targetFile = new File(mRootPath, httpPath);
+ if (targetFile.exists() && targetFile.isFile()) {
+ return new FileBody(targetFile);
+ }
+
+ File indexFile = new File(targetFile, getIndexFileName());
+ if (indexFile.exists() && indexFile.isFile()) {
+ if (!httpPath.endsWith(File.separator)) {
+ String redirectPath = addEndSlash(httpPath);
+ String query = queryString(request);
+ response.sendRedirect(redirectPath + "?" + query);
+ return new StringBody("");
+ }
+
+ return new FileBody(indexFile);
+ }
+ throw new NotFoundException(httpPath);
+ }
+
+ /**
+ * Find the path specified resource.
+ *
+ * @param httpPath path.
+ *
+ * @return return if the file is found.
+ */
+ private File findPathFile( String httpPath) {
+ File targetFile = new File(mRootPath, httpPath);
+ if (targetFile.exists() && targetFile.isFile()) {
+ return targetFile;
+ }
+
+ File indexFile = new File(targetFile, getIndexFileName());
+ if (indexFile.exists() && indexFile.isFile()) {
+ return indexFile;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/framework/website/Website.java b/api/src/main/java/com/yanzhenjie/andserver/framework/website/Website.java
new file mode 100644
index 0000000000000000000000000000000000000000..36ace6f165ecc68964c7656b3963aee131a8ab3b
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/framework/website/Website.java
@@ -0,0 +1,73 @@
+/*
+ * 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.website;
+
+
+
+import com.yanzhenjie.andserver.framework.ETag;
+import com.yanzhenjie.andserver.framework.LastModified;
+import com.yanzhenjie.andserver.framework.handler.HandlerAdapter;
+import com.yanzhenjie.andserver.framework.handler.RequestHandler;
+import com.yanzhenjie.andserver.framework.view.BodyView;
+import com.yanzhenjie.andserver.framework.view.View;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.HttpResponse;
+import com.yanzhenjie.andserver.http.ResponseBody;
+
+import java.io.IOException;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/4.
+ */
+public abstract class Website implements HandlerAdapter, ETag, LastModified {
+
+
+ @Override
+ public String getETag( HttpRequest request) throws Throwable {
+ return null;
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ return 0;
+ }
+
+
+ @Override
+ public RequestHandler getHandler(HttpRequest request) {
+ return new RequestHandler() {
+
+ @Override
+ public String getETag( HttpRequest request) throws Throwable {
+ return Website.this.getETag(request);
+ }
+
+ @Override
+ public long getLastModified( HttpRequest request) throws Throwable {
+ return Website.this.getLastModified(request);
+ }
+
+ @Override
+ public View handle(HttpRequest request, HttpResponse response) throws Throwable {
+ return new BodyView(getBody(request, response));
+ }
+ };
+ }
+
+
+ public abstract ResponseBody getBody(HttpRequest request, HttpResponse response)
+ throws IOException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/FAcceptLanguage.java b/api/src/main/java/com/yanzhenjie/andserver/http/FAcceptLanguage.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6dc642d967eff0942f8dfc93a9e186bc1ca02d7
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/FAcceptLanguage.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.http;
+
+import com.yanzhenjie.andserver.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/7.
+ */
+public class FAcceptLanguage {
+
+ private final Locale locale;
+ private final double quality;
+
+ protected FAcceptLanguage(Locale locale, double quality) {
+ this.locale = locale;
+ this.quality = quality;
+ }
+
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public double getQuality() {
+ return quality;
+ }
+
+ public static List parse(String input) {
+ if (StringUtils.isEmpty(input)) {
+ return Collections.emptyList();
+ }
+
+ String[] segments = input.split(",");
+ if (segments.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List list = new ArrayList<>();
+ for (String segment: segments) {
+ String[] values = segment.split(";");
+ if (values.length == 2 && values[1].length() > 2 && values[1].charAt(0) == 'q' &&
+ values[1].charAt(1) == '=') {
+ String q = values[1].substring(2);
+ try {
+ list.add(new FAcceptLanguage(new Locale(values[1]), Double.parseDouble(q)));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/HttpContext.java b/api/src/main/java/com/yanzhenjie/andserver/http/HttpContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..a60f03b1d0a6834c087a68fb43ecffd9c44f7cdb
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/HttpContext.java
@@ -0,0 +1,61 @@
+/*
+ * 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.http;
+
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/8/31.
+ */
+public interface HttpContext {
+
+ String RESPONSE_PRODUCE_TYPE = "http.response.Produce";
+
+ String REQUEST_CREATED_SESSION = "http.request.Session";
+
+ String HTTP_MESSAGE_CONVERTER = "http.message.converter";
+
+ String ANDROID_CONTEXT = "android.context";
+
+ /**
+ * Obtains attribute with the given name.
+ *
+ * @param id the attribute name.
+ *
+ * @return attribute value, or {@code null} if not set.
+ */
+
+ Object getAttribute(String id);
+
+ /**
+ * Sets value of the attribute with the given name.
+ *
+ * @param id the attribute name.
+ * @param obj the attribute value.
+ */
+ void setAttribute(String id, Object obj);
+
+ /**
+ * Removes attribute with the given name from the context.
+ *
+ * @param id the attribute name.
+ *
+ * @return attribute value, or {@code null} if not set.
+ */
+
+ Object removeAttribute(String id);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/HttpHeaders.java b/api/src/main/java/com/yanzhenjie/andserver/http/HttpHeaders.java
new file mode 100644
index 0000000000000000000000000000000000000000..2944a5b625afdd15e533e41b47bd0dea7436463f
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/HttpHeaders.java
@@ -0,0 +1,352 @@
+/*
+ * 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.http;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/7.
+ */
+public interface HttpHeaders {
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.1
+ */
+ String ACCEPT = "Accept";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.2
+ */
+ String ACCEPT_CHARSET = "Accept-Charset";
+
+ /**
+ * Access-Control-Allow-Credentials.
+ */
+ String Access_Control_Allow_Credentials = "Access-Control-Allow-Credentials";
+
+ /**
+ * Access-Control-Allow-Headers.
+ */
+ String Access_Control_Allow_Headers = "Access-Control-Allow-Headers";
+
+ /**
+ * Access-Control-Allow-Methods.
+ */
+ String Access_Control_Allow_Methods = "Access-Control-Allow-Methods";
+
+ /**
+ * Access-Control-Allow-Origin.
+ */
+ String Access_Control_Allow_Origin = "Access-Control-Allow-Origin";
+
+ /**
+ * Access-Control-Expose-Headers.
+ */
+ String Access_Control_Expose_Headers = "Access-Control-Expose-Headers";
+
+ /**
+ * Access-Control-Max-Age.
+ */
+ String Access_Control_Max_Age = "Access-Control-Max-Age";
+
+ /**
+ * Access-Control-Request-Headers.
+ */
+ String Access_Control_Request_Headers = "Access-Control-Request-Headers";
+
+ /**
+ * Access-Control-Request-Method.
+ */
+ String Access_Control_Request_Method = "Access-Control-Request-Method";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.3
+ */
+ String ACCEPT_ENCODING = "Accept-Encoding";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.4
+ */
+ String ACCEPT_LANGUAGE = "Accept-Language";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.5
+ */
+ String ACCEPT_RANGES = "Accept-Ranges";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.6
+ */
+ String AGE = "Age";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.1, RFC 2616 (HTTP/1.1) Section 14.7
+ */
+ String ALLOW = "Allow";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.2, RFC 2616 (HTTP/1.1) Section 14.8
+ */
+ String AUTHORIZATION = "Authorization";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.9
+ */
+ String CACHE_CONTROL = "Cache-Control";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.10
+ */
+ String CONNECTION = "Connection";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.3, RFC 2616 (HTTP/1.1) Section 14.11
+ */
+ String CONTENT_ENCODING = "Content-Encoding";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.12
+ */
+ String CONTENT_LANGUAGE = "Content-Language";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.4, RFC 2616 (HTTP/1.1) Section 14.13
+ */
+ String CONTENT_LENGTH = "Content-Length";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.14
+ */
+ String CONTENT_LOCATION = "Content-Location";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.15
+ */
+ String CONTENT_MD5 = "Content-MD5";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.16
+ */
+ String CONTENT_RANGE = "Content-Range";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.5, RFC 2616 (HTTP/1.1) Section 14.17
+ */
+ String CONTENT_TYPE = "Content-Type";
+
+ /**
+ * RFC 6265 (HTTP/1.0) Section 4.2
+ */
+ String COOKIE = "Cookie";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.6, RFC 2616 (HTTP/1.1) Section 14.18
+ */
+ String DATE = "Date";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.1
+ */
+ String DAV = "Dav";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.2
+ */
+ String DEPTH = "Depth";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.3
+ */
+ String DESTINATION = "Destination";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.19
+ */
+ String ETAG = "ETag";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.20
+ */
+ String EXPECT = "Expect";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.7, RFC 2616 (HTTP/1.1) Section 14.21
+ */
+ String EXPIRES = "Expires";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.8, RFC 2616 (HTTP/1.1) Section 14.22
+ */
+ String FROM = "From";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.23
+ */
+ String HOST = "Host";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.4
+ */
+ String IF = "If";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.24
+ */
+ String IF_MATCH = "If-Match";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.9, RFC 2616 (HTTP/1.1) Section 14.25
+ */
+ String IF_MODIFIED_SINCE = "If-Modified-Since";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.26
+ */
+ String IF_NONE_MATCH = "If-None-Match";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.27
+ */
+ String IF_RANGE = "If-Range";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.28
+ */
+ String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.10, RFC 2616 (HTTP/1.1) Section 14.29
+ */
+ String LAST_MODIFIED = "Last-Modified";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.11, RFC 2616 (HTTP/1.1) Section 14.30
+ */
+ String LOCATION = "Location";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.5
+ */
+ String LOCK_TOKEN = "Lock-Token";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.31
+ */
+ String MAX_FORWARDS = "Max-Forwards";
+
+ /**
+ * Origin.
+ */
+ String ORIGIN = "Origin";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.6
+ */
+ String OVERWRITE = "Overwrite";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.12, RFC 2616 (HTTP/1.1) Section 14.32
+ */
+ String PRAGMA = "Pragma";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.33
+ */
+ String PROXY_AUTHENTICATE = "Proxy-Authenticate";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.34
+ */
+ String PROXY_AUTHORIZATION = "Proxy-Authorization";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.35
+ */
+ String RANGE = "Range";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.13, RFC 2616 (HTTP/1.1) Section 14.36
+ */
+ String REFERER = "Referer";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.37
+ */
+ String RETRY_AFTER = "Retry-After";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.14, RFC 2616 (HTTP/1.1) Section 14.38
+ */
+ String SERVER = "Server";
+
+ /**
+ * RFC 6265 (HTTP/1.0) Section 4.1, RFC 2109 (Http/1.1) Section 4.2.2
+ */
+ String SET_COOKIE = "Set-Cookie";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.7
+ */
+ String STATUS_URI = "Status-URI";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.39
+ */
+ String TE = "TE";
+
+ /**
+ * RFC 2518 (WevDAV) Section 9.8
+ */
+ String TIMEOUT = "Timeout";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.40
+ */
+ String TRAILER = "Trailer";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.41
+ */
+ String TRANSFER_ENCODING = "Transfer-Encoding";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.42
+ */
+ String UPGRADE = "Upgrade";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.15, RFC 2616 (HTTP/1.1) Section 14.43
+ */
+ String USER_AGENT = "User-Agent";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.44
+ */
+ String VARY = "Vary";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.45
+ */
+ String VIA = "Via";
+
+ /**
+ * RFC 2616 (HTTP/1.1) Section 14.46
+ */
+ String WARNING = "Warning";
+
+ /**
+ * RFC 1945 (HTTP/1.0) Section 10.16, RFC 2616 (HTTP/1.1) Section 14.47
+ */
+ String WWW_AUTHENTICATE = "WWW-Authenticate";
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/HttpMethod.java b/api/src/main/java/com/yanzhenjie/andserver/http/HttpMethod.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000f7dc1bccccbd5f3a47ff8837b01cab107d24
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/HttpMethod.java
@@ -0,0 +1,106 @@
+/*
+ * 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.http;
+
+import java.util.Locale;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/29.
+ */
+public enum HttpMethod {
+
+ GET("GET"),
+ HEAD("HEAD"),
+ POST("POST"),
+ PUT("PUT"),
+ PATCH("PATCH"),
+ DELETE("DELETE"),
+ OPTIONS("OPTIONS"),
+ TRACE("TRACE");
+
+ private String value;
+
+ HttpMethod(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ /**
+ * Whether to allow the body to be transmitted.
+ *
+ * @return true, otherwise is false.
+ */
+ public boolean allowBody() {
+ switch (this) {
+ case POST:
+ case PUT:
+ case PATCH:
+ case DELETE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Reverse the text for the request value.
+ *
+ * @param method value text, such as: GET, POST.
+ *
+ * @return {@link HttpMethod}.
+ */
+ public static HttpMethod reverse(String method) {
+ method = method.toUpperCase(Locale.ENGLISH);
+ switch (method) {
+ case "GET": {
+ return GET;
+ }
+ case "HEAD": {
+ return HEAD;
+ }
+ case "POST": {
+ return POST;
+ }
+ case "PUT": {
+ return PUT;
+ }
+ case "PATCH": {
+ return PATCH;
+ }
+ case "DELETE": {
+ return DELETE;
+ }
+ case "OPTIONS": {
+ return OPTIONS;
+ }
+ case "TRACE": {
+ return TRACE;
+ }
+ default: {
+ String message = String.format("The value %1$s is not supported.", method);
+ throw new UnsupportedOperationException(message);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/HttpRequest.java b/api/src/main/java/com/yanzhenjie/andserver/http/HttpRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..978243fd092b12c35f0e2bdc7c1c556f75618d84
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/HttpRequest.java
@@ -0,0 +1,351 @@
+/*
+ * 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.http;
+
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+import com.yanzhenjie.andserver.http.session.Session;
+import com.yanzhenjie.andserver.util.MediaType;
+import com.yanzhenjie.andserver.util.MultiValueMap;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/12.
+ */
+public interface HttpRequest extends HttpContext, HttpHeaders {
+
+ String SESSION_NAME = "ASESSIONID";
+
+ /**
+ * Returns {@link HttpMethod} with which this request was made.
+ */
+
+ HttpMethod getMethod();
+
+ /**
+ * Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP
+ * request. The web container does not decode this String.
+ *
+ * E.g.
| First line of HTTP request | Returned Value |
|---|
| POST /some/path.html
+ * HTTP/1.1 | /some/path.html |
| GET http://foo.bar/a.html HTTP/1.0 | /a.html | |
+ * | HEAD /xyz?a=b HTTP/1.1 | /xyz |
+ */
+
+ String getURI();
+
+ /**
+ * Get the path to this request.
+ */
+
+ String getPath();
+
+ /**
+ * Returns an {@link List} of {@code String} objects containing the names of the query parameters contained in url of
+ * this request, or empty {@link List} if there are no query parameters.
+ *
+ * @return an {@link List} of {@code String}.
+ */
+
+ List getQueryNames();
+
+ /**
+ * Returns the value of a request parameter as a {@code String} object, or {@code null} if the parameter does not
+ * exist.
+ *
+ * You should only use this method when you are sure the query parameter has only one value. If the query
+ * parameter might have more than one value, use {@link #getQueries(String)}.
+ *
+ * @param name a {@code String} specifying the name of the query parameter.
+ *
+ * @return the single value of the query parameter.
+ *
+ * @see #getQueries(String)
+ */
+
+ String getQuery(String name);
+
+ /**
+ * Returns an {@link List} of {@code String} objects containing all of the values the given query parameter name, or
+ * empty {@link List} if the query parameter does not exist.
+ *
+ * @param name the name of the query parameter.
+ *
+ * @return an {@link List} of {@code String} containing the parameter's values.
+ *
+ * @see #getQuery(String)
+ */
+
+ List getQueries(String name);
+
+ /**
+ * Returns {@link MultiValueMap} of the query parameters of this request, or empty {@link MultiValueMap} if there are no
+ * query parameters.
+ *
+ * @return a {@link MultiValueMap} containing query parameter names as keys and query parameter values as map values.
+ */
+
+ MultiValueMap getQuery();
+
+ /**
+ * Returns an {@link List} of all the header names this request contains, or empty {@link List} if the request has no
+ * headers.
+ */
+
+ List getHeaderNames();
+
+ /**
+ * Returns the value of the specified request header as a {@code String}. If the request did not include a header of the
+ * specified name, this method returns {@code null}. If there are multiple headers with the same name, this method
+ * returns the first head in the request.
+ *
+ * @param name a {@code String} specifying the header name.
+ *
+ * @see #getHeaders(String)
+ */
+
+ String getHeader(String name);
+
+ /**
+ * Returns all the values of the specified request header as an {@link List} of {@code String}.
+ *
+ * Some headers, such as {@code Accept-Language} can be sent by clients as several headers each with a different
+ * value rather than sending the header as a comma separated list.
+ *
+ *
If the request did not include any headers of the specified name, this method returns an empty {@link List}.
+ *
+ * @param name a {@code String} specifying the header name.
+ */
+
+ List getHeaders(String name);
+
+ /**
+ * Returns the value of the specified request header as a {@code long} value that represents a {@link Date} object. Use
+ * this method with headers that contain dates, e.g. {@code If-Modified-Since}.
+ *
+ * The date is returned as the number of milliseconds since January 1, 1970 GMT. The header name is case
+ * insensitive.
+ *
+ *
If the request did not have a header of the specified name, this method returns -1. If the header can't be
+ * converted to a date, the method throws an {@link IllegalStateException}.
+ *
+ * @param name a {@code String} specifying the name of the header.
+ */
+ long getDateHeader(String name);
+
+ /**
+ * Returns the value of the specified request header as an {@code int}. If the request does not have a header of the
+ * specified name, this method returns -1. If the header cannot be converted to an integer, this method throws a {@link
+ * IllegalStateException}.
+ *
+ * @param name a {@code String} specifying the name of a request header.
+ */
+ int getIntHeader(String name);
+
+ /**
+ * Returns the preferred {@link MediaType} that the client will accept content in, based on the Accept header.
+ */
+
+ MediaType getAccept();
+
+ /**
+ * Returns a {@link List} of {@link MediaType} objects indicating, in decreasing order starting with the preferred
+ * MediaType, the media types that are acceptable to the client based on the {@code Accept} header. If the client
+ * request doesn't provide an {@code Accept} header, this method returns a empty {@link List}.
+ *
+ * @return a {@link List} of {@link MediaType} objects indicating.
+ */
+
+ List getAccepts();
+
+ /**
+ * Returns the preferred {@link Locale} that the client will accept content in, based on the {@code Accept-Language}
+ * header. If the client request doesn't provide an {@code Accept-Language} header, this method returns the default
+ * locale for the server.
+ *
+ * @return the preferred {@link Locale} for the client.
+ */
+
+ Locale getAcceptLanguage();
+
+ /**
+ * Returns a {@link List} of {@link Locale} objects indicating, in decreasing order starting with the preferred {@link
+ * Locale}, the locales that are acceptable to the client based on the {@code Accept-Language} header. If the client
+ * request doesn't provide an {@code Accept-Language} header, this method returns a {@link List} containing one {@link
+ * Locale}, the default locale for the server.
+ *
+ * @return an {@link List} of preferred {@link Locale} objects for the client.
+ */
+
+ List getAcceptLanguages();
+
+ /**
+ * Gets the value of the cookie with the specified name.
+ *
+ * @param name the name of value.
+ *
+ * @return the value or null if the cookie with the specified name does not exist.
+ */
+
+ String getCookieValue(String name);
+
+ /**
+ * Return a {@link Cookie} object, if there is no cookie corresponding to this name, it returns {@code null}.
+ *
+ * @param name cookie name.
+ *
+ * @return a {@link Cookie} object or null if there is no cookie corresponding to this name .
+ */
+
+ Cookie getCookie(String name);
+
+ /**
+ * Returns an {@link List} containing all of the {@link Cookie} objects the client sent with this request. This method
+ * returns {@code null} if no cookies were sent.
+ */
+
+ List getCookies();
+
+ /**
+ * Returns the length, in bytes, of the request body and made available by the input stream, or -1 if the length is not
+ * known.
+ *
+ * @return a long containing the length of the request body or -1L if the length is not known.
+ */
+ long getContentLength();
+
+ /**
+ * Returns the MIME type of the body of the request, or {@code null} if the type is not known.
+ *
+ * @return a {@code String} containing the name of the MIME type of the request, or null if the type is not known.
+ */
+
+ MediaType getContentType();
+
+ /**
+ * Returns an {@link List} of {@code String} containing the names of the parameters contained in this request. If the
+ * request has no parameters, the method returns an empty {@link List}.
+ *
+ * @return an {@link List} of {@code String} objects.
+ */
+
+ List getParameterNames();
+
+ /**
+ * Returns the value of a request parameter as a {@code String}, or {@code null} if the parameter does not exist.
+ *
+ * You should only use this method when you are sure the parameter has only one value. If the parameter might
+ * have more than one value, use {@link #getParameters(String)}.
+ *
+ * @param name a {@code String} specifying the name of the parameter.
+ *
+ * @return the single value of the parameter.
+ *
+ * @see #getParameters(String)
+ */
+
+ String getParameter(String name);
+
+ /**
+ * Returns an array of {@code String} objects containing all of the values the given request parameter has, or {@code
+ * null} if the parameter does not exist.
+ *
+ * @param name a {@code String} containing the name of the parameter whose value is requested.
+ *
+ * @return an array of {@code String} objects containing the parameter's values.
+ *
+ * @see #getParameter(String)
+ */
+
+ List getParameters(String name);
+
+ /**
+ * Returns {@link MultiValueMap} of the parameters of this request, or empty {@link MultiValueMap} if there are no
+ * parameters.
+ *
+ * @return a {@link MultiValueMap} containing parameter names as keys and parameter values as map values.
+ */
+
+ MultiValueMap getParameter();
+
+ /**
+ * Get the response body.
+ *
+ * @return {@link RequestBody}.
+ *
+ * @throws UnsupportedOperationException if the request method does not allow the body to be sent.
+ */
+
+ RequestBody getBody();
+
+ /**
+ * Returns the current {@code Session} associated with this request or, if there is no current session returns a new
+ * session.
+ *
+ * @return the session associated with this request or a new session.
+ *
+ * @see #getSession()
+ */
+
+ Session getValidSession();
+
+ /**
+ * Returns the current session associated with this request, or if the request does not have a session, creates one.
+ *
+ * @see #getValidSession()
+ */
+
+ Session getSession();
+
+ /**
+ * Change the session id of the current session associated with this request and return the new session id, if there is
+ * no session associated with the request, an {@link IllegalStateException} is thrown.
+ *
+ * @return the new session id.
+ *
+ * @see #getSession()
+ * @see #getValidSession()
+ */
+
+ String changeSessionId();
+
+ /**
+ * Checks whether the requested session ID is still valid.
+ *
+ * If the client did not specify any session ID, this method returns {@code false}.
+ *
+ * @see #getSession()
+ * @see #getValidSession()
+ */
+ boolean isSessionValid();
+
+ /**
+ * Returns a {@link RequestDispatcher} object that acts as a wrapper for the resource located at the given path.
+ *
+ * @return a {@link RequestDispatcher} object or null if the handler corresponding to the path is not
+ * found.
+ */
+
+ RequestDispatcher getRequestDispatcher(String path);
+
+ /**
+ * Get the http context of this request.
+ *
+ * @return {@link HttpContext}.
+ */
+ HttpContext getContext();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/HttpResponse.java b/api/src/main/java/com/yanzhenjie/andserver/http/HttpResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..9074c6d0d56dc16e059463c5d2e1a07ab94eec2c
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/HttpResponse.java
@@ -0,0 +1,158 @@
+/*
+ * 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.http;
+
+
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/12.
+ */
+public interface HttpResponse extends StatusCode, HttpHeaders {
+
+ /**
+ * Sets the status code for this response.
+ *
+ *
This method preserves any cookies and other response headers.
+ *
+ *
Valid status codes are those in the 2XX, 3XX, 4XX, and 5XX ranges. Other status codes will be considered to be
+ * specific.
+ */
+ void setStatus(int sc);
+
+ /**
+ * Gets the current status code of this response.
+ *
+ * @return status code.
+ */
+ int getStatus();
+
+ /**
+ * Sets a response header with the given name and value. If the header had already been set, the new value overwrites
+ * the previous one. The {@link #containsHeader(String)} method can be used to test for the presence of a header before
+ * setting its value.
+ *
+ * @see #containsHeader(String)
+ * @see #addHeader(String, String)
+ */
+ void setHeader(String name, String value);
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ *
+ * @see #setHeader(String, String)
+ */
+ void addHeader(String name, String value);
+
+ /**
+ * Gets the value of the response header with the given name.
+ *
+ *
If a response header with the given name exists and contains multiple values, the value that was added first
+ * will be returned.
+ *
+ * @param name the name of the response header.
+ *
+ * @return the value of the response header with the given name, or {@code null} if no header with the given name has
+ * been set on this response.
+ */
+
+ String getHeader(String name);
+
+ /**
+ * Sets a response header with the given name and date-value. The date is specified in terms of milliseconds since the
+ * epoch.
+ *
+ *
If the header had already been set, the new value overwrites the previous one. The {@link
+ * #containsHeader(String)} method can be used to test for the presence of a header before setting its value.
+ *
+ * @see #containsHeader(String)
+ * @see #addDateHeader(String, long)
+ */
+ void setDateHeader(String name, long date);
+
+ /**
+ * Adds a response header with the given name and date-value. The date is specified in terms of milliseconds since the
+ * epoch. This method allows response headers to have multiple values.
+ *
+ * @see #setDateHeader(String, long)
+ */
+ void addDateHeader(String name, long date);
+
+ /**
+ * Sets a response header with the given name and integer value. If the header had already been set, the new value
+ * overwrites the previous one. The {@link #containsHeader(String)} method can be used to test for the presence of a
+ * header before setting its value.
+ *
+ * @see #containsHeader(String)
+ * @see #addIntHeader(String, int)
+ */
+ void setIntHeader(String name, int value);
+
+ /**
+ * Adds a response header with the given name and integer value. This method allows response headers to have multiple
+ * values.
+ *
+ * @see #setIntHeader(String, int)
+ */
+ void addIntHeader(String name, int value);
+
+ /**
+ * Returns a boolean indicating whether the named response header has already been set.
+ */
+ boolean containsHeader(String name);
+
+ /**
+ * Gets the values of the response header with the given name.
+ *
+ * @param name the name of the response header.
+ *
+ * @return a {@link List} of the values of the response header with the given name.
+ */
+
+ List getHeaders(String name);
+
+ /**
+ * Gets the names of the headers of this response.
+ *
+ * @return a {@link Enumeration} of the names of the headers of this response.
+ */
+
+ List getHeaderNames();
+
+ /**
+ * Adds the specified cookie to the response. This method can be called multiple times to set more than one cookie.
+ *
+ * @param cookie the cookie to return to the client.
+ */
+ void addCookie(Cookie cookie);
+
+ /**
+ * Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer.
+ * The buffer will be replaced with the data set by this method. Calling this method sets the status code to {@link
+ * StatusCode#SC_FOUND}.
+ */
+ void sendRedirect(String location);
+
+ /**
+ * Set the response body.
+ *
+ * @param body write the message content sent to the client.
+ */
+ void setBody(ResponseBody body);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/Modified.java b/api/src/main/java/com/yanzhenjie/andserver/http/Modified.java
new file mode 100644
index 0000000000000000000000000000000000000000..4cb9ced59c97ff7af90dd0149024c5e56e59ab5f
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/Modified.java
@@ -0,0 +1,206 @@
+/*
+ * 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.http;
+
+import com.yanzhenjie.andserver.util.StringUtils;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.yanzhenjie.andserver.util.HttpDateFormat.parseDate;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/29.
+ */
+public class Modified implements HttpHeaders {
+
+ private static final Pattern ETAG_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
+
+ private HttpRequest mRequest;
+ private HttpResponse mResponse;
+
+ private boolean isNotModified;
+
+ public Modified( HttpRequest request, HttpResponse response) {
+ this.mRequest = request;
+ this.mResponse = response;
+ }
+
+ /**
+ * Process {@code Modified} according to given the supplied {@code Last-Modified}.
+ *
+ * @param lastModified the last-modified timestamp in milliseconds of the resource.
+ *
+ * @return true if the request does not require further processing.
+ */
+ public boolean process(long lastModified) {
+ return process(null, lastModified);
+ }
+
+ /**
+ * Process {@code Modified} according to given the supplied {@code ETag}.
+ *
+ * @param eTag the tag of the resource.
+ *
+ * @return true if the request does not require further processing.
+ */
+ public boolean process(String eTag) {
+ return process(eTag, -1);
+ }
+
+ /**
+ * Process {@code Modified} according to given the supplied {@code Last-Modified} and {@code ETag}.
+ *
+ * @param eTag the tag of the resource.
+ * @param lastModified he last-modified timestamp in milliseconds of the resource.
+ *
+ * @return true if the request does not require further processing.
+ */
+ public boolean process(String eTag, long lastModified) {
+ if (isNotModified) {
+ return true;
+ }
+
+ // See https://tools.ietf.org/html/rfc7232#section-6
+ if (validateIfUnmodifiedSince(lastModified)) {
+ if (!isNotModified) {
+ mResponse.setStatus(StatusCode.SC_LENGTH_REQUIRED);
+ }
+ return isNotModified;
+ }
+
+ // First, prioritized.
+ boolean validated = validateIfNoneMatch(eTag);
+ // Second.
+ if (!validated) {
+ validateIfModifiedSince(lastModified);
+ }
+
+ // Update response
+ HttpMethod method = mRequest.getMethod();
+ boolean isGetHead = (method == HttpMethod.GET || method == HttpMethod.HEAD);
+ if (isNotModified) {
+ mResponse.setStatus(isGetHead ? StatusCode.SC_NOT_MODIFIED : StatusCode.SC_LENGTH_REQUIRED);
+ }
+ if (isGetHead) {
+ if (lastModified > 0 && mResponse.getHeader(LAST_MODIFIED) == null) {
+ mResponse.setDateHeader(LAST_MODIFIED, lastModified);
+ }
+ if (!StringUtils.isEmpty(eTag) && mResponse.getHeader(ETAG) == null) {
+ mResponse.setHeader(ETAG, padETagIfNecessary(eTag));
+ }
+ mResponse.setHeader(CACHE_CONTROL, "private");
+ }
+ return isNotModified;
+ }
+
+ private boolean validateIfNoneMatch(String eTag) {
+ if (StringUtils.isEmpty(eTag)) {
+ return false;
+ }
+
+ List ifNoneMatch = mRequest.getHeaders(IF_NONE_MATCH);
+ if (ifNoneMatch.isEmpty()) {
+ return false;
+ }
+
+ // We will perform this validation...
+ eTag = padETagIfNecessary(eTag);
+ for (String clientETags: ifNoneMatch) {
+ Matcher eTagMatcher = ETAG_PATTERN.matcher(clientETags);
+ // Compare weak/strong ETags as per https://tools.ietf.org/html/rfc7232#section-2.3
+ while (eTagMatcher.find()) {
+ if (!StringUtils.isEmpty(eTagMatcher.group()) &&
+ eTag.replaceFirst("^W/", "").equals(eTagMatcher.group(3))) {
+ isNotModified = true;
+ break;
+ }
+ }
+ }
+ return true;
+ }
+
+ private String padETagIfNecessary(String eTag) {
+ if (StringUtils.isEmpty(eTag)) {
+ return eTag;
+ }
+ if ((eTag.startsWith("\"") || eTag.startsWith("W/\"")) && eTag.endsWith("\"")) {
+ return eTag;
+ }
+ return "\"" + eTag + "\"";
+ }
+
+ private boolean validateIfModifiedSince(long lastModifiedTimestamp) {
+ if (lastModifiedTimestamp < 0) {
+ return false;
+ }
+
+ long ifModifiedSince = parseDateHeader(IF_MODIFIED_SINCE);
+ if (ifModifiedSince == -1) {
+ return false;
+ }
+
+ // We will perform this validation...
+ isNotModified = ifModifiedSince >= lastModifiedTimestamp;
+ return true;
+ }
+
+ private boolean validateIfUnmodifiedSince(long lastModifiedTimestamp) {
+ if (lastModifiedTimestamp < 0) {
+ return false;
+ }
+ long ifUnmodifiedSince = parseDateHeader(IF_UNMODIFIED_SINCE);
+ if (ifUnmodifiedSince == -1) {
+ return false;
+ }
+ // We will perform this validation...
+ isNotModified = ifUnmodifiedSince >= lastModifiedTimestamp;
+ return true;
+ }
+
+ private long parseDateHeader(String headerName) {
+ long dateValue = -1;
+ try {
+ dateValue = mRequest.getDateHeader(headerName);
+ } catch (IllegalStateException ex) {
+ String headerValue = mRequest.getHeader(headerName);
+ if (StringUtils.isEmpty(headerValue)) {
+ return -1;
+ }
+ // Possibly an IE 10 style value: "Wed, 09 Apr 2014 09:57:42 GMT; length=13774"
+ int separatorIndex = headerValue.indexOf(';');
+ if (separatorIndex != -1) {
+ String datePart = headerValue.substring(0, separatorIndex);
+ dateValue = parseDateValue(datePart);
+ }
+ }
+ return dateValue;
+ }
+
+ private long parseDateValue(String headerValue) {
+ if (headerValue == null) {
+ return -1;
+ }
+
+ if (headerValue.length() >= 3) {
+ // Short "0" or "-1" like values are never valid HTTP date headers...
+ // Let's only bother with SimpleDateFormat parsing for long enough values.
+ return parseDate(headerValue);
+ }
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/RequestBody.java b/api/src/main/java/com/yanzhenjie/andserver/http/RequestBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..20dc7185004608d45ed6f5b52ba1e0ae6b30b71b
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/RequestBody.java
@@ -0,0 +1,70 @@
+/*
+ * 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.http;
+
+
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/9.
+ */
+public interface RequestBody {
+
+ /**
+ * Retrieve the character encoding for the request.
+ *
+ * @return the character encoding for the request.
+ */
+ String contentEncoding();
+
+ /**
+ * Get the {@code Content-Length} of the message body, if the length is unknown, return a negative value.
+ *
+ * @return message length.
+ */
+ long length();
+
+ /**
+ * Get the {@code Content-Type} of the message body, including charset.
+ *
+ * @return e.g. {@code application/json; charset=utf-8}, or {@code null} if the content type is unknown.
+ */
+
+ MediaType contentType();
+
+ /**
+ * Returns a content stream of this body.
+ *
+ * @return content stream of this body.
+ *
+ * @throws IOException if the stream could not be created.
+ */
+
+ InputStream stream() throws IOException;
+
+ /**
+ * Convert the request body to a String.
+ *
+ * @return string.
+ *
+ * @throws IOException if the stream could not be created.
+ */
+
+ String string() throws IOException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/RequestDispatcher.java b/api/src/main/java/com/yanzhenjie/andserver/http/RequestDispatcher.java
new file mode 100644
index 0000000000000000000000000000000000000000..04c2dd51cd568b346faa2c34c6341fc3bcc6fd61
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/RequestDispatcher.java
@@ -0,0 +1,32 @@
+/*
+ * 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.http;
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/8/31.
+ */
+public interface RequestDispatcher {
+
+ /**
+ * Forwards a request from a handler to another handler on the server.
+ *
+ * @param request the current request.
+ * @param response the current response.
+ */
+ void forward(HttpRequest request, HttpResponse response);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/RequestWrapper.java b/api/src/main/java/com/yanzhenjie/andserver/http/RequestWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..e05297969f71ece54cbc2ec95f83868d3f46e0c3
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/RequestWrapper.java
@@ -0,0 +1,250 @@
+/*
+ * 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.http;
+
+
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+import com.yanzhenjie.andserver.http.session.Session;
+import com.yanzhenjie.andserver.util.MediaType;
+import com.yanzhenjie.andserver.util.MultiValueMap;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/6.
+ */
+public class RequestWrapper implements HttpRequest {
+
+ private HttpRequest mRequest;
+
+ public RequestWrapper(HttpRequest request) {
+ this.mRequest = request;
+ }
+
+ /**
+ * Get the original request object.
+ *
+ * @return {@link HttpRequest}.
+ */
+ public HttpRequest getRequest() {
+ return mRequest;
+ }
+
+
+ @Override
+ public HttpMethod getMethod() {
+ return mRequest.getMethod();
+ }
+
+
+ @Override
+ public String getURI() {
+ return mRequest.getURI();
+ }
+
+
+ @Override
+ public String getPath() {
+ return mRequest.getPath();
+ }
+
+
+ @Override
+ public List getQueryNames() {
+ return mRequest.getQueryNames();
+ }
+
+
+ @Override
+ public String getQuery( String name) {
+ return mRequest.getQuery(name);
+ }
+
+
+ @Override
+ public List getQueries( String name) {
+ return mRequest.getQueries(name);
+ }
+
+
+ @Override
+ public MultiValueMap getQuery() {
+ return mRequest.getQuery();
+ }
+
+
+ @Override
+ public List getHeaderNames() {
+ return mRequest.getHeaderNames();
+ }
+
+
+ @Override
+ public String getHeader( String name) {
+ return mRequest.getHeader(name);
+ }
+
+
+ @Override
+ public List getHeaders( String name) {
+ return mRequest.getHeaders(name);
+ }
+
+ @Override
+ public long getDateHeader( String name) {
+ return mRequest.getDateHeader(name);
+ }
+
+ @Override
+ public int getIntHeader( String name) {
+ return mRequest.getIntHeader(name);
+ }
+
+
+ @Override
+ public MediaType getAccept() {
+ return mRequest.getAccept();
+ }
+
+
+ @Override
+ public List getAccepts() {
+ return mRequest.getAccepts();
+ }
+
+
+ @Override
+ public Locale getAcceptLanguage() {
+ return mRequest.getAcceptLanguage();
+ }
+
+
+ @Override
+ public List getAcceptLanguages() {
+ return mRequest.getAcceptLanguages();
+ }
+
+
+ @Override
+ public String getCookieValue(String name) {
+ return mRequest.getCookieValue(name);
+ }
+
+
+ @Override
+ public Cookie getCookie(String name) {
+ return mRequest.getCookie(name);
+ }
+
+
+ @Override
+ public List getCookies() {
+ return mRequest.getCookies();
+ }
+
+ @Override
+ public long getContentLength() {
+ return mRequest.getContentLength();
+ }
+
+
+ @Override
+ public MediaType getContentType() {
+ return mRequest.getContentType();
+ }
+
+
+ @Override
+ public List getParameterNames() {
+ return mRequest.getParameterNames();
+ }
+
+
+ @Override
+ public String getParameter( String name) {
+ return mRequest.getParameter(name);
+ }
+
+
+ @Override
+ public List getParameters( String name) {
+ return mRequest.getParameters(name);
+ }
+
+
+ @Override
+ public MultiValueMap getParameter() {
+ return mRequest.getParameter();
+ }
+
+
+ @Override
+ public RequestBody getBody() throws UnsupportedOperationException {
+ return mRequest.getBody();
+ }
+
+
+ @Override
+ public Session getValidSession() {
+ return mRequest.getValidSession();
+ }
+
+
+ @Override
+ public Session getSession() {
+ return mRequest.getSession();
+ }
+
+
+ @Override
+ public String changeSessionId() {
+ return mRequest.changeSessionId();
+ }
+
+ @Override
+ public boolean isSessionValid() {
+ return mRequest.isSessionValid();
+ }
+
+
+ @Override
+ public RequestDispatcher getRequestDispatcher( String path) {
+ return mRequest.getRequestDispatcher(path);
+ }
+
+ @Override
+ public HttpContext getContext() {
+ return mRequest.getContext();
+ }
+
+
+ @Override
+ public Object getAttribute( String id) {
+ return mRequest.getAttribute(id);
+ }
+
+ @Override
+ public void setAttribute( String id, Object obj) {
+ mRequest.setAttribute(id, obj);
+ }
+
+
+ @Override
+ public Object removeAttribute( String id) {
+ return mRequest.removeAttribute(id);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/ResponseBody.java b/api/src/main/java/com/yanzhenjie/andserver/http/ResponseBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..fede7df0e548b458a4b237eff56037dd79fc3ef1
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/ResponseBody.java
@@ -0,0 +1,58 @@
+/*
+ * 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.http;
+
+
+import com.yanzhenjie.andserver.util.MediaType;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/3.
+ */
+public interface ResponseBody {
+
+ /**
+ * Can it be reused?
+ *
+ * @return true, otherwise is false.
+ */
+ boolean isRepeatable();
+
+ /**
+ * Get the content-length of the message body, if the length is unknown, return a negative value.
+ *
+ * @return message length.
+ */
+ long contentLength();
+
+ /**
+ * Get the content-type of the message body, including charset.
+ *
+ * @return e.g. {@code application/json; charset=utf-8}.
+ */
+
+ MediaType contentType();
+
+ /**
+ * Write the body to the output stream.
+ *
+ * @param output the output stream to write the body.
+ */
+ void writeTo(OutputStream output) throws IOException;
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/ResponseWrapper.java b/api/src/main/java/com/yanzhenjie/andserver/http/ResponseWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b00f5d434e4bce975f3041991da77f64049f6d7e
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/ResponseWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * 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.http;
+
+
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/6.
+ */
+public class ResponseWrapper implements HttpResponse {
+
+ private HttpResponse mResponse;
+
+ public ResponseWrapper(HttpResponse response) {
+ this.mResponse = response;
+ }
+
+ /**
+ * Get the original response object.
+ *
+ * @return {@link HttpResponse}.
+ */
+ public HttpResponse getResponse() {
+ return mResponse;
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ mResponse.setStatus(sc);
+ }
+
+ @Override
+ public int getStatus() {
+ return mResponse.getStatus();
+ }
+
+ @Override
+ public void setHeader( String name, String value) {
+ mResponse.setHeader(name, value);
+ }
+
+ @Override
+ public void addHeader( String name, String value) {
+ mResponse.addHeader(name, value);
+ }
+
+
+ @Override
+ public String getHeader( String name) {
+ return mResponse.getHeader(name);
+ }
+
+ @Override
+ public void setDateHeader( String name, long date) {
+ mResponse.setDateHeader(name, date);
+ }
+
+ @Override
+ public void addDateHeader( String name, long date) {
+ mResponse.addDateHeader(name, date);
+ }
+
+ @Override
+ public void setIntHeader( String name, int value) {
+ mResponse.setIntHeader(name, value);
+ }
+
+ @Override
+ public void addIntHeader( String name, int value) {
+ mResponse.addIntHeader(name, value);
+ }
+
+ @Override
+ public boolean containsHeader( String name) {
+ return mResponse.containsHeader(name);
+ }
+
+
+ @Override
+ public List getHeaders( String name) {
+ return mResponse.getHeaders(name);
+ }
+
+
+ @Override
+ public List getHeaderNames() {
+ return mResponse.getHeaderNames();
+ }
+
+ @Override
+ public void addCookie( Cookie cookie) {
+ mResponse.addCookie(cookie);
+ }
+
+ @Override
+ public void sendRedirect( String location) {
+ mResponse.sendRedirect(location);
+ }
+
+ @Override
+ public void setBody(ResponseBody body) {
+ mResponse.setBody(body);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/StandardContext.java b/api/src/main/java/com/yanzhenjie/andserver/http/StandardContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b91ebda7a9a3cdf6e56a6eee484b6ac7cacf431
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/StandardContext.java
@@ -0,0 +1,48 @@
+/*
+ * 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.http;
+
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/8/31.
+ */
+public class StandardContext implements HttpContext {
+
+ private org.apache.httpcore.protocol.HttpContext mContext;
+
+ public StandardContext(org.apache.httpcore.protocol.HttpContext context) {
+ this.mContext = context;
+ }
+
+
+ @Override
+ public Object getAttribute( String id) {
+ return mContext.getAttribute(id);
+ }
+
+ @Override
+ public void setAttribute( String id, Object obj) {
+ mContext.setAttribute(id, obj);
+ }
+
+
+ @Override
+ public Object removeAttribute( String id) {
+ return mContext.removeAttribute(id);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/StandardRequest.java b/api/src/main/java/com/yanzhenjie/andserver/http/StandardRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab7d1886437f55d619d9d06a4db4cdb7629942a2
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/StandardRequest.java
@@ -0,0 +1,591 @@
+/*
+ * 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.http;
+
+import com.yanzhenjie.andserver.DispatcherHandler;
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+import com.yanzhenjie.andserver.http.cookie.CookieProcessor;
+import com.yanzhenjie.andserver.http.cookie.StandardCookieProcessor;
+import com.yanzhenjie.andserver.http.session.Session;
+import com.yanzhenjie.andserver.http.session.SessionManager;
+import com.yanzhenjie.andserver.util.*;
+import org.apache.httpcore.Header;
+import org.apache.httpcore.HttpEntity;
+import org.apache.httpcore.HttpEntityEnclosingRequest;
+import org.apache.httpcore.RequestLine;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/12.
+ */
+public class StandardRequest implements HttpRequest {
+
+ private static final CookieProcessor COOKIE_PROCESSOR = new StandardCookieProcessor();
+
+ private org.apache.httpcore.HttpRequest mRequest;
+ private HttpContext mContext;
+ private DispatcherHandler mHandler;
+ private RequestLine mRequestLine;
+ private SessionManager mSessionManager;
+
+ private Uri mUri;
+ private boolean isParsedUri;
+
+ private MultiValueMap mQuery;
+ private boolean isParsedQuery;
+
+ private List mAccepts;
+ private boolean isParsedAccept;
+
+ private List mLocales;
+ private boolean isParsedLocale;
+
+ private MultiValueMap mParameter;
+ private boolean isParsedParameter;
+
+ public StandardRequest(org.apache.httpcore.HttpRequest request, HttpContext context, DispatcherHandler handler,
+ SessionManager sessionManager) {
+ this.mRequest = request;
+ this.mContext = context;
+ this.mHandler = handler;
+ this.mRequestLine = request.getRequestLine();
+ this.mSessionManager = sessionManager;
+ }
+
+
+ @Override
+ public HttpMethod getMethod() {
+ return HttpMethod.reverse(mRequestLine.getMethod());
+ }
+
+
+ @Override
+ public String getURI() {
+ parseUri();
+ return mUri.toString();
+ }
+
+ private void parseUri() {
+ if (isParsedUri) {
+ return;
+ }
+
+ String requestLine = mRequestLine.getUri();
+ if (StringUtils.isEmpty(requestLine)) {
+ requestLine = "/";
+ }
+
+ String uri = "scheme://host:ip" + requestLine;
+ mUri = Uri.newBuilder(uri).build();
+ isParsedUri = true;
+ }
+
+ public void setPath(String path) {
+ parseUri();
+ mUri = mUri.builder().setPath(path).build();
+ }
+
+
+ @Override
+ public String getPath() {
+ parseUri();
+ return mUri.getPath();
+ }
+
+
+ @Override
+ public List getQueryNames() {
+ parseQuery();
+ return new LinkedList<>(mQuery.keySet());
+ }
+
+
+ @Override
+ public String getQuery( String name) {
+ parseQuery();
+ return mQuery.getFirst(name);
+ }
+
+
+ @Override
+ public List getQueries( String name) {
+ parseQuery();
+ List values = mQuery.get(name);
+ return (values == null || values.isEmpty()) ? Collections.emptyList() : values;
+ }
+
+
+ @Override
+ public MultiValueMap getQuery() {
+ parseQuery();
+ return mQuery;
+ }
+
+ private void parseQuery() {
+ if (isParsedQuery) {
+ return;
+ }
+ parseUri();
+
+ mQuery = mUri.getParams();
+ isParsedQuery = true;
+ }
+
+
+ @Override
+ public List getHeaderNames() {
+ Header[] headers = mRequest.getAllHeaders();
+ if (headers == null || headers.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List nameList = new ArrayList<>();
+ for (Header header: headers) {
+ nameList.add(header.getName());
+ }
+ return nameList;
+ }
+
+
+ @Override
+ public String getHeader( String name) {
+ Header header = mRequest.getFirstHeader(name);
+ return header == null ? null : header.getValue();
+ }
+
+
+ @Override
+ public List getHeaders( String name) {
+ Header[] headers = mRequest.getHeaders(name);
+ if (headers == null || headers.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List valueList = new ArrayList<>();
+ for (Header header: headers) {
+ valueList.add(header.getValue());
+ }
+ return valueList;
+ }
+
+ @Override
+ public long getDateHeader( String name) {
+ Header header = mRequest.getFirstHeader(name);
+ if (header == null) {
+ return -1;
+ }
+
+ String value = header.getValue();
+ long date = HttpDateFormat.parseDate(value);
+
+ if (date == -1) {
+ String message = String.format("The %s cannot be converted to date.", value);
+ throw new IllegalStateException(message);
+ }
+
+ return date;
+ }
+
+ @Override
+ public int getIntHeader( String name) {
+ Header header = mRequest.getFirstHeader(name);
+ if (header == null) {
+ return -1;
+ }
+
+ try {
+ return Integer.parseInt(header.getValue());
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ }
+
+
+ @Override
+ public MediaType getAccept() {
+ List mediaTypes = getAccepts();
+ return mediaTypes.isEmpty() ? null : mediaTypes.get(0);
+ }
+
+
+ @Override
+ public List getAccepts() {
+ parseAccept();
+ return mAccepts;
+ }
+
+ private void parseAccept() {
+ if (isParsedAccept) {
+ return;
+ }
+
+ mAccepts = new ArrayList<>();
+ Header[] headers = mRequest.getHeaders(ACCEPT);
+ if (headers != null && headers.length > 0) {
+ for (Header header: headers) {
+ List mediaTypes = MediaType.parseMediaTypes(header.getValue());
+ mAccepts.addAll(mediaTypes);
+ }
+ }
+ if (mAccepts.isEmpty()) {
+ mAccepts.add(MediaType.ALL);
+ }
+
+ isParsedAccept = true;
+ }
+
+
+ @Override
+ public Locale getAcceptLanguage() {
+ return getAcceptLanguages().get(0);
+ }
+
+
+ @Override
+ public List getAcceptLanguages() {
+ parseLocale();
+ return mLocales;
+ }
+
+ private void parseLocale() {
+ if (isParsedLocale) {
+ return;
+ }
+
+ mLocales = new ArrayList<>();
+ Header[] headers = mRequest.getHeaders(ACCEPT_LANGUAGE);
+ if (headers != null && headers.length > 0) {
+ for (Header header: headers) {
+ List acceptLanguages = FAcceptLanguage.parse(header.getValue());
+ for (FAcceptLanguage acceptLanguage: acceptLanguages) {
+ mLocales.add(acceptLanguage.getLocale());
+ }
+ }
+ }
+ if (mLocales.isEmpty()) {
+ mLocales.add(Locale.getDefault());
+ }
+
+ isParsedLocale = true;
+ }
+
+
+ @Override
+ public String getCookieValue(String name) {
+ Cookie cookie = getCookie(name);
+ if (cookie != null) {
+ return cookie.getValue();
+ }
+ return null;
+ }
+
+
+ @Override
+ public Cookie getCookie(String name) {
+ List cookies = getCookies();
+ if (cookies.isEmpty()) {
+ return null;
+ }
+
+ for (Cookie cookie: cookies) {
+ if (name.equalsIgnoreCase(cookie.getName())) {
+ return cookie;
+ }
+ }
+ return null;
+ }
+
+
+ @Override
+ public List getCookies() {
+ return COOKIE_PROCESSOR.parseCookieHeader(mRequest.getHeaders(COOKIE));
+ }
+
+ @Override
+ public long getContentLength() {
+ String contentLength = getHeader(CONTENT_LENGTH);
+ if (StringUtils.isEmpty(contentLength)) {
+ return -1;
+ }
+ try {
+ return Long.parseLong(contentLength);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+
+ @Override
+ public MediaType getContentType() {
+ String contentType = getHeader(CONTENT_TYPE);
+ if (contentType.isEmpty()) {
+ return null;
+ }
+ return MediaType.valueOf(contentType);
+ }
+
+
+ @Override
+ public List getParameterNames() {
+ parseParameter();
+ List paramNames = new LinkedList<>(mParameter.keySet());
+ List names = getQueryNames();
+ if (!names.isEmpty()) {
+ paramNames.addAll(names);
+ }
+ return paramNames;
+ }
+
+
+ @Override
+ public String getParameter( String name) {
+ parseParameter();
+ String value = mParameter.getFirst(name);
+ return value.isEmpty() ? getQuery(name) : value;
+ }
+
+
+ @Override
+ public List getParameters( String name) {
+ parseParameter();
+ List values = mParameter.get(name);
+ if (values == null || values.isEmpty()) {
+ return getQueries(name);
+ }
+ return values;
+ }
+
+
+ @Override
+ public MultiValueMap getParameter() {
+ parseParameter();
+ return mParameter.isEmpty() ? getQuery() : mParameter;
+ }
+
+ private void parseParameter() {
+ if (isParsedParameter) {
+ return;
+ }
+
+ if (!getMethod().allowBody()) {
+ mParameter = new LinkedMultiValueMap<>();
+ return;
+ }
+
+ MediaType mediaType = getContentType();
+ if (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)) {
+ try {
+ RequestBody body = getBody();
+ String bodyString = body == null ? "" : body.string();
+ mParameter = parseParameters(bodyString);
+ } catch (Exception ignored) {
+ }
+ }
+ if (mParameter == null) {
+ mParameter = new LinkedMultiValueMap<>();
+ }
+ isParsedParameter = true;
+ }
+
+
+ @Override
+ public RequestBody getBody() {
+ if (getMethod().allowBody()) {
+ if (mRequest instanceof HttpEntityEnclosingRequest) {
+ HttpEntityEnclosingRequest request = (HttpEntityEnclosingRequest) mRequest;
+ HttpEntity entity = request.getEntity();
+ if (entity == null) {
+ return null;
+ }
+ return new EntityToBody(entity);
+ }
+ return null;
+ }
+ throw new UnsupportedOperationException("This method does not allow body.");
+ }
+
+
+ @Override
+ public Session getValidSession() {
+ Session session = getSession();
+ if (session == null) {
+ session = mSessionManager.createSession();
+ } else if (session.isValid()) {
+ session = mSessionManager.createSession();
+ }
+
+ setAttribute(REQUEST_CREATED_SESSION, session);
+ return session;
+ }
+
+ @Override
+ public Session getSession() {
+ Object objSession = getAttribute(REQUEST_CREATED_SESSION);
+ if (objSession instanceof Session) {
+ return (Session) objSession;
+ }
+
+ List cookies = getCookies();
+ if (cookies.isEmpty()) {
+ return null;
+ }
+
+ String sessionId = null;
+ for (Cookie cookie: cookies) {
+ if (SESSION_NAME.equalsIgnoreCase(cookie.getName())) {
+ sessionId = cookie.getValue();
+ break;
+ }
+ }
+
+ if (sessionId.isEmpty()) {
+ return null;
+ }
+
+ Session session = null;
+ try {
+ session = mSessionManager.findSession(sessionId);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ return session;
+ }
+
+ @Override
+ public String changeSessionId() {
+ Session session = getSession();
+ if (session == null) {
+ throw new IllegalStateException("No session associated with this request.");
+ }
+ mSessionManager.changeSessionId(session);
+ return session.getId();
+ }
+
+ @Override
+ public boolean isSessionValid() {
+ Session session = getSession();
+ return session != null && session.isValid();
+ }
+
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return mHandler.getRequestDispatcher(this, path);
+ }
+
+ @Override
+ public HttpContext getContext() {
+ return mContext;
+ }
+
+
+ @Override
+ public Object getAttribute(String id) {
+ return mContext.getAttribute(id);
+ }
+
+ @Override
+ public void setAttribute( String id, Object obj) {
+ mContext.setAttribute(id, obj);
+ }
+
+
+ @Override
+ public Object removeAttribute( String id) {
+ return mContext.removeAttribute(id);
+ }
+
+
+ private static MultiValueMap parseParameters(String input) {
+ MultiValueMap parameters = new LinkedMultiValueMap<>();
+ StringTokenizer tokenizer = new StringTokenizer(input, "&");
+ while (tokenizer.hasMoreElements()) {
+ String element = tokenizer.nextToken();
+ int end = element.indexOf("=");
+
+ if (end > 0 && end < element.length() - 1) {
+ String key = element.substring(0, end);
+ String value = element.substring(end + 1);
+ parameters.add(key, UrlCoder.urlDecode(value, StandardCharsets.UTF_8));
+ }
+ }
+ return parameters;
+ }
+
+ private static class EntityToBody implements RequestBody {
+
+ private HttpEntity mEntity;
+
+ private EntityToBody(HttpEntity entity) {
+ this.mEntity = entity;
+ }
+
+ @Override
+ public String contentEncoding() {
+ Header encoding = mEntity.getContentType();
+ return encoding == null ? "" : encoding.getValue();
+ }
+
+ @Override
+ public long length() {
+ return mEntity.getContentLength();
+ }
+
+
+ @Override
+ public MediaType contentType() {
+ Header header = mEntity.getContentType();
+ if (header == null) {
+ return null;
+ }
+
+ String contentType = header.getValue();
+ return MediaType.valueOf(contentType);
+ }
+
+
+ @Override
+ public InputStream stream() throws IOException {
+ InputStream stream = mEntity.getContent();
+ if (contentEncoding().toLowerCase().contains("gzip")) {
+ stream = new GZIPInputStream(stream);
+ }
+ return stream;
+ }
+
+
+ @Override
+ public String string() throws IOException {
+ MimeType mimeType = contentType();
+ Charset charset = mimeType == null ? null : mimeType.getCharset();
+
+ if (charset == null) {
+ return IOUtils.toString(stream());
+ } else {
+ return IOUtils.toString(stream(), charset);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/StandardResponse.java b/api/src/main/java/com/yanzhenjie/andserver/http/StandardResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbac08aa2037de1a2026cd5523aa920a56ff0480
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/StandardResponse.java
@@ -0,0 +1,202 @@
+/*
+ * 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.http;
+
+import com.yanzhenjie.andserver.http.cookie.Cookie;
+import com.yanzhenjie.andserver.http.cookie.CookieProcessor;
+import com.yanzhenjie.andserver.http.cookie.StandardCookieProcessor;
+import com.yanzhenjie.andserver.util.HttpDateFormat;
+import com.yanzhenjie.andserver.util.MediaType;
+import org.apache.httpcore.Header;
+import org.apache.httpcore.HttpEntity;
+import org.apache.httpcore.message.BasicHeader;
+import org.apache.httpcore.util.EntityUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/12.
+ */
+public class StandardResponse implements HttpResponse {
+
+ private static final CookieProcessor COOKIE_PROCESSOR = new StandardCookieProcessor();
+
+ private org.apache.httpcore.HttpResponse mResponse;
+
+ public StandardResponse(org.apache.httpcore.HttpResponse response) {
+ this.mResponse = response;
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ mResponse.setStatusCode(sc);
+ }
+
+ @Override
+ public int getStatus() {
+ return mResponse.getStatusLine().getStatusCode();
+ }
+
+ @Override
+ public void setHeader( String name, String value) {
+ mResponse.setHeader(name, value);
+ }
+
+ @Override
+ public void addHeader( String name, String value) {
+ mResponse.addHeader(name, value);
+ }
+
+
+ @Override
+ public String getHeader( String name) {
+ Header header = mResponse.getFirstHeader(name);
+ return header == null ? null : header.getValue();
+ }
+
+ @Override
+ public void setDateHeader( String name, long date) {
+ setHeader(name, HttpDateFormat.formatDate(date));
+ }
+
+ @Override
+ public void addDateHeader( String name, long date) {
+ addHeader(name, HttpDateFormat.formatDate(date));
+ }
+
+ @Override
+ public void setIntHeader( String name, int value) {
+ setHeader(name, Integer.toString(value));
+ }
+
+ @Override
+ public void addIntHeader( String name, int value) {
+ addHeader(name, Integer.toString(value));
+ }
+
+ @Override
+ public boolean containsHeader( String name) {
+ return mResponse.containsHeader(name);
+ }
+
+
+ @Override
+ public List getHeaders( String name) {
+ Header[] headers = mResponse.getHeaders(name);
+ if (headers == null || headers.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List list = new ArrayList<>();
+ for (Header header: headers) {
+ list.add(header.getValue());
+ }
+ return list;
+ }
+
+
+ @Override
+ public List getHeaderNames() {
+ Header[] headers = mResponse.getAllHeaders();
+ if (headers == null || headers.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List list = new ArrayList<>();
+ for (Header header: headers) {
+ list.add(header.getName());
+ }
+ return list;
+ }
+
+ @Override
+ public void addCookie( Cookie cookie) {
+ addHeader(SET_COOKIE, COOKIE_PROCESSOR.generateHeader(cookie));
+ }
+
+ @Override
+ public void sendRedirect( String location) {
+ setStatus(SC_FOUND);
+ setHeader(LOCATION, location);
+ }
+
+ @Override
+ public void setBody(ResponseBody body) {
+ mResponse.setEntity(new BodyToEntity(body));
+ }
+
+ private static class BodyToEntity implements HttpEntity {
+
+ private ResponseBody mBody;
+
+ private BodyToEntity(ResponseBody body) {
+ this.mBody = body;
+ }
+
+ @Override
+ public boolean isRepeatable() {
+ return false;
+ }
+
+ @Override
+ public boolean isChunked() {
+ return false;
+ }
+
+ @Override
+ public long getContentLength() {
+ return mBody.contentLength();
+ }
+
+ @Override
+ public Header getContentType() {
+ MediaType mimeType = mBody.contentType();
+ if (mimeType == null) {
+ return null;
+ }
+ return new BasicHeader(CONTENT_TYPE, mimeType.toString());
+ }
+
+ @Override
+ public Header getContentEncoding() {
+ return null;
+ }
+
+ @Override
+ public InputStream getContent() throws IOException {
+ return null;
+ }
+
+ @Override
+ public void writeTo(OutputStream out) throws IOException {
+ mBody.writeTo(out);
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return false;
+ }
+
+ @Override
+ public void consumeContent() {
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/StatusCode.java b/api/src/main/java/com/yanzhenjie/andserver/http/StatusCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..9247a36b3322c2ab7a98086654ae4e404bf8efc6
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/StatusCode.java
@@ -0,0 +1,246 @@
+/*
+ * 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.http;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+public interface StatusCode {
+
+ /**
+ * Status code (100) indicating the client can continue.
+ */
+ int SC_CONTINUE = 100;
+
+ /**
+ * Status code (101) indicating the server is switching protocols according to Upgrade header.
+ */
+ int SC_SWITCHING_PROTOCOLS = 101;
+
+ /**
+ * Status code (200) indicating the request succeeded normally.
+ */
+ int SC_OK = 200;
+
+ /**
+ * Status code (201) indicating the request succeeded and created a new resource on the server.
+ */
+ int SC_CREATED = 201;
+
+ /**
+ * Status code (202) indicating that a request was accepted for processing, but was not completed.
+ */
+ int SC_ACCEPTED = 202;
+
+ /**
+ * Status code (203) indicating that the meta information presented by the client did not originate from the
+ * server.
+ */
+ int SC_NON_AUTHORITATIVE_INFORMATION = 203;
+
+ /**
+ * Status code (204) indicating that the request succeeded but that there was no new information to return.
+ */
+ int SC_NO_CONTENT = 204;
+
+ /**
+ * Status code (205) indicating that the agent SHOULD reset the document view which caused the request to
+ * be sent.
+ */
+ int SC_RESET_CONTENT = 205;
+
+ /**
+ * Status code (206) indicating that the server has fulfilled the partial GET request for the resource.
+ */
+ int SC_PARTIAL_CONTENT = 206;
+
+ /**
+ * Status code (300) indicating that the requested resource corresponds to any one of a set of representations, each
+ * with its own specific location.
+ */
+ int SC_MULTIPLE_CHOICES = 300;
+
+ /**
+ * Status code (301) indicating that the resource has permanently moved to a new location, and that future
+ * references should use a new URI with their requests.
+ */
+ int SC_MOVED_PERMANENTLY = 301;
+
+ /**
+ * Status code (302) indicating that the resource reside temporarily under a different URI. Since the redirection
+ * might be altered on occasion, the client should continue to use the Request-URI for future requests.(HTTP/1.1) To
+ * represent the status code (302), it is recommended to use this variable.
+ */
+ int SC_FOUND = 302;
+
+ /**
+ * Status code (303) indicating that the response to the request can be found under a different URI.
+ */
+ int SC_SEE_OTHER = 303;
+
+ /**
+ * Status code (304) indicating that a conditional GET operation found that the resource was available and not
+ * modified.
+ */
+ int SC_NOT_MODIFIED = 304;
+
+ /**
+ * Status code (305) indicating that the requested resource MUST be accessed through the proxy given by
+ * the
+ * Location field.
+ */
+ int SC_USE_PROXY = 305;
+
+ /**
+ * Status code (307) indicating that the requested resource resides temporarily under a different URI. The temporary
+ * URI
+ * SHOULD be given by the Location field in the response.
+ */
+ int SC_TEMPORARY_REDIRECT = 307;
+
+ /**
+ * Status code (400) indicating the request sent by the client was syntactically incorrect.
+ */
+ int SC_BAD_REQUEST = 400;
+
+ /**
+ * Status code (401) indicating that the request requires HTTP authentication.
+ */
+ int SC_UNAUTHORIZED = 401;
+
+ /**
+ * Status code (402) reserved for future use.
+ */
+ int SC_PAYMENT_REQUIRED = 402;
+
+ /**
+ * Status code (403) indicating the server understood the request but refused to fulfill it.
+ */
+ int SC_FORBIDDEN = 403;
+
+ /**
+ * Status code (404) indicating that the requested resource is not available.
+ */
+ int SC_NOT_FOUND = 404;
+
+ /**
+ * Status code (405) indicating that the method specified in the Request-Line is not allowed
+ * for the resource identified by the Request-URI.
+ */
+ int SC_METHOD_NOT_ALLOWED = 405;
+
+ /**
+ * Status code (406) indicating that the resource identified by the request is only capable of generating response
+ * entities which have content characteristics not acceptable according to the accept headers sent in the request.
+ */
+ int SC_NOT_ACCEPTABLE = 406;
+
+ /**
+ * Status code (407) indicating that the client MUST first authenticate itself with the proxy.
+ */
+ int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+
+ /**
+ * Status code (408) indicating that the client did not produce a request within the time that the server was
+ * prepared to wait.
+ */
+ int SC_REQUEST_TIMEOUT = 408;
+
+ /**
+ * Status code (409) indicating that the request could not be completed due to a conflict with the current state of
+ * the resource.
+ */
+ int SC_CONFLICT = 409;
+
+ /**
+ * Status code (410) indicating that the resource is no longer available at the server and no forwarding address is
+ * known. This condition SHOULD be considered permanent.
+ */
+ int SC_GONE = 410;
+
+ /**
+ * Status code (411) indicating that the request cannot be handled without a defined
+ * Content-Length.
+ */
+ int SC_LENGTH_REQUIRED = 411;
+
+ /**
+ * Status code (412) indicating that the precondition given in one or more of the request-header fields evaluated to
+ * false when it was tested on the server.
+ */
+ int SC_PRECONDITION_FAILED = 412;
+
+ /**
+ * Status code (413) indicating that the server is refusing to process the request because the request entity is
+ * larger than the server is willing or able to process.
+ */
+ int SC_REQUEST_ENTITY_TOO_LARGE = 413;
+
+ /**
+ * Status code (414) indicating that the server is refusing to service the request because the
+ * Request-URI is longer than the server is willing to interpret.
+ */
+ int SC_REQUEST_URI_TOO_LONG = 414;
+
+ /**
+ * Status code (415) indicating that the server is refusing to service the request because the entity of the request
+ * is in a format not supported by the requested resource for the requested method.
+ */
+ int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+
+ /**
+ * Status code (416) indicating that the server cannot serve the requested byte range.
+ */
+ int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+
+ /**
+ * Status code (417) indicating that the server could not meet the expectation given in the Expect request header.
+ */
+ int SC_EXPECTATION_FAILED = 417;
+
+ /**
+ * Status code (500) indicating an error inside the HTTP server which prevented it from fulfilling the request.
+ */
+ int SC_INTERNAL_SERVER_ERROR = 500;
+
+ /**
+ * Status code (501) indicating the HTTP server does not support the functionality needed to fulfill the request.
+ */
+ int SC_NOT_IMPLEMENTED = 501;
+
+ /**
+ * Status code (502) indicating that the HTTP server received an invalid response from a server it consulted when
+ * acting as a proxy or gateway.
+ */
+ int SC_BAD_GATEWAY = 502;
+
+ /**
+ * Status code (503) indicating that the HTTP server is temporarily overloaded, and unable to handle the request.
+ */
+ int SC_SERVICE_UNAVAILABLE = 503;
+
+ /**
+ * Status code (504) indicating that the server did not receive a timely response from the upstream server while
+ * acting as a gateway or proxy.
+ */
+ int SC_GATEWAY_TIMEOUT = 504;
+
+ /**
+ * Status code (505) indicating that the server does not support or refuses to support the HTTP protocol version
+ * that was used in the request message.
+ */
+ int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/Uri.java b/api/src/main/java/com/yanzhenjie/andserver/http/Uri.java
new file mode 100644
index 0000000000000000000000000000000000000000..df081667ea835103085b0b0bf810d661486c77d6
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/Uri.java
@@ -0,0 +1,438 @@
+/*
+ * 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.http;
+
+
+import com.yanzhenjie.andserver.util.*;
+import org.apache.commons.io.Charsets;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * Add the mPath to the URL, such as:
+ *
+ * Uri url = Uri.newBuilder("http://www.example.com/xx")
+ * .scheme("https")
+ * .port(8080)
+ * .path("yy")
+ * .query("abc", "123")
+ * .setFragment("article")
+ * .build();
+ * ...
+ * The real url is: https://www.example.com:8080/xx/yy?abc=123#article.
+ *
+ *
+ * Uri url = Uri.newBuilder("http://www.example.com/xx/yy?abc=123")
+ * .setSegments("/aa/bb/cc")
+ * .setQuery("mln=456&ijk=789")
+ * .build();
+ * ...
+ * The real url is: http://www.example.com/aa/bb/cc?mln=456&ijk=789.
+ *
+ *
+ * Uri url = Uri.newBuilder("http://www.example.com/user/photo/search?name=abc").build();
+ * Uri newUrl = url.location("../../get?name=mln");
+ * ...
+ * The new url is: http://www.example.com/get?name=abc.
+ *
+ * Created by Zhenjie Yan on 2018/2/9.
+ */
+public class Uri implements Patterns {
+
+ public static Builder newBuilder(String uri) {
+ return new Builder(uri);
+ }
+
+ private final String mScheme;
+ private final String mHost;
+ private final int mPort;
+ private final String mPath;
+ private final String mQuery;
+ private final String mFragment;
+
+ private Uri(Builder builder) {
+ this.mScheme = builder.mScheme;
+ this.mHost = builder.mHost;
+ this.mPort = builder.mPort;
+ this.mPath = pathsToPath(builder.mPath);
+ this.mQuery = parametersToQuery(builder.mQuery);
+ this.mFragment = builder.mFragment;
+ }
+
+
+ public String getScheme() {
+ return mScheme;
+ }
+
+
+ public String getHost() {
+ return mHost;
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+
+ public String getPath() {
+ return mPath;
+ }
+
+
+ public List getPaths() {
+ return pathToPaths(mPath);
+ }
+
+
+ public String getQuery() {
+ return mQuery;
+ }
+
+
+ public MultiValueMap getParams() {
+ return queryToParameters(mQuery);
+ }
+
+
+ public String getFragment() {
+ return mFragment;
+ }
+
+
+ public Builder builder() {
+ return new Builder(toString());
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (StringUtils.isEmpty(mScheme)) {
+ builder.append(mScheme).append(":");
+ }
+
+ if (!StringUtils.isEmpty(mHost) && mPort > 0) {
+ builder.append("//").append(mHost).append(":").append(mPort);
+ }
+
+ if (!StringUtils.isEmpty(mPath)) {
+ builder.append(mPath);
+ }
+
+ if (!StringUtils.isEmpty(mQuery)) {
+ builder.append("?").append(mQuery);
+ }
+
+ if (!StringUtils.isEmpty(mFragment)) {
+ builder.append("#").append(mFragment);
+ }
+
+ return builder.toString();
+ }
+
+
+ public Uri location(String location) {
+ if (StringUtils.isEmpty(location)) {
+ return null;
+ }
+
+ if (URLUtil.isNetworkUrl(location)) {
+ return newBuilder(location).build();
+ }
+
+ URI newUri = URI.create(location);
+ if (location.startsWith("/")) {
+ return builder().setPath(newUri.getPath())
+ .setQuery(newUri.getQuery())
+ .setFragment(newUri.getFragment())
+ .build();
+ } else if (location.contains("../")) {
+ List oldPathList = pathToPaths(getPath());
+ List newPathList = pathToPaths(newUri.getPath());
+
+ int start = newPathList.lastIndexOf("..");
+ newPathList = newPathList.subList(start + 1, newPathList.size());
+ if (!oldPathList.isEmpty()) {
+ oldPathList = oldPathList.subList(0, oldPathList.size() - start - 2);
+ oldPathList.addAll(newPathList);
+ String path = TextUtils.join("/", oldPathList);
+ return builder().setPath(path).setQuery(newUri.getQuery()).setFragment(newUri.getFragment()).build();
+ }
+ String path = TextUtils.join("/", newPathList);
+ return builder().setPath(path).setQuery(newUri.getQuery()).setFragment(newUri.getFragment()).build();
+ } else {
+ List oldPathList = pathToPaths(getPath());
+ oldPathList.addAll(pathToPaths(newUri.getPath()));
+ String path = TextUtils.join("/", oldPathList);
+ return builder().setPath(path).setQuery(newUri.getQuery()).setFragment(newUri.getFragment()).build();
+ }
+ }
+
+ public static class Builder {
+
+ private String mScheme;
+ private String mHost;
+ private int mPort;
+ private List mPath;
+ private MultiValueMap mQuery;
+ private String mFragment;
+
+ private Builder( String url) {
+ URI uri = URI.create(url);
+
+ this.mScheme = uri.getScheme();
+ this.mHost = uri.getHost();
+ this.mPort = uri.getPort();
+ String path = uri.getPath();
+ this.mPath = pathToPaths(path);
+ String query = uri.getQuery();
+ this.mQuery = queryToParameters(query);
+ this.mFragment = uri.getFragment();
+ }
+
+ public Builder setScheme( String scheme) {
+ this.mScheme = scheme;
+ return this;
+ }
+
+ public Builder setHost( String host) {
+ this.mHost = host;
+ return this;
+ }
+
+ public Builder setPort(int port) {
+ this.mPort = port;
+ return this;
+ }
+
+ public Builder addPath(int value) {
+ return addPath(Integer.toString(value));
+ }
+
+ public Builder addPath(long value) {
+ return addPath(Long.toString(value));
+ }
+
+ public Builder addPath(boolean value) {
+ return addPath(Boolean.toString(value));
+ }
+
+ public Builder addPath(char value) {
+ return addPath(String.valueOf(value));
+ }
+
+ public Builder addPath(double value) {
+ return addPath(Double.toString(value));
+ }
+
+ public Builder addPath(float value) {
+ return addPath(Float.toString(value));
+ }
+
+ public Builder addPath( CharSequence path) {
+ mPath.add(path.toString());
+ return this;
+ }
+
+ public Builder addPath( String path) {
+ mPath.add(path);
+ return this;
+ }
+
+ public Builder setPath( String path) {
+ mPath = pathToPaths(path);
+ return this;
+ }
+
+ public Builder clearPath() {
+ mPath.clear();
+ return this;
+ }
+
+ public Builder addQuery( String key, int value) {
+ return addQuery(key, Integer.toString(value));
+ }
+
+ public Builder addQuery( String key, long value) {
+ return addQuery(key, Long.toString(value));
+ }
+
+ public Builder addQuery( String key, boolean value) {
+ return addQuery(key, Boolean.toString(value));
+ }
+
+ public Builder addQuery( String key, char value) {
+ return addQuery(key, String.valueOf(value));
+ }
+
+ public Builder addQuery( String key, double value) {
+ return addQuery(key, Double.toString(value));
+ }
+
+ public Builder addQuery( String key, float value) {
+ return addQuery(key, Float.toString(value));
+ }
+
+ public Builder addQuery( String key, short value) {
+ return addQuery(key, Integer.toString(value));
+ }
+
+ public Builder addQuery( String key, CharSequence value) {
+ mQuery.add(key, value.toString());
+ return this;
+ }
+
+ public Builder addQuery( String key, String value) {
+ mQuery.add(key, value);
+ return this;
+ }
+
+ public Builder addQuery( String key, List values) {
+ for (String value: values) {
+ mQuery.add(key, value);
+ }
+ return this;
+ }
+
+ public Builder setQuery( String query) {
+ mQuery = queryToParameters(query);
+ return this;
+ }
+
+ public Builder setQuery( MultiValueMap query) {
+ mQuery = query;
+ return this;
+ }
+
+ public Builder removeQuery( String key) {
+ mQuery.remove(key);
+ return this;
+ }
+
+ public Builder clearQuery() {
+ mQuery.clear();
+ return this;
+ }
+
+ public Builder setFragment( String fragment) {
+ this.mFragment = fragment;
+ return this;
+ }
+
+ public Uri build() {
+ return new Uri(this);
+ }
+ }
+
+ public static List pathToPaths(String path) {
+ List pathList = new LinkedList<>();
+ if (StringUtils.isEmpty(path)) {
+ return pathList;
+ }
+
+ while (path.contains("//")) {
+ path = path.replace("//", "/");
+ }
+
+ while (path.contains("/")) {
+ if (path.startsWith("/")) {
+ pathList.add("");
+ path = path.substring(1);
+ } else {
+ int index = path.indexOf("/");
+ pathList.add(path.substring(0, index));
+ path = path.substring(index + 1);
+ }
+
+ if (!path.contains("/")) {
+ pathList.add(path);
+ }
+ }
+ return pathList;
+ }
+
+ public static MultiValueMap queryToParameters(String query) {
+ MultiValueMap valueMap = new LinkedMultiValueMap<>();
+ if (!StringUtils.isEmpty(query)) {
+ if (query.startsWith("?")) {
+ query = query.substring(1);
+ }
+
+ StringTokenizer tokenizer = new StringTokenizer(query, "&");
+ while (tokenizer.hasMoreElements()) {
+ String element = tokenizer.nextToken();
+ int end = element.indexOf("=");
+
+ if (end > 0 && end < element.length() - 1) {
+ String key = element.substring(0, end);
+ String value = element.substring(end + 1);
+ valueMap.add(key, UrlCoder.urlDecode(value, StandardCharsets.UTF_8));
+ }
+ }
+ }
+ return valueMap;
+ }
+
+ public static String pathsToPath(List pathList) {
+ if (pathList == null || pathList.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder builder = new StringBuilder();
+ for (String path: pathList) {
+ builder.append("/").append(path);
+ }
+
+ String path = builder.toString();
+ while (path.contains("//")) {
+ path = path.replace("//", "/");
+ }
+ return path;
+ }
+
+ public static String parametersToQuery(MultiValueMap params) {
+ StringBuilder builder = new StringBuilder();
+ Iterator>> iterator = params.entrySet().iterator();
+ if (iterator.hasNext()) {
+ Map.Entry> param = iterator.next();
+ String key = param.getKey();
+ List valueList = param.getValue();
+ if (valueList != null && !valueList.isEmpty()) {
+ for (String value: valueList) {
+ builder.append(key).append("=").append(value);
+ }
+ } else {
+ builder.append(key).append("=");
+ }
+ }
+
+ while (iterator.hasNext()) {
+ Map.Entry> param = iterator.next();
+ String key = param.getKey();
+ List valueList = param.getValue();
+ if (valueList != null && !valueList.isEmpty()) {
+ for (String value: valueList) {
+ builder.append("&").append(key).append("=").append(value);
+ }
+ } else {
+ builder.append("&").append(key).append("=");
+ }
+ }
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/cookie/Cookie.java b/api/src/main/java/com/yanzhenjie/andserver/http/cookie/Cookie.java
new file mode 100644
index 0000000000000000000000000000000000000000..86dfcb9edfcffa159668aff90fb235c00f4d3f45
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/cookie/Cookie.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.http.cookie;
+
+import com.yanzhenjie.andserver.util.StringUtils;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+/**
+ * Created by Zhenjie Yan on 2018
+ */
+public class Cookie implements Cloneable, Serializable {
+
+ private static final String TSPECIALS = "/()<>@,;:\\\"[]?={} \t";
+
+ private String name; // NAME= ... "$Name" style is reserved.
+ private String value; // value of NAME.
+
+ private String comment; // ;Comment=VALUE ... describes cookie's use.
+ // ;Discard ... implied by maxAge < 0
+ private String domain; // ;Domain=VALUE ... domain that sees cookie.
+ private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire.
+ private String path; // ;Path=VALUE ... URLs that see the cookie.
+ private boolean secure; // ;Secure ... e.g. use SSL.
+ private int version = 0; // ;Version=1 ... means RFC 2109++ style.
+ private boolean isHttpOnly = false;
+
+ /**
+ * Constructs a cookie with the specified name and value.
+ *
+ * The name must conform to RFC 2109.
+ *
+ *
The name of a cookie cannot be changed once the cookie has been created.
+ *
+ *
The value can be anything the server chooses to send. Its value is probably of interest only to the server.
+ * The cookie's value can be changed after creation with the {@link #setValue(String)} method.
+ *
+ *
The version can be changed with the {@link #setVersion(int)} method.
+ *
+ * @param name the name of the cookie.
+ * @param value the value of the cookie.
+ *
+ * @throws IllegalArgumentException if the cookie name is null or empty or contains any illegal characters (e.g.
+ * a comma, space, or semicolon) or matches a token reserved for use by the cookie protocol.
+ * @see #setValue(String)
+ * @see #setVersion(int)
+ */
+ public Cookie( String name, String value) {
+ if (StringUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("The name of the cookie cannot be empty or null.");
+ }
+ if (!isToken(name) || name.equalsIgnoreCase("Comment") || // rfc2019
+ name.equalsIgnoreCase("Discard") || // 2019++
+ name.equalsIgnoreCase("Domain") || name.equalsIgnoreCase("Expires") || // (old cookies)
+ name.equalsIgnoreCase("Max-Age") || // rfc2019
+ name.equalsIgnoreCase("Path") || name.equalsIgnoreCase("Secure") || name.equalsIgnoreCase("Version") ||
+ name.startsWith("$")) {
+ String message = String.format("This name [%1$s] is a reserved character.", name);
+ throw new IllegalArgumentException(message);
+ }
+
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * Specifies a comment that describes a cookie's purpose. The comment is useful if the browser presents the cookie
+ * to the user.
+ *
+ * @param purpose a {@link String} specifying the comment to display to the user.
+ *
+ * @see #getComment()
+ */
+ public void setComment(String purpose) {
+ comment = purpose;
+ }
+
+ /**
+ * Returns the comment describing the purpose of this cookie, or {@code null} if the cookie has no comment.
+ *
+ * @return the comment of the cookie, or {@code null} if unspecified.
+ *
+ * @see #setComment(String)
+ */
+
+ public String getComment() {
+ return comment;
+ }
+
+ /**
+ * Specifies the domain within which this cookie should be presented.
+ *
+ *
The form of the domain name is specified by RFC 2109. StandardCookieProcessor domain name begins with a dot
+ * ({@code .foo.com}) and means that the cookie is visible to servers in a specified Domain Name System (DNS) zone
+ * (e.g. {@code www.foo.com}, but not {@code a.b.foo.com}). By default, cookies are only returned to the server that
+ * sent them.
+ *
+ * @param domain the domain name within which this cookie is visible; form is according to RFC 2109.
+ *
+ * @see #getDomain()
+ */
+ public void setDomain(String domain) {
+ if (!StringUtils.isEmpty(domain)) {
+ this.domain = domain.toLowerCase(Locale.ENGLISH); // IE allegedly needs this.
+ } else {
+ this.domain = null;
+ }
+ }
+
+ /**
+ * Gets the domain name of this Cookie.
+ *
+ *
Domain names are formatted according to RFC 2109.
+ *
+ * @return the domain name of this Cookie.
+ *
+ * @see #setDomain(String)
+ */
+
+ public String getDomain() {
+ return domain;
+ }
+
+ /**
+ * Sets the maximum age in seconds for this Cookie.
+ *
+ *
StandardCookieProcessor positive value indicates that the cookie will expire after that many seconds have
+ * passed. Note that the value is the maximum age when the cookie will expire, not the cookie's current age.
+ *
+ *
StandardCookieProcessor negative value means that the cookie is not stored persistently and will be deleted
+ * when the Web browser exits. StandardCookieProcessor zero value causes the cookie to be deleted.
+ *
+ * @param expiry an integer specifying the maximum age of the cookie in seconds; if negative, means the cookie
+ * is not stored; if zero, deletes the cookie.
+ *
+ * @see #getMaxAge()
+ */
+ public void setMaxAge(int expiry) {
+ maxAge = expiry;
+ }
+
+ /**
+ * Gets the maximum age in seconds of this Cookie.
+ *
+ *
By default, {@code -1} is returned, which indicates that the cookie will persist until browser shutdown.
+ *
+ * @see #setMaxAge(int)
+ */
+ public int getMaxAge() {
+ return maxAge;
+ }
+
+ /**
+ * Specifies a path for the cookie to which the client should return the cookie.
+ *
+ *
The cookie is visible to all the pages in the directory you specify, and all the pages in that directory's
+ * subdirectories.
+ *
+ *
Consult RFC 2109 (available on the Internet) for more information on setting path names for cookies.
+ *
+ * @param path a {@code String} specifying a path.
+ *
+ * @see #getPath()
+ */
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ /**
+ * Returns the path on the server to which the browser returns this cookie. The cookie is visible to all subpaths on
+ * the server.
+ *
+ * @see #setPath(String)
+ */
+
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Indicates to the browser whether the cookie should only be sent using a secure protocol, e.g. HTTPS or SSL.
+ *
+ *
The default value is {@code false}.
+ *
+ * @param flag if {@code true}, sends the cookie from the browser to the server only when using a secure
+ * protocol; if {@code false}, sent on any protocol.
+ *
+ * @see #getSecure()
+ */
+ public void setSecure(boolean flag) {
+ secure = flag;
+ }
+
+ /**
+ * Returns {@code true} if the browser is sending cookies only over a secure protocol, or {@code false} if the
+ * browser can send cookies using any protocol.
+ *
+ * @return {@code true} if the browser uses a secure protocol, {@code false} otherwise.
+ *
+ * @see #setSecure(boolean)
+ */
+ public boolean getSecure() {
+ return secure;
+ }
+
+ /**
+ * Returns the name of the cookie. The name cannot be changed after creation.
+ *
+ * @return the name of the cookie.
+ */
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Assigns a new value to this Cookie.
+ *
+ *
If you use a binary value, you may want to use BASE64 encoding.
+ *
+ *
With Version 0 cookies, values should not contain white space, brackets, parentheses, equals signs, commas,
+ * double quotes, slashes, question marks, at signs, colons, and semicolons. Empty values may not behave the same
+ * way on all browsers.
+ *
+ * @param newValue the new value of the cookie.
+ *
+ * @see #getValue()
+ */
+ public void setValue(String newValue) {
+ value = newValue;
+ }
+
+ /**
+ * Gets the current value of this Cookie.
+ *
+ * @return the current value of this Cookie.
+ *
+ * @see #setValue(String)
+ */
+
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the version of the protocol this cookie complies with. Version 1 complies with RFC 2109, and version 0
+ * complies with the original cookie specification drafted by Netscape. Cookies provided by a browser use and
+ * identify the browser's cookie version.
+ *
+ * @see #setVersion(int)
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the version of the cookie protocol that this Cookie complies with.
+ *
+ *
Version 0 complies with the original Netscape cookie specification. Version 1 complies with RFC 2109.
+ *
+ *
Since RFC 2109 is still somewhat new, consider version 1 as experimental; do not use it yet on production
+ * sites.
+ *
+ * @param v 0 if the cookie should comply with the original Netscape specification; 1 if the cookie should
+ * comply with RFC 2109.
+ *
+ * @see #getVersion()
+ */
+ public void setVersion(int v) {
+ version = v;
+ }
+
+ /**
+ * Tests a string and returns true if the string counts as a reserved token in the Java language.
+ *
+ * @param value the {@code String} to be tested.
+ *
+ * @return {@code true} if the {@code String} is a reserved token; {@code false} otherwise.
+ */
+ private boolean isToken(String value) {
+ int len = value.length();
+ for (int i = 0; i < len; i++) {
+ char c = value.charAt(i);
+ if (c < 0x20 || c >= 0x7f || TSPECIALS.indexOf(c) != -1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ /**
+ * Marks or unmarks this Cookie as HttpOnly.
+ *
+ *
If isHttpOnly is set to true, this cookie is marked as HttpOnly, by adding the
+ * HttpOnly attribute to it.
+ *
+ *
HttpOnly cookies are not supposed to be exposed to client-side scripting code, and may therefore help
+ * mitigate certain kinds of cross-site scripting attacks.
+ *
+ * @param isHttpOnly true if this cookie is to be marked as HttpOnly, false otherwise.
+ */
+ public void setHttpOnly(boolean isHttpOnly) {
+ this.isHttpOnly = isHttpOnly;
+ }
+
+ /**
+ * Checks whether this Cookie has been marked as HttpOnly.
+ *
+ * @return true if this Cookie has been marked as HttpOnly, false otherwise.
+ */
+ public boolean isHttpOnly() {
+ return isHttpOnly;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/cookie/CookieProcessor.java b/api/src/main/java/com/yanzhenjie/andserver/http/cookie/CookieProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c30bad639a8d79794002fc691726b232133c0be
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/cookie/CookieProcessor.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.http.cookie;
+
+
+
+
+import org.apache.httpcore.Header;
+
+import java.util.List;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/27.
+ */
+public interface CookieProcessor {
+
+ /**
+ * Parse the provided headers into server cookie objects.
+ *
+ * @param headers the HTTP headers to parse.
+ */
+
+ List parseCookieHeader(Header[] headers);
+
+ /**
+ * Generate the {@code Set-Cookie} HTTP header value for the given {@code Cookie}.
+ *
+ * @param cookie the cookie for which the header will be generated.
+ *
+ * @return the header value in a form that can be added directly to the response.
+ */
+
+ String generateHeader(Cookie cookie);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/cookie/StandardCookieProcessor.java b/api/src/main/java/com/yanzhenjie/andserver/http/cookie/StandardCookieProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d5616c97661f4ccc74a7acfce48083dd17c6aa0
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/cookie/StandardCookieProcessor.java
@@ -0,0 +1,215 @@
+/*
+ * 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.http.cookie;
+
+import com.yanzhenjie.andserver.util.StringUtils;
+import org.apache.httpcore.Header;
+
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/27.
+ */
+public class StandardCookieProcessor implements CookieProcessor {
+
+ private static final String COOKIE_DATE_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z";
+
+ private static final ThreadLocal COOKIE_DATE_FORMAT = new ThreadLocal() {
+ @Override
+ protected DateFormat initialValue() {
+ DateFormat df = new SimpleDateFormat(COOKIE_DATE_PATTERN, Locale.US);
+ df.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return df;
+ }
+ };
+
+ private static final String ANCIENT_DATE = COOKIE_DATE_FORMAT.get().format(new Date(10000));
+ private static final BitSet DOMAIN_VALID = new BitSet(128);
+
+ static {
+ for (char c = '0'; c <= '9'; c++) {
+ DOMAIN_VALID.set(c);
+ }
+ for (char c = 'a'; c <= 'z'; c++) {
+ DOMAIN_VALID.set(c);
+ }
+ for (char c = 'A'; c <= 'Z'; c++) {
+ DOMAIN_VALID.set(c);
+ }
+ DOMAIN_VALID.set('.');
+ DOMAIN_VALID.set('-');
+ }
+
+
+ @Override
+ public List parseCookieHeader(Header[] headers) {
+ if (headers == null || headers.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List cookieList = new ArrayList<>();
+ for (Header header: headers) {
+ String name = header.getName();
+ if ("Cookie".equalsIgnoreCase(name)) {
+ String headerValue = header.getValue();
+ parserCookieValue(headerValue, cookieList);
+ }
+ }
+ return cookieList;
+ }
+
+ private void parserCookieValue(String headerValue, List cookieList) {
+ StringTokenizer tokenizer = new StringTokenizer(headerValue, ";");
+ while (tokenizer.hasMoreTokens()) {
+ String segment = tokenizer.nextToken();
+ int split = segment.indexOf("=");
+ int valueIndex = split + 1;
+ if (split > 0 && valueIndex < segment.length()) {
+ String name = segment.substring(0, split).trim();
+ String value = segment.substring(valueIndex, segment.length()).trim();
+ cookieList.add(new Cookie(name, value));
+ }
+ }
+ }
+
+
+ @Override
+ public String generateHeader( Cookie cookie) {
+ // Can't use StringBuilder due to DateFormat.
+ StringBuffer header = new StringBuffer();
+ header.append(cookie.getName());
+ header.append('=');
+ String value = cookie.getValue();
+ if (!StringUtils.isEmpty(value)) {
+ validateCookieValue(value);
+ header.append(value);
+ }
+
+ // RFC 6265 prefers Max-Age to Expires but... (see below).
+ int maxAge = cookie.getMaxAge();
+ if (maxAge > -1) {
+ // Negative Max-Age is equivalent to no Max-Age.
+ header.append("; Max-Age=");
+ header.append(maxAge);
+
+ // Microsoft IE and Microsoft Edge don't understand Max-Age so send
+ // expires as well. Without this, persistent cookies fail with those
+ // browsers. See http://tomcat.markmail.org/thread/g6sipbofsjossacn.
+
+ // Wdy, DD-Mon-YY HH:MM:SS GMT (Expires Netscape format).
+ header.append("; Expires=");
+ // To expire immediately we need to set the time in past.
+ if (maxAge == 0) {
+ header.append(ANCIENT_DATE);
+ } else {
+ Date date = new Date(System.currentTimeMillis() + maxAge * 1000L);
+ COOKIE_DATE_FORMAT.get().format(date, header, new FieldPosition(0));
+ }
+ }
+
+ String domain = cookie.getDomain();
+ if (domain != null && domain.length() > 0) {
+ validateDomain(domain);
+ header.append("; Domain=");
+ header.append(domain);
+ }
+
+ String path = cookie.getPath();
+ if (path != null && path.length() > 0) {
+ validatePath(path);
+ header.append("; Path=");
+ header.append(path);
+ }
+
+ if (cookie.getSecure()) {
+ header.append("; Secure");
+ }
+
+ if (cookie.isHttpOnly()) {
+ header.append("; HttpOnly");
+ }
+
+ return header.toString();
+ }
+
+
+ private void validateCookieValue(String value) {
+ int start = 0;
+ int end = value.length();
+
+ if (end > 1 && value.charAt(0) == '"' && value.charAt(end - 1) == '"') {
+ start = 1;
+ end--;
+ }
+
+ char[] chars = value.toCharArray();
+ for (int i = start; i < end; i++) {
+ char c = chars[i];
+ if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c || c == 0x7f) {
+ String message = String.format("The cookie's value [%1$s] is invalid.", value);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+
+
+ private void validateDomain(String domain) {
+ int i = 0;
+ int prev = -1;
+ int cur = -1;
+ char[] chars = domain.toCharArray();
+ while (i < chars.length) {
+ prev = cur;
+ cur = chars[i];
+ if (!DOMAIN_VALID.get(cur)) {
+ String message = String.format("The cookie's domain [%1$s] is invalid.", domain);
+ throw new IllegalArgumentException(message);
+ }
+ // labels must start with a letter or number.
+ if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) {
+ String message = String.format("The cookie's domain [%1$s] is invalid.", domain);
+ throw new IllegalArgumentException(message);
+ }
+ // labels must end with a letter or number.
+ if (prev == '-' && cur == '.') {
+ String message = String.format("The cookie's domain [%1$s] is invalid.", domain);
+ throw new IllegalArgumentException(message);
+ }
+ i++;
+ }
+ // domain must end with a label.
+ if (cur == '.' || cur == '-') {
+ String message = String.format("The cookie's domain [%1$s] is invalid.", domain);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+
+ private void validatePath(String path) {
+ char[] chars = path.toCharArray();
+
+ for (int i = 0; i < chars.length; i++) {
+ char ch = chars[i];
+ if (ch < 0x20 || ch > 0x7E || ch == ';') {
+ String message = String.format("The cookie's path [%1$s] is invalid.", path);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/BodyContext.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/BodyContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..39867610049d82e32de6f0eb2bdbcc07e64a256c
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/BodyContext.java
@@ -0,0 +1,69 @@
+/*
+ * 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.http.multipart;
+
+
+
+import com.yanzhenjie.andserver.http.RequestBody;
+import com.yanzhenjie.andserver.util.MediaType;
+import org.apache.commons.fileupload.UploadContext;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/9.
+ */
+public class BodyContext implements UploadContext {
+
+ private final RequestBody mBody;
+
+ public BodyContext( RequestBody body) {
+ this.mBody = body;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return mBody.contentEncoding();
+ }
+
+ @Override
+ public String getContentType() {
+ MediaType contentType = mBody.contentType();
+ return contentType == null ? null : contentType.toString();
+ }
+
+ @Override
+ public int getContentLength() {
+ long contentLength = contentLength();
+ return contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) contentLength;
+ }
+
+ @Override
+ public long contentLength() {
+ return mBody.length();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return mBody.stream();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ContentLength=%s, Mime=%s", contentLength(), getContentType());
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartFile.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..8868aba43fecd4d4f110533c5f467d86ee07d218
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartFile.java
@@ -0,0 +1,99 @@
+/*
+ * 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.http.multipart;
+
+
+import com.yanzhenjie.andserver.util.MediaType;
+import org.apache.commons.fileupload.FileItem;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/18.
+ */
+public interface MultipartFile {
+
+ /**
+ * Return the name of the parameter in the multipart form.
+ *
+ * @return the name of the parameter.
+ */
+
+ String getName();
+
+ /**
+ * Return the original filename in the client's filesystem.
+ *
+ * @return the original filename, or the empty String if no file, or null if not defined.
+ *
+ * @see FileItem#getName()
+ */
+
+ String getFilename();
+
+ /**
+ * Return the content type of the file.
+ *
+ * @return the content type, or the empty String if no file, or null if not defined.
+ */
+
+ MediaType getContentType();
+
+ /**
+ * Return whether the uploaded file is empty.
+ *
+ * @return true, otherwise is false.
+ */
+ boolean isEmpty();
+
+ /**
+ * Return the size of the file in bytes.
+ *
+ * @return the size of the file, or 0 if empty.
+ */
+ long getSize();
+
+ /**
+ * Return the contents of the file as an array of bytes.
+ *
+ * @return the contents of the file as bytes, or an empty byte array if empty.
+ *
+ * @throws IOException in case of access errors (if the temporary store fails).
+ */
+ byte[] getBytes() throws IOException;
+
+ /**
+ * Return an {@code InputStream} to read the contents of the file from.
+ *
+ * @return the contents of the file as stream, or an empty stream if empty.
+ *
+ * @throws IOException in case of access errors.
+ */
+
+ InputStream getStream() throws IOException;
+
+ /**
+ * Writing the received file to the given destination file. If the destination file already exists, it will be
+ * deleted first.
+ *
+ * If the target file has been written to disk, this operation cannot be invoked again afterwards.
+ *
+ * @param dest the destination file.
+ */
+ void transferTo(File dest) throws IOException, IllegalStateException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartRequest.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f80b4bdfe7621d8dd10d4d49ddb9ff442c7331ef
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartRequest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.http.multipart;
+
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.util.MultiValueMap;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
This interface defines the multipart request access operations that are exposed for actual multipart requests.
+ *
+ *
+ * Created by Zhenjie Yan on 2018/6/21.
+ */
+public interface MultipartRequest extends HttpRequest {
+
+ /**
+ * Return an {@link Iterator} of String objects containing the parameter names of the multipart files
+ * contained in this request. These are the field names of the form (like with normal parameters), not the original
+ * file names.
+ *
+ * @return the names of the files.
+ */
+
+ Iterator getFileNames();
+
+ /**
+ * Return the contents plus description of an uploaded file in this request, or null if it does not exist.
+ *
+ * @param name parameter name.
+ *
+ * @return a {@link MultipartFile} object.
+ */
+
+ MultipartFile getFile(String name);
+
+ /**
+ * Return the contents plus description of uploaded files in this request, or an empty list if it does not exist.
+ *
+ * @param name parameter name.
+ *
+ * @return a {@link MultipartFile} list.
+ */
+
+ List getFiles(String name);
+
+ /**
+ * Return a {@link Map} of the multipart files contained in this request.
+ *
+ * @return a map containing the parameter names as keys, and the {@link MultipartFile} objects as values.
+ */
+
+ Map getFileMap();
+
+ /**
+ * Return a {@link MultiValueMap} of the multipart files contained in this request.
+ *
+ * @return a map containing the parameter names as keys, and a list of {@link MultipartFile} objects as values.
+ */
+
+ MultiValueMap getMultiFileMap();
+
+ /**
+ * Determine the content type of the specified request part.
+ *
+ * @param paramOrFileName the name of the part.
+ *
+ * @return the associated content type, or null if not defined.
+ */
+
+ String getMultipartContentType(String paramOrFileName);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartResolver.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef49fd49c7825a58dddc6bf9f9efac5685b868ff
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/MultipartResolver.java
@@ -0,0 +1,91 @@
+/*
+ * 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.http.multipart;
+
+import com.yanzhenjie.andserver.error.MultipartException;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+
+import java.io.File;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/8.
+ */
+public interface MultipartResolver {
+
+ /**
+ * 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)
+ */
+ void setAllFileMaxSize(long allFileMaxSize);
+
+ /**
+ * 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)
+ */
+ void setFileMaxSize(long fileMaxSize);
+
+ /**
+ * 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)
+ */
+ void setMaxInMemorySize(int maxInMemorySize);
+
+ /**
+ * Set the temporary directory where uploaded files get stored.
+ */
+ void setUploadTempDir(File uploadTempDir);
+
+ /**
+ * Determine if the given request contains multipart content, will typically check for content type
+ * "multipart/form-data".
+ *
+ * @param request the request to be evaluated.
+ *
+ * @return whether the request contains multipart content.
+ */
+ boolean isMultipart(HttpRequest request);
+
+ /**
+ * Parse the given request into multipart files and parameters, and wrap the request inside a {@link
+ * MultipartRequest} object that provides access to file descriptors and makes contained parameters accessible via
+ * the standard HttpRequest methods.
+ *
+ * @param request the request to wrap (must be of a multipart content type).
+ *
+ * @return the wrapped request.
+ *
+ * @throws MultipartException if the request is not multipart, or encounter other problems.
+ */
+ MultipartRequest resolveMultipart(HttpRequest request) throws MultipartException;
+
+ /**
+ * Cleanup any resources used for the multipart handling, like a storage for the uploaded files.
+ *
+ * @param request the request to cleanup resources for.
+ */
+ void cleanupMultipart(MultipartRequest request);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartFile.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac55895ae0f7c36879a6a2e7b4b243fb558023b7
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartFile.java
@@ -0,0 +1,168 @@
+/*
+ * 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.http.multipart;
+
+import com.yanzhenjie.andserver.util.IOUtils;
+import com.yanzhenjie.andserver.util.MediaType;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItem;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+/**
+ * Created by Zhenjie Yan on 2018/6/21.
+ */
+public class StandardMultipartFile implements MultipartFile, Serializable {
+
+ private final FileItem fileItem;
+ private final long size;
+
+ /**
+ * Create an instance wrapping the given FileItem.
+ *
+ * @param fileItem the FileItem to wrap.
+ */
+ public StandardMultipartFile(FileItem fileItem) {
+ this.fileItem = fileItem;
+ this.size = fileItem.getSize();
+ }
+
+ /**
+ * Return the underlying {@link FileItem} instance. There is hardly any need to access this.
+ */
+ public final FileItem getFileItem() {
+ return fileItem;
+ }
+
+
+ @Override
+ public String getName() {
+ return this.fileItem.getFieldName();
+ }
+
+
+ @Override
+ public String getFilename() {
+ String filename = this.fileItem.getName();
+ if (filename == null) {
+ // Should never happen.
+ return "";
+ }
+
+ // Check for Unix-style path.
+ int unixSep = filename.lastIndexOf("/");
+ // Check for Windows-style path.
+ int winSep = filename.lastIndexOf("\\");
+ // Cut off at latest possible point.
+ int pos = (winSep > unixSep ? winSep : unixSep);
+ if (pos != -1) {
+ // Any sort of path separator found...
+ return filename.substring(pos + 1);
+ } else {
+ // A plain name.
+ return filename;
+ }
+ }
+
+
+ @Override
+ public MediaType getContentType() {
+ String mimeType = fileItem.getContentType();
+ MediaType mediaType;
+ try {
+ mediaType = MediaType.parseMediaType(mimeType);
+ } catch (Exception e) {
+ mediaType = MediaType.APPLICATION_OCTET_STREAM;
+ }
+ return mediaType;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ @Override
+ public long getSize() {
+ return size;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has been moved - cannot be read again.");
+ }
+ byte[] bytes = this.fileItem.get();
+ return (bytes != null ? bytes : new byte[0]);
+ }
+
+
+ @Override
+ public InputStream getStream() throws IOException {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has been moved - cannot be read again.");
+ }
+ InputStream inputStream = fileItem.getInputStream();
+ return (inputStream != null ? inputStream : IOUtils.createEmptyInput());
+ }
+
+ @Override
+ public void transferTo( File dest) throws IOException, IllegalStateException {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has already been moved - cannot be transferred again.");
+ }
+
+ if (dest.exists() && !dest.delete()) {
+ throw new IOException(
+ "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted.");
+ }
+
+ try {
+ fileItem.write(dest);
+ } catch (FileUploadException ex) {
+ throw new IllegalStateException(ex.getMessage(), ex);
+ } catch (IllegalStateException ex) {
+ // Pass through when coming from FileItem directly.
+ throw ex;
+ } catch (IOException ex) {
+ // From I/O operations within FileItem.write.
+ throw ex;
+ } catch (Exception ex) {
+ throw new IOException("File transfer failed", ex);
+ }
+ }
+
+ /**
+ * Determine whether the multipart content is still available. If a temporary file has been moved, the content is no
+ * longer available.
+ */
+ protected boolean isAvailable() {
+ // If in memory, it's available.
+ if (fileItem.isInMemory()) {
+ return true;
+ }
+ // Check actual existence of temporary file.
+ if (fileItem instanceof DiskFileItem) {
+ return ((DiskFileItem) fileItem).getStoreLocation().exists();
+ }
+ // Check whether current file size is different than original one.
+ return (fileItem.getSize() == size);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartRequest.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..783d0da5eb1e119d2c75249c68da5189e6381303
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartRequest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.http.multipart;
+
+
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.RequestWrapper;
+import com.yanzhenjie.andserver.util.LinkedMultiValueMap;
+import com.yanzhenjie.andserver.util.MultiValueMap;
+
+import java.util.*;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/9.
+ */
+public class StandardMultipartRequest extends RequestWrapper implements MultipartRequest {
+
+ private HttpRequest mRequest;
+
+ private MultiValueMap mMultipartFiles;
+ private MultiValueMap mMultipartParameters;
+ private Map mMultipartContentTypes;
+
+ public StandardMultipartRequest( HttpRequest request, MultiValueMap mpFiles,
+ MultiValueMap mpParams,
+ Map mpContentTypes) {
+ super(request);
+ this.mRequest = request;
+ this.mMultipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(mpFiles));
+ this.mMultipartParameters = new LinkedMultiValueMap<>(Collections.unmodifiableMap(mpParams));
+ this.mMultipartContentTypes = Collections.unmodifiableMap(mpContentTypes);
+ }
+
+
+ @Override
+ public Iterator getFileNames() {
+ return mMultipartFiles.keySet().iterator();
+ }
+
+
+ @Override
+ public MultipartFile getFile(String name) {
+ return mMultipartFiles.getFirst(name);
+ }
+
+
+ @Override
+ public List getFiles(String name) {
+ List multipartFiles = mMultipartFiles.get(name);
+ if (multipartFiles != null) {
+ return multipartFiles;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+
+ @Override
+ public Map getFileMap() {
+ return mMultipartFiles.toSingleValueMap();
+ }
+
+
+ @Override
+ public MultiValueMap getMultiFileMap() {
+ return mMultipartFiles;
+ }
+
+
+ @Override
+ public String getMultipartContentType(String paramOrFileName) {
+ MultipartFile file = getFile(paramOrFileName);
+ return file == null ? mMultipartContentTypes.get(paramOrFileName) : file.getContentType().getType();
+ }
+
+
+ @Override
+ public List getParameterNames() {
+ if (mMultipartParameters.isEmpty()) {
+ return mRequest.getParameterNames();
+ }
+
+ List paramNames = new LinkedList<>();
+ List names = mRequest.getParameterNames();
+ if (!names.isEmpty()) {
+ paramNames.addAll(names);
+ }
+ paramNames.addAll(mMultipartParameters.keySet());
+ return paramNames;
+ }
+
+
+ @Override
+ public String getParameter( String name) {
+ String value = mMultipartParameters.getFirst(name);
+ return value.isEmpty() ? mRequest.getParameter(name) : value;
+ }
+
+
+ @Override
+ public List getParameters( String name) {
+ List values = mMultipartParameters.get(name);
+ return values == null ? mRequest.getParameters(name) : values;
+ }
+
+
+ @Override
+ public MultiValueMap getParameter() {
+ return mMultipartParameters.isEmpty() ? mRequest.getParameter() : mMultipartParameters;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartResolver.java b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f9fd100aa0d458d9e31fa2d4554f580335e691b
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/multipart/StandardMultipartResolver.java
@@ -0,0 +1,287 @@
+/*
+ * 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.http.multipart;
+
+import com.yanzhenjie.andserver.AndServer;
+import com.yanzhenjie.andserver.error.MaxUploadSizeExceededException;
+import com.yanzhenjie.andserver.error.MultipartException;
+import com.yanzhenjie.andserver.http.HttpRequest;
+import com.yanzhenjie.andserver.http.RequestBody;
+import com.yanzhenjie.andserver.util.Assert;
+import com.yanzhenjie.andserver.util.LinkedMultiValueMap;
+import com.yanzhenjie.andserver.util.MediaType;
+import com.yanzhenjie.andserver.util.MultiValueMap;
+import ohos.hiviewdfx.HiLog;
+import ohos.hiviewdfx.HiLogLabel;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.FileUploadBase;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.io.Charsets;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/9.
+ */
+public class StandardMultipartResolver implements MultipartResolver {
+
+ private DiskFileItemFactory mFileItemFactory;
+ private FileUpload mFileUpload;
+
+ public StandardMultipartResolver() {
+ this.mFileItemFactory = new DiskFileItemFactory();
+ this.mFileUpload = new FileUpload(mFileItemFactory);
+ }
+
+ @Override
+ public void setAllFileMaxSize(long allFileMaxSize) {
+ this.mFileUpload.setSizeMax(allFileMaxSize);
+ }
+
+ @Override
+ public void setFileMaxSize(long fileMaxSize) {
+ this.mFileUpload.setFileSizeMax(fileMaxSize);
+ }
+
+ @Override
+ public void setMaxInMemorySize(int maxInMemorySize) {
+ this.mFileItemFactory.setSizeThreshold(maxInMemorySize);
+ }
+
+ @Override
+ public void setUploadTempDir(File uploadTempDir) {
+ if (!uploadTempDir.exists() && !uploadTempDir.mkdirs()) {
+ String message = "Given uploadTempDir [" + uploadTempDir + "] could not be created.";
+ throw new IllegalArgumentException(message);
+ }
+ this.mFileItemFactory.setRepository(uploadTempDir);
+ }
+
+ @Override
+ public boolean isMultipart(HttpRequest request) {
+ if (!request.getMethod().allowBody()) {
+ return false;
+ }
+
+ RequestBody body = request.getBody();
+ return body != null && FileUploadBase.isMultipartContent(new BodyContext(body));
+ }
+
+ @Override
+ public MultipartRequest resolveMultipart(HttpRequest request) throws MultipartException {
+ if (request instanceof MultipartRequest) {
+ return (MultipartRequest) request;
+ }
+
+ MultipartParsingResult result = parseRequest(request);
+ return new StandardMultipartRequest(request, result.getMultipartFiles(), result.getMultipartParameters(),
+ result.getMultipartContentTypes());
+ }
+
+ @Override
+ public void cleanupMultipart(MultipartRequest request) {
+ if (request != null) {
+ try {
+ MultiValueMap multipartFiles = request.getMultiFileMap();
+ for (List files: multipartFiles.values()) {
+ for (MultipartFile file: files) {
+ if (file instanceof StandardMultipartFile) {
+ StandardMultipartFile cmf = (StandardMultipartFile) file;
+ cmf.getFileItem().delete();
+ }
+ }
+ }
+ } catch (Throwable ex) {
+ HiLog.warn(new HiLogLabel(0,1, AndServer.TAG),"Failed to perform multipart cleanup for servlet request.");
+ }
+ }
+ }
+
+ /**
+ * Parse the given request, resolving its multipart elements.
+ *
+ * @param request the request to parse.
+ *
+ * @return the parsing result.
+ *
+ * @throws MultipartException if multipart resolution failed.
+ */
+ private MultipartParsingResult parseRequest(HttpRequest request) throws MultipartException {
+ String encoding = determineEncoding(request);
+ FileUpload fileUpload = prepareFileUpload(encoding);
+ try {
+ RequestBody body = request.getBody();
+ Assert.notNull(body, "The body cannot be null.");
+ List fileItems = fileUpload.parseRequest(new BodyContext(body));
+ return parseFileItems(fileItems, encoding);
+ } catch (FileUploadBase.SizeLimitExceededException ex) {
+ throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
+ } catch (FileUploadBase.FileSizeLimitExceededException ex) {
+ throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
+ } catch (FileUploadException ex) {
+ throw new MultipartException("Failed to parse multipart servlet request.", ex);
+ }
+ }
+
+ /**
+ * Determine the encoding for the given request.
+ *
+ * The default implementation checks the request encoding, falling back to the default encoding specified for
+ * this resolver.
+ *
+ * @param request current request
+ *
+ * @return the encoding for the request .
+ */
+
+ private String determineEncoding(HttpRequest request) {
+ MediaType mimeType = request.getContentType();
+ if (mimeType == null) {
+ return StandardCharsets.UTF_8.name();
+ }
+ Charset charset = mimeType.getCharset();
+ return charset == null ? StandardCharsets.UTF_8.name() : charset.name();
+ }
+
+ /**
+ * Determine an appropriate FileUpload instance for the given encoding.
+ *
+ *
Default implementation returns the shared FileUpload instance if the encoding matches, else creates a new
+ * FileUpload instance with the same configuration other than the desired encoding.
+ *
+ * @param encoding the character encoding to use.
+ *
+ * @return an appropriate FileUpload instance.
+ */
+ private FileUpload prepareFileUpload( String encoding) {
+ FileUpload actualFileUpload = mFileUpload;
+ if (!encoding.equalsIgnoreCase(mFileUpload.getHeaderEncoding())) {
+ actualFileUpload = new FileUpload(mFileItemFactory);
+ actualFileUpload.setSizeMax(mFileUpload.getSizeMax());
+ actualFileUpload.setFileSizeMax(mFileUpload.getFileSizeMax());
+ actualFileUpload.setHeaderEncoding(encoding);
+ }
+ return actualFileUpload;
+ }
+
+ /**
+ * Parse the given List of Commons FileItems into a MultipartParsingResult, containing MultipartFile instances and a
+ * Map of multipart parameter.
+ *
+ * @param fileItems the Commons FileItems to parse.
+ * @param encoding the encoding to use for form fields.
+ *
+ * @return the MultipartParsingResult.
+ *
+ * @see StandardMultipartFile#StandardMultipartFile(FileItem)
+ */
+ protected MultipartParsingResult parseFileItems(List fileItems, String encoding) {
+ MultiValueMap multipartFiles = new LinkedMultiValueMap<>();
+ MultiValueMap multipartParameters = new LinkedMultiValueMap<>();
+ Map multipartContentTypes = new HashMap<>();
+
+ // Extract multipart files and multipart parameters.
+ for (FileItem fileItem: fileItems) {
+ if (fileItem.isFormField()) {
+ String value;
+ String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
+ if (partEncoding != null) {
+ try {
+ value = fileItem.getString(partEncoding);
+ } catch (UnsupportedEncodingException ex) {
+ value = fileItem.getString();
+ }
+ } else {
+ value = fileItem.getString();
+ }
+ List curParam = multipartParameters.get(fileItem.getFieldName());
+ if (curParam == null) {
+ // Simple form field.
+ curParam = new LinkedList<>();
+ curParam.add(value);
+ multipartParameters.put(fileItem.getFieldName(), curParam);
+ } else {
+ // Array of simple form fields.
+ curParam.add(value);
+ }
+ multipartContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
+ } else {
+ StandardMultipartFile file = createMultipartFile(fileItem);
+ multipartFiles.add(file.getName(), file);
+ }
+ }
+ return new MultipartParsingResult(multipartFiles, multipartParameters, multipartContentTypes);
+ }
+
+ /**
+ * Create a {@link StandardMultipartFile} wrapper for the given Commons {@link FileItem}.
+ *
+ * @param fileItem the Commons FileItem to wrap.
+ *
+ * @return the corresponding StandardMultipartFile (potentially a custom subclass).
+ */
+ protected StandardMultipartFile createMultipartFile(FileItem fileItem) {
+ return new StandardMultipartFile(fileItem);
+ }
+
+ private String determineEncoding(String contentTypeHeader, String defaultEncoding) {
+ if (contentTypeHeader.isEmpty()) {
+ return defaultEncoding;
+ }
+ MediaType contentType = MediaType.parseMediaType(contentTypeHeader);
+ Charset charset = contentType.getCharset();
+ return charset != null ? charset.name() : defaultEncoding;
+ }
+
+ /**
+ * Holder for a Map of MultipartFiles and a Map of multipart parameters.
+ */
+ protected static class MultipartParsingResult {
+
+ private final MultiValueMap multipartFiles;
+ private final MultiValueMap multipartParameters;
+ private final Map multipartContentTypes;
+
+ public MultipartParsingResult(MultiValueMap mpFiles,
+ MultiValueMap mpParams,
+ Map mpParamContentTypes) {
+ this.multipartFiles = mpFiles;
+ this.multipartParameters = mpParams;
+ this.multipartContentTypes = mpParamContentTypes;
+ }
+
+ public MultiValueMap getMultipartFiles() {
+ return this.multipartFiles;
+ }
+
+ public MultiValueMap getMultipartParameters() {
+ return this.multipartParameters;
+ }
+
+ public Map getMultipartContentTypes() {
+ return this.multipartContentTypes;
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/IdGenerator.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/IdGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..7430e082d9f5550dd2fdd868e3c39ec96db78ab9
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/IdGenerator.java
@@ -0,0 +1,32 @@
+/*
+ * 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.http.session;
+
+
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+public interface IdGenerator {
+
+ /**
+ * Generate and return a new identifier.
+ *
+ * @return the newly generated id.
+ */
+
+ String generateId();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/Session.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/Session.java
new file mode 100644
index 0000000000000000000000000000000000000000..254dcc6590fefede0320fe0e367618e5e08f50f4
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/Session.java
@@ -0,0 +1,119 @@
+/*
+ * 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.http.session;
+
+
+
+
+import java.util.Enumeration;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/13.
+ */
+public interface Session {
+
+ /**
+ * Returns a string containing the unique identifier assigned to this session.
+ */
+
+ String getId();
+
+ /**
+ * Returns the time when this session was created, measured in milliseconds since midnight January 1, 1970 GMT.
+ */
+ long getCreatedTime();
+
+ /**
+ * Returns the last time the client sent a request associated with this session, as the number of milliseconds since
+ * midnight January 1, 1970 GMT, and marked by the time the container received the request.
+ *
+ * Actions that your application takes, such as getting or setting a value associated with the session, do not
+ * affect the access time.
+ */
+ long getLastAccessedTime();
+
+ /**
+ * Specifies the time, in seconds, between client requests before the server will invalidate this session.
+ *
+ *
An interval value of zero or less indicates that the session should never timeout.
+ *
+ * @param interval an integer specifying the number of seconds.
+ */
+ void setMaxInactiveInterval(int interval);
+
+ /**
+ * Returns the maximum time interval, in seconds, that the server will keep this session open between client
+ * accesses. After this interval, the server will invalidate the session. The maximum time interval can be set with
+ * the {@link #setMaxInactiveInterval(int)} method.
+ *
+ *
A return value of zero or less indicates that the session will never timeout.
+ */
+ int getMaxInactiveInterval();
+
+ /**
+ * Returns the object bound with the specified name in this session, or {@code null} if no object is bound under the
+ * name.
+ *
+ * @param name a string specifying the name of the object.
+ */
+
+ Object getAttribute(String name);
+
+ /**
+ * Returns an {@code Enumeration} of {@code String} objects containing the names of all the objects bound to this
+ * session.
+ */
+
+ Enumeration getAttributeNames();
+
+ /**
+ * Binds an object to this session, using the name specified. If an object of the same name is already bound to the
+ * session, the object is replaced.
+ *
+ * If the value passed in is null, this has the same effect as calling {@link #removeAttribute(String)}.
+ *
+ * @param name the name to which the object is bound, cannot be null.
+ * @param value the object to be bound.
+ */
+ void setAttribute(String name, Object value);
+
+ /**
+ * Removes the object bound with the specified name from this session. If the session does not have an object bound
+ * with the specified name, this method does nothing.
+ *
+ * @param name the name of the object to remove from this session.
+ */
+ void removeAttribute(String name);
+
+ /**
+ * Invalidates this session then unbinds any objects bound to it.
+ */
+ void invalidate();
+
+ /**
+ * Returns {@code true} if the client does not yet know about the session or if the client chooses not to join the
+ * session. E.g. if the server used only cookie-based sessions, and the client had disabled the use of cookies, then
+ * a session would be new on each request.
+ *
+ * @return {@code true} if the server has created a session, but the client has not yet joined.
+ */
+ boolean isNew();
+
+ /**
+ * @return returns {@code true} is the session is valid, or false if the session is invalid.
+ */
+ boolean isValid();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/SessionManager.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/SessionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f58255f9cb74903ef9ff9ad2b9f44c1dadd1e48
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/SessionManager.java
@@ -0,0 +1,71 @@
+/*
+ * 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.http.session;
+
+
+
+
+import java.io.IOException;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+public interface SessionManager {
+
+ /**
+ * Add this session to the set of active sessions for this {@code SessionManager}.
+ *
+ * @param session session to be added.
+ *
+ * @throws IOException if an output error occurs while processing this request.
+ */
+ void add(Session session) throws IOException;
+
+ /**
+ * Change the session ID of the current session to a new randomly generated session ID.
+ *
+ * @param session the session to change the session ID for.
+ */
+ void changeSessionId(Session session);
+
+ /**
+ * Create a new session object.
+ *
+ * @return an empty session object.
+ */
+
+ Session createSession();
+
+ /**
+ * Return the active session, with the specified session id (if any); otherwise return {@code null}.
+ *
+ * @param id the session id for the session to be returned.
+ *
+ * @return the request session or {@code null}.
+ *
+ * @throws IllegalStateException if a new session cannot be instantiated for any reason.
+ * @throws IOException if an output error occurs while processing this request.
+ */
+
+ Session findSession(String id) throws IOException, ClassNotFoundException;
+
+ /**
+ * Remove this session from the active sessions for this {@code SessionManager}.
+ *
+ * @param session session to be removed.
+ */
+ void remove(Session session);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardIdGenerator.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardIdGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..87c9d30fd041df6629ab10df98725a368f159935
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardIdGenerator.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.andserver.http.session;
+
+
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+public class StandardIdGenerator implements IdGenerator {
+
+ private static final int ID_LENGTH = 30;
+
+ private SecureRandom mRandom;
+
+ public StandardIdGenerator() {
+ this.mRandom = createSecureRandom();
+ }
+
+
+ @Override
+ public String generateId() {
+ byte random[] = new byte[16];
+
+ // Render the result as a String of hexadecimal digits.
+ // Start with enough space for sessionIdLength and medium route size.
+ StringBuilder buffer = new StringBuilder(2 * ID_LENGTH + 20);
+
+ int resultLenBytes = 0;
+
+ while (resultLenBytes < ID_LENGTH) {
+ mRandom.nextBytes(random);
+ for (int j = 0; j < random.length && resultLenBytes < ID_LENGTH; j++) {
+ byte b1 = (byte) ((random[j] & 0xf0) >> 4);
+ byte b2 = (byte) (random[j] & 0x0f);
+ if (b1 < 10) {
+ buffer.append((char) ('0' + b1));
+ } else {
+ buffer.append((char) ('A' + (b1 - 10)));
+ }
+ if (b2 < 10) {
+ buffer.append((char) ('0' + b2));
+ } else {
+ buffer.append((char) ('A' + (b2 - 10)));
+ }
+ resultLenBytes++;
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Create a new random number generator instance we should use for generating session identifiers.
+ */
+ private SecureRandom createSecureRandom() {
+ SecureRandom result;
+ try {
+ result = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ result = new SecureRandom();
+ }
+
+ // Force seeding to take place.
+ result.nextInt();
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardSession.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3507e22941a4a67e8c836330d2d4d71bd2559e4
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardSession.java
@@ -0,0 +1,228 @@
+/*
+ * 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.http.session;
+
+
+import com.yanzhenjie.andserver.util.Assert;
+import com.yanzhenjie.andserver.util.StringUtils;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+public class StandardSession implements Session {
+
+ private static final String EMPTY_ARRAY[] = new String[0];
+ private String id;
+ private long createdTime;
+ private long lastAccessedTime;
+ private int maxInactiveInterval = -1;
+ private Map mAttributes = new ConcurrentHashMap<>();
+ private boolean isNew;
+ private boolean isValid;
+
+ public StandardSession() {
+ }
+
+ public void setId( String id) {
+ if (StringUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("The id can not be empty or null.");
+ }
+ this.id = id;
+ }
+
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setCreatedTime(long createdTime) {
+ this.createdTime = createdTime;
+ }
+
+ @Override
+ public long getCreatedTime() {
+ return createdTime;
+ }
+
+ public void setLastAccessedTime(long lastAccessedTime) {
+ this.lastAccessedTime = lastAccessedTime;
+ }
+
+ @Override
+ public long getLastAccessedTime() {
+ validate();
+
+ return lastAccessedTime;
+ }
+
+ @Override
+ public void setMaxInactiveInterval(int interval) {
+ this.maxInactiveInterval = interval;
+ }
+
+ @Override
+ public int getMaxInactiveInterval() {
+ return maxInactiveInterval;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ validate();
+
+ if (name == null) {
+ return null;
+ }
+ return mAttributes.get(name);
+ }
+
+
+ @Override
+ public Enumeration getAttributeNames() {
+ validate();
+
+ return Collections.enumeration(new HashSet<>(mAttributes.keySet()));
+ }
+
+ @Override
+ public void setAttribute( String name, Object value) {
+ validate();
+
+ Assert.notNull(name, "The name cannot be null.");
+
+ if (value == null) {
+ return;
+ }
+ mAttributes.put(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ validate();
+
+ if (name == null) {
+ return;
+ }
+ mAttributes.remove(name);
+ }
+
+ @Override
+ public void invalidate() {
+ validate();
+
+ this.isValid = false;
+ }
+
+ public void setNew(boolean aNew) {
+ this.isNew = aNew;
+ }
+
+ @Override
+ public boolean isNew() {
+ validate();
+
+ return isNew;
+ }
+
+ private void validate() {
+ if (!isValid()) {
+ throw new IllegalStateException("This session is invalid.");
+ }
+ }
+
+ public void setValid(boolean valid) {
+ this.isValid = valid;
+ }
+
+ @Override
+ public boolean isValid() {
+ if (!isValid) {
+ return false;
+ }
+
+ if (maxInactiveInterval > 0) {
+ long inactiveInterval = System.currentTimeMillis() - lastAccessedTime;
+ int timeIdle = (int) (inactiveInterval / 1000L);
+ if (timeIdle >= maxInactiveInterval) {
+ isValid = false;
+ }
+ } else {
+ isValid = true;
+ }
+
+ return isValid;
+ }
+
+ /**
+ * Write attribute values to the stream.
+ *
+ * @param stream stream.
+ *
+ * @throws IOException if the output error occurs while processing this request.
+ */
+ public void writeObject( ObjectOutputStream stream) throws IOException {
+ stream.writeObject(id);
+ stream.writeLong(createdTime);
+ stream.writeLong(lastAccessedTime);
+ stream.writeInt(maxInactiveInterval);
+ stream.writeBoolean(isNew);
+ stream.writeBoolean(isValid);
+ stream.writeInt(mAttributes.size());
+ String keys[] = mAttributes.keySet().toArray(EMPTY_ARRAY);
+ for (String key: keys) {
+ Object value = mAttributes.get(key);
+ if (value != null && value instanceof Serializable) {
+ stream.writeObject(key);
+ stream.writeObject(value);
+ }
+ }
+ }
+
+ /**
+ * Read attribute values from the stream.
+ *
+ * @param stream stream.
+ *
+ * @throws IllegalStateException if a new session cannot be instantiated for any reason.
+ * @throws IOException if the input error occurs while processing this request.
+ */
+ public final void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ id = (String) stream.readObject();
+ createdTime = stream.readLong();
+ lastAccessedTime = stream.readLong();
+ maxInactiveInterval = stream.readInt();
+ isNew = stream.readBoolean();
+ isValid = stream.readBoolean();
+
+ // Deserialize the attribute count and attribute values
+ int size = stream.readInt();
+ for (int i = 0; i < size; i++) {
+ String name = (String) stream.readObject();
+ Object value = stream.readObject();
+ mAttributes.put(name, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardSessionManager.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardSessionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..83cb53d319386ff2df20fdadaa53a18754086452
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardSessionManager.java
@@ -0,0 +1,89 @@
+/*
+ * 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.http.session;
+
+import ohos.app.Context;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+public class StandardSessionManager implements SessionManager {
+
+ private IdGenerator mIdGenerator;
+ private Store mStore;
+
+ public StandardSessionManager(Context context) {
+ this.mIdGenerator = new StandardIdGenerator();
+
+ File sessionDir = new File(context.getCacheDir(), "_andserver_session_");
+ this.mStore = new StandardStore(sessionDir);
+ }
+
+ @Override
+ public void add( Session session) throws IOException {
+ if (session instanceof StandardSession && session.isNew()) {
+ StandardSession standardSession = (StandardSession) session;
+ standardSession.setNew(false);
+ mStore.replace(standardSession);
+ }
+ }
+
+ @Override
+ public void changeSessionId( Session session) {
+ if (session instanceof StandardSession) {
+ StandardSession standardSession = (StandardSession) session;
+ standardSession.setId(mIdGenerator.generateId());
+ }
+ }
+
+
+ @Override
+ public Session createSession() {
+ StandardSession session = newSession();
+ session.setId(mIdGenerator.generateId());
+ return session;
+ }
+
+
+ @Override
+ public Session findSession( String id) throws IOException, ClassNotFoundException {
+ StandardSession session = mStore.getSession(id);
+ if (session != null) {
+ session.setLastAccessedTime(System.currentTimeMillis());
+ }
+ return session;
+ }
+
+ @Override
+ public void remove( Session session) {
+ if (session instanceof StandardSession) {
+ mStore.remove((StandardSession) session);
+ }
+ }
+
+ private StandardSession newSession() {
+ StandardSession session = new StandardSession();
+ long currentTime = System.currentTimeMillis();
+ session.setCreatedTime(currentTime);
+ session.setLastAccessedTime(currentTime);
+ session.setNew(true);
+ session.setValid(true);
+ return session;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardStore.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardStore.java
new file mode 100644
index 0000000000000000000000000000000000000000..6adad190f099ab560fd9d9fd58f30eaf30222370
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/StandardStore.java
@@ -0,0 +1,101 @@
+/*
+ * 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.http.session;
+
+
+import com.yanzhenjie.andserver.util.Assert;
+import com.yanzhenjie.andserver.util.IOUtils;
+import com.yanzhenjie.andserver.util.StringUtils;
+
+import java.io.*;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+public class StandardStore implements Store {
+
+ private File mDirectory;
+
+ public StandardStore(File directory) {
+ this.mDirectory = directory;
+ }
+
+ @Override
+ public boolean replace( StandardSession session) throws IOException {
+ Assert.notNull(session, "The session can not be null.");
+
+ String id = session.getId();
+ if (StringUtils.isEmpty(id)) {
+ throw new IllegalStateException("The session id can not be empty or null.");
+ }
+
+ ObjectOutputStream writer = null;
+ try {
+ if (!IOUtils.createFolder(mDirectory)) {
+ return false;
+ }
+
+ File file = new File(mDirectory, id);
+ if (!IOUtils.createNewFile(file)) {
+ return false;
+ }
+
+ writer = new ObjectOutputStream(new FileOutputStream(file));
+ session.writeObject(writer);
+ return true;
+ } catch (IOException e) {
+ IOUtils.delFileOrFolder(new File(mDirectory, id));
+ throw e;
+ } finally {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+
+ @Override
+ public StandardSession getSession( String id) throws IOException, ClassNotFoundException {
+ if (id.isEmpty()) {
+ throw new IllegalArgumentException("The id can not be empty or null.");
+ }
+
+ ObjectInputStream reader = null;
+ try {
+ File file = new File(mDirectory, id);
+ if (!file.exists() || file.isDirectory()) {
+ return null;
+ }
+
+ reader = new ObjectInputStream(new FileInputStream(file));
+ StandardSession session = new StandardSession();
+ session.readObject(reader);
+ return session;
+ } catch (IOException e) {
+ IOUtils.delFileOrFolder(new File(mDirectory, id));
+ throw e;
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+ }
+
+ @Override
+ public boolean remove( StandardSession session) {
+ String id = session.getId();
+ if (id.isEmpty()) {
+ throw new IllegalStateException("The session id can not be empty or null.");
+ }
+ return IOUtils.delFileOrFolder(new File(mDirectory, session.getId()));
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/http/session/Store.java b/api/src/main/java/com/yanzhenjie/andserver/http/session/Store.java
new file mode 100644
index 0000000000000000000000000000000000000000..53d90cbd1c6b7ad3956da8e22cbe7430ffbe5eaf
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/http/session/Store.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.http.session;
+
+
+
+
+import java.io.IOException;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/26.
+ */
+interface Store {
+
+ /**
+ * Increase the session to the persistent store.
+ *
+ * @param session the session.
+ *
+ * @return true if it is successfully added or replaced, otherwise is false.
+ *
+ * @throws IOException if an output error occurs while processing this request.
+ */
+ boolean replace(StandardSession session) throws IOException;
+
+ /**
+ * Get the session from the persistent store.
+ *
+ * @param id the session ID.
+ *
+ * @return a session object.
+ *
+ * @throws IOException if the input error occurs while processing this request.
+ */
+
+ StandardSession getSession(String id) throws IOException, ClassNotFoundException;
+
+ /**
+ * Remove the session from the persistent store.
+ *
+ * @param session the session.
+ *
+ * @return true if successful removal, otherwise is false.
+ */
+ boolean remove(StandardSession session);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/register/OnRegister.java b/api/src/main/java/com/yanzhenjie/andserver/register/OnRegister.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a93909a4ef8b5c1d0eb699f7bf5c16ce83d9554
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/register/OnRegister.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.register;
+
+import ohos.app.Context;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public interface OnRegister {
+
+ /**
+ * Register the component.
+ *
+ * @param context context.
+ * @param group group name.
+ * @param register onRegister.
+ */
+ void onRegister(Context context, String group, Register register);
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/register/Register.java b/api/src/main/java/com/yanzhenjie/andserver/register/Register.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1e0444789cd3fdbf5a4842011dbc4addc00fa93
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/register/Register.java
@@ -0,0 +1,65 @@
+/*
+ * 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.register;
+
+
+import com.yanzhenjie.andserver.framework.ExceptionResolver;
+import com.yanzhenjie.andserver.framework.HandlerInterceptor;
+import com.yanzhenjie.andserver.framework.MessageConverter;
+import com.yanzhenjie.andserver.framework.config.Multipart;
+import com.yanzhenjie.andserver.framework.handler.HandlerAdapter;
+
+/**
+ * Created by Zhenjie Yan on 2018/9/10.
+ */
+public interface Register {
+
+ /**
+ * Increase the handler adapter.
+ *
+ * @param adapter {@link HandlerAdapter}.
+ */
+ void addAdapter(HandlerAdapter adapter);
+
+ /**
+ * Increase handler interceptor.
+ *
+ * @param interceptor {@link HandlerInterceptor}.
+ */
+ void addInterceptor(HandlerInterceptor interceptor);
+
+ /**
+ * Set up a message converter to convert messages that are not recognized by AndServer.
+ *
+ * @param converter {@link MessageConverter}.
+ */
+ void setConverter(MessageConverter converter);
+
+ /**
+ * Set the exception handler. If you don't want you to let AndServer output the default error message, set it to
+ * take over the exception.
+ *
+ * @param resolver {@link ExceptionResolver}.
+ */
+ void setResolver(ExceptionResolver resolver);
+
+ /**
+ * Set the parameters used to resolve the multipart request.
+ *
+ * @param multipart {@link Multipart}.
+ */
+ void setMultipart(Multipart multipart);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/server/BasicServer.java b/api/src/main/java/com/yanzhenjie/andserver/server/BasicServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e7e51bfbccc620730117e03a47785db53a5f974
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/server/BasicServer.java
@@ -0,0 +1,247 @@
+/*
+ * 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.server;
+
+import com.yanzhenjie.andserver.AndServer;
+import com.yanzhenjie.andserver.SSLSocketInitializer;
+import com.yanzhenjie.andserver.Server;
+import com.yanzhenjie.andserver.util.Executors;
+import org.apache.httpcore.ExceptionLogger;
+import org.apache.httpcore.config.SocketConfig;
+import org.apache.httpcore.impl.bootstrap.HttpServer;
+import org.apache.httpcore.impl.bootstrap.SSLServerSetupHandler;
+import org.apache.httpcore.impl.bootstrap.ServerBootstrap;
+import org.apache.httpcore.protocol.HttpRequestHandler;
+
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocket;
+import java.net.InetAddress;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by Zhenjie Yan on 3/7/20.
+ */
+public abstract class BasicServer implements Server {
+
+ static final int BUFFER = 8 * 1024;
+
+ protected final InetAddress mInetAddress;
+ protected final int mPort;
+ protected final int mTimeout;
+ protected final ServerSocketFactory mSocketFactory;
+ protected final SSLContext mSSLContext;
+ protected final SSLSocketInitializer mSSLSocketInitializer;
+ protected final Server.ServerListener mListener;
+
+ private HttpServer mHttpServer;
+ protected boolean isRunning;
+
+ BasicServer(T builder) {
+ this.mInetAddress = builder.inetAddress;
+ this.mPort = builder.port;
+ this.mTimeout = builder.timeout;
+ this.mSocketFactory = builder.mSocketFactory;
+ this.mSSLContext = builder.sslContext;
+ this.mSSLSocketInitializer = builder.mSSLSocketInitializer;
+ this.mListener = builder.listener;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+ @Override
+ public void startup() {
+ if (isRunning) {
+ return;
+ }
+
+ Executors.getInstance().execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mHttpServer = ServerBootstrap.bootstrap()
+ .setServerSocketFactory(mSocketFactory)
+ .setSocketConfig(
+ SocketConfig.custom()
+ .setSoKeepAlive(true)
+ .setSoReuseAddress(true)
+ .setTcpNoDelay(true)
+ .setSoTimeout(mTimeout)
+ .setBacklogSize(BUFFER)
+ .setRcvBufSize(BUFFER)
+ .setSndBufSize(BUFFER)
+ .setSoLinger(0)
+ .build()
+ )
+ .setLocalAddress(mInetAddress)
+ .setListenerPort(mPort)
+ .setSslContext(mSSLContext)
+ .setSslSetupHandler(new SSLSetup(mSSLSocketInitializer))
+ .setServerInfo(AndServer.INFO)
+ .registerHandler("*", requestHandler())
+ .setExceptionLogger(ExceptionLogger.NO_OP)
+ .create();
+
+ mHttpServer.start();
+ isRunning = true;
+
+ Executors.getInstance().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onStarted();
+ }
+ }
+ });
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ mHttpServer.shutdown(3, TimeUnit.SECONDS);
+ }
+ });
+ } catch (final Exception e) {
+ Executors.getInstance().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onException(e);
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * Assigns {@link HttpRequestHandler} instance.
+ */
+ protected abstract HttpRequestHandler requestHandler();
+
+ /**
+ * Quit the server.
+ */
+ @Override
+ public void shutdown() {
+ if (!isRunning) {
+ return;
+ }
+
+ Executors.getInstance().execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mHttpServer != null) {
+ mHttpServer.shutdown(3, TimeUnit.SECONDS);
+ isRunning = false;
+ Executors.getInstance().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onStopped();
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
+ private static final class SSLSetup implements SSLServerSetupHandler {
+
+ private final SSLSocketInitializer mInitializer;
+
+ public SSLSetup( SSLSocketInitializer initializer) {
+ this.mInitializer = initializer;
+ }
+
+ @Override
+ public void initialize(SSLServerSocket socket) throws SSLException {
+ mInitializer.onCreated(socket);
+ }
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ if (isRunning) {
+ return mHttpServer.getInetAddress();
+ }
+ throw new IllegalStateException("The server has not been started yet.");
+ }
+
+ @Override
+ public int getPort() {
+ if (isRunning) {
+ return mHttpServer.getLocalPort();
+ }
+ throw new IllegalStateException("The server has not been started yet.");
+ }
+
+ protected abstract static class Builder {
+
+ InetAddress inetAddress;
+ int port;
+ int timeout;
+ ServerSocketFactory mSocketFactory;
+ SSLContext sslContext;
+ SSLSocketInitializer mSSLSocketInitializer;
+ Server.ServerListener listener;
+
+ Builder() {
+ }
+ @SuppressWarnings("unchecked")
+ public T inetAddress(InetAddress inetAddress) {
+ this.inetAddress = inetAddress;
+ return (T) this;
+ }
+ @SuppressWarnings("unchecked")
+ public T port(int port) {
+ this.port = port;
+ return (T) this;
+ }
+ @SuppressWarnings("unchecked")
+ public T timeout(int timeout, TimeUnit timeUnit) {
+ long timeoutMs = timeUnit.toMillis(timeout);
+ this.timeout = (int) Math.min(timeoutMs, Integer.MAX_VALUE);
+ return (T) this;
+ }
+ @SuppressWarnings("unchecked")
+ public T serverSocketFactory(ServerSocketFactory factory) {
+ this.mSocketFactory = factory;
+ return (T) this;
+ }
+ @SuppressWarnings("unchecked")
+ public T sslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ return (T) this;
+ }
+ @SuppressWarnings("unchecked")
+ public T sslSocketInitializer(SSLSocketInitializer initializer) {
+ this.mSSLSocketInitializer = initializer;
+ return (T) this;
+ }
+ @SuppressWarnings("unchecked")
+ public T listener(Server.ServerListener listener) {
+ this.listener = listener;
+ return (T) this;
+ }
+
+ public abstract S build();
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/server/ProxyServer.java b/api/src/main/java/com/yanzhenjie/andserver/server/ProxyServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bbaee6ef8d4db3d2adc2c5c79a70bfab80322ba
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/server/ProxyServer.java
@@ -0,0 +1,378 @@
+/*
+ * 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.server;
+
+
+import com.yanzhenjie.andserver.AndServer;
+import com.yanzhenjie.andserver.ProxyHandler;
+import com.yanzhenjie.andserver.SSLSocketInitializer;
+import com.yanzhenjie.andserver.Server;
+import com.yanzhenjie.andserver.util.Executors;
+import org.apache.httpcore.ConnectionClosedException;
+import org.apache.httpcore.HttpException;
+import org.apache.httpcore.HttpHost;
+import org.apache.httpcore.HttpServerConnection;
+import org.apache.httpcore.impl.DefaultBHttpClientConnection;
+import org.apache.httpcore.impl.DefaultBHttpServerConnection;
+import org.apache.httpcore.protocol.*;
+
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Created by Zhenjie Yan on 3/7/20.
+ */
+public class ProxyServer extends BasicServer {
+
+ public static final String PROXY_CONN_CLIENT = "http.proxy.conn.client";
+ public static final String PROXY_CONN_ALIVE = "http.proxy.conn.alive";
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ private final InetAddress mInetAddress;
+ private final int mPort;
+ private final int mTimeout;
+ private final ServerSocketFactory mSocketFactory;
+ private final SSLContext mSSLContext;
+ private final SSLSocketInitializer mSSLSocketInitializer;
+ private final Server.ServerListener mListener;
+
+ private Map mHostList;
+
+ private HttpServer mHttpServer;
+ private boolean isRunning;
+
+ private ProxyServer(Builder builder) {
+ super(builder);
+ this.mInetAddress = builder.inetAddress;
+ this.mPort = builder.port;
+ this.mTimeout = builder.timeout;
+ this.mSocketFactory = builder.mSocketFactory;
+ this.mSSLContext = builder.sslContext;
+ this.mSSLSocketInitializer = builder.mSSLSocketInitializer;
+ this.mListener = builder.listener;
+
+ this.mHostList = builder.mHostList;
+ }
+
+ @Override
+ protected HttpRequestHandler requestHandler() {
+ return new ProxyHandler(mHostList);
+ }
+
+ @Override
+ public void startup() {
+ if (isRunning) {
+ return;
+ }
+
+ Executors.getInstance().execute(new Runnable() {
+ @Override
+ public void run() {
+ ServerSocketFactory socketFactory = mSocketFactory;
+ if (socketFactory == null) {
+ if (mSSLContext != null) {
+ socketFactory = mSSLContext.getServerSocketFactory();
+ } else {
+ socketFactory = ServerSocketFactory.getDefault();
+ }
+ }
+
+ mHttpServer = new HttpServer(mInetAddress,
+ mPort,
+ mTimeout,
+ socketFactory,
+ mSSLSocketInitializer,
+ requestHandler());
+ try {
+ mHttpServer.startServer();
+ isRunning = true;
+
+ Executors.getInstance().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onStarted();
+ }
+ }
+ });
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ mHttpServer.stopServer();
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void shutdown() {
+ if (!isRunning) {
+ return;
+ }
+
+ Executors.getInstance().execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mHttpServer != null) {
+ mHttpServer.stopServer();
+ isRunning = false;
+ Executors.getInstance().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onStopped();
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
+ public static class Builder extends BasicServer.Builder
+ implements Server.ProxyBuilder {
+
+ private Map mHostList = new HashMap<>();
+
+ public Builder() {
+ }
+
+ @Override
+ public Builder addProxy(String hostName, String proxyHost) {
+ mHostList.put(hostName.toLowerCase(Locale.ROOT), HttpHost.create(proxyHost));
+ return this;
+ }
+
+ @Override
+ public ProxyServer build() {
+ return new ProxyServer(this);
+ }
+ }
+
+ private static class HttpServer implements Runnable {
+
+ private final InetAddress mInetAddress;
+ private final int mPort;
+ private final int mTimeout;
+ private final ServerSocketFactory mSocketFactory;
+ private final SSLSocketInitializer mSSLSocketInitializer;
+ private final HttpRequestHandler mHandler;
+
+ private final ThreadPoolExecutor mServerExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
+ new SynchronousQueue<>(), new ThreadFactoryImpl("HTTP-Server-"));
+ private final ThreadGroup mWorkerThreads = new ThreadGroup("HTTP-workers");
+ private final ThreadPoolExecutor mWorkerExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 1L,
+ TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactoryImpl("HTTP-Handlers-", mWorkerThreads)) {
+ @Override
+ protected void beforeExecute(Thread t, Runnable r) {
+ if (r instanceof Worker) {
+ mWorkerSet.put((Worker) r, Boolean.TRUE);
+ }
+ }
+
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ if (r instanceof Worker) {
+ mWorkerSet.remove(r);
+ }
+ }
+ };
+ private final Map mWorkerSet = new ConcurrentHashMap<>();
+
+ private HttpService mHttpService;
+ private ServerSocket mServerSocket;
+
+ public HttpServer(InetAddress inetAddress, int port, int timeout, ServerSocketFactory socketFactory,
+ SSLSocketInitializer sslSocketInitializer, HttpRequestHandler handler) {
+ this.mInetAddress = inetAddress;
+ this.mPort = port;
+ this.mTimeout = timeout;
+ this.mSocketFactory = socketFactory;
+ this.mSSLSocketInitializer = sslSocketInitializer;
+ this.mHandler = handler;
+
+ HttpProcessor inProcessor = new ImmutableHttpProcessor(
+ new ResponseDate(),
+ new ResponseServer(AndServer.INFO),
+ new ResponseContent(),
+ new ResponseConnControl());
+
+ UriHttpRequestHandlerMapper mapper = new UriHttpRequestHandlerMapper();
+ mapper.register("*", mHandler);
+
+ this.mHttpService = new HttpService(inProcessor, mapper);
+ }
+
+ public void startServer() throws IOException {
+ mServerSocket = mSocketFactory.createServerSocket();
+ mServerSocket.setReuseAddress(true);
+ mServerSocket.bind(new InetSocketAddress(mInetAddress, mPort), BUFFER);
+ mServerSocket.setReceiveBufferSize(BUFFER);
+ if (mSSLSocketInitializer != null && mServerSocket instanceof SSLServerSocket) {
+ mSSLSocketInitializer.onCreated((SSLServerSocket) mServerSocket);
+ }
+
+ mServerExecutor.execute(this);
+ }
+
+ public void stopServer() {
+ mServerExecutor.shutdown();
+ mWorkerExecutor.shutdown();
+ try {
+ mServerSocket.close();
+ } catch (IOException ignored) {
+ }
+ mWorkerThreads.interrupt();
+
+ try {
+ mWorkerExecutor.awaitTermination(3, TimeUnit.SECONDS);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+ Set workers = mWorkerSet.keySet();
+ for (Worker worker : workers) {
+ HttpServerConnection conn = worker.getServerConn();
+ try {
+ conn.shutdown();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!Thread.interrupted()) {
+ Socket socket = mServerSocket.accept();
+ socket.setSoTimeout(mTimeout);
+ socket.setKeepAlive(true);
+ socket.setTcpNoDelay(true);
+ socket.setReceiveBufferSize(BUFFER);
+ socket.setSendBufferSize(BUFFER);
+ socket.setSoLinger(true, 0);
+
+ DefaultBHttpServerConnection serverConn = new DefaultBHttpServerConnection(BUFFER);
+ serverConn.bind(socket);
+
+ DefaultBHttpClientConnection clientConn = new DefaultBHttpClientConnection(BUFFER);
+ Worker worker = new Worker(mHttpService, serverConn, clientConn);
+
+ mWorkerExecutor.execute(worker);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private static class Worker implements Runnable {
+
+ private final HttpService mHttpService;
+ private final DefaultBHttpServerConnection mServerConn;
+ private final DefaultBHttpClientConnection mClientConn;
+
+ public Worker(HttpService httpservice,
+ DefaultBHttpServerConnection serverConn, DefaultBHttpClientConnection clientConn) {
+ this.mHttpService = httpservice;
+ this.mServerConn = serverConn;
+ this.mClientConn = clientConn;
+ }
+
+ public DefaultBHttpServerConnection getServerConn() {
+ return mServerConn;
+ }
+
+ @Override
+ public void run() {
+ BasicHttpContext localContext = new BasicHttpContext();
+ HttpCoreContext context = HttpCoreContext.adapt(localContext);
+ context.setAttribute(PROXY_CONN_CLIENT, mClientConn);
+
+ try {
+ while (!Thread.interrupted()) {
+ if (!mServerConn.isOpen()) {
+ mClientConn.close();
+ break;
+ }
+
+ mHttpService.handleRequest(mServerConn, context);
+
+ Boolean keepAlive = (Boolean) context.getAttribute(PROXY_CONN_ALIVE);
+ if (!Boolean.TRUE.equals(keepAlive)) {
+ mClientConn.close();
+ mServerConn.close();
+ break;
+ }
+ }
+ } catch (ConnectionClosedException ex) {
+ System.err.println("Client closed connection.");
+ } catch (IOException ex) {
+ System.err.println("I/O error: " + ex.getMessage());
+ } catch (HttpException ex) {
+ System.err.println("Unrecoverable HTTP protocol violation: " + ex.getMessage());
+ } finally {
+ try {
+ mServerConn.shutdown();
+ } catch (IOException ignore) {
+ }
+ try {
+ mClientConn.shutdown();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ private static class ThreadFactoryImpl implements ThreadFactory {
+ private final String mPrefix;
+ private final ThreadGroup mGroup;
+ private final AtomicLong mCount;
+
+ ThreadFactoryImpl(String prefix, ThreadGroup group) {
+ this.mPrefix = prefix;
+ this.mGroup = group;
+ this.mCount = new AtomicLong();
+ }
+
+ ThreadFactoryImpl(String mPrefix) {
+ this(mPrefix, null);
+ }
+
+ @Override
+ public Thread newThread( Runnable target) {
+ return new Thread(mGroup, target, mPrefix + "-" + mCount.incrementAndGet());
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/server/WebServer.java b/api/src/main/java/com/yanzhenjie/andserver/server/WebServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbd3a3e92f912037d52ea4a9fda1db527a296d2d
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/server/WebServer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server;
+
+import com.yanzhenjie.andserver.ComponentRegister;
+import com.yanzhenjie.andserver.DispatcherHandler;
+import com.yanzhenjie.andserver.Server;
+import ohos.app.Context;
+import org.apache.httpcore.protocol.HttpRequestHandler;
+
+/**
+ * Created by Zhenjie Yan on 3/7/20.
+ */
+public class WebServer extends BasicServer {
+
+ public static Builder newBuilder(Context context, String group) {
+ return new Builder(context, group);
+ }
+
+ private Context mContext;
+ private String mGroup;
+
+ private WebServer(Builder builder) {
+ super(builder);
+ this.mContext = builder.context;
+ this.mGroup = builder.group;
+ }
+
+ @Override
+ protected HttpRequestHandler requestHandler() {
+ DispatcherHandler handler = new DispatcherHandler(mContext);
+ ComponentRegister register = new ComponentRegister(mContext);
+ try {
+ register.register(handler, mGroup);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ return handler;
+ }
+
+ public static class Builder extends BasicServer.Builder
+ implements Server.Builder {
+
+ private Context context;
+ private String group;
+
+ private Builder(Context context, String group) {
+ this.context = context;
+ this.group = group;
+ }
+
+ @Override
+ public WebServer build() {
+ return new WebServer(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/util/AcceptLanguage.java b/api/src/main/java/com/yanzhenjie/andserver/util/AcceptLanguage.java
new file mode 100644
index 0000000000000000000000000000000000000000..8419bf515452d3366b981d056dd98efcac1a043e
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/util/AcceptLanguage.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.util;
+
+import com.yanzhenjie.andserver.http.FAcceptLanguage;
+
+import java.util.Locale;
+
+/**
+ * Created by Zhenjie Yan on 2018/8/7.
+ *
+ * @deprecated use {@link FAcceptLanguage} instead.
+ */
+@Deprecated
+public class AcceptLanguage extends FAcceptLanguage {
+
+ protected AcceptLanguage(Locale locale, double quality) {
+ super(locale, quality);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/util/Assert.java b/api/src/main/java/com/yanzhenjie/andserver/util/Assert.java
new file mode 100644
index 0000000000000000000000000000000000000000..04173d6db82f52e36928c5384dfb7fb51f0245fe
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/util/Assert.java
@@ -0,0 +1,329 @@
+/*
+ * 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.util;
+
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/5.
+ */
+public abstract class Assert {
+
+ /**
+ * Assert a boolean expression, throwing an {@code IllegalStateException} if the expression evaluates to {@code
+ * false}.
+ *
+ * Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} on an assertion failure.
Assert.state(id == null, "The id property must not already be initialized");
+ *
+ * @param expression a boolean expression.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalStateException if {@code expression} is {@code false}
+ */
+ public static void state(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing an {@code IllegalArgumentException} if the expression evaluates to {@code
+ * false}.
+ *
+ * Assert.isTrue(i > 0, "The value must be greater than zero");
+ *
+ * @param expression a boolean expression.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if {@code expression} is {@code false}.
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is {@code null}.
+ *
+ * Assert.isNull(value, "The value must be null");
+ *
+ * @param object the object to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the object is not {@code null}.
+ */
+ public static void isNull(Object object, String message) {
+ if (object != null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is not {@code null}.
+ *
+ * Assert.notNull(clazz, "The class must not be null");
+ *
+ * @param object the object to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the object is {@code null}
+ */
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+
+ /**
+ * Assert that the given String is not empty; that is, it must not be {@code null} and not the empty String.
+ *
+ * Assert.hasLength(name, "Name must not be empty");
+ *
+ * @param text the String to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the text is empty.
+ */
+ public static void hasLength(String text, String message) {
+ if (text.isEmpty()) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String contains valid text content; that is, it must not be {@code null} and must contain
+ * at least one non-whitespace character.
+ *
+ * Assert.hasText(name, "'name' must not be empty");
+ *
+ * @param text the String to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the text does not contain valid text content.
+ */
+ public static void hasText(String text, String message) {
+ if (text.isEmpty()) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ *
+ * Assert .doesNotContain(name, "rod", "Name must not contain 'rod'");
+ *
+ * @param textToSearch the text to search.
+ * @param substring the substring to find within the text.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the text contains the substring.
+ */
+ public static void doesNotContain(String textToSearch, String substring, String message) {
+ if (!textToSearch.isEmpty() && !substring.isEmpty() && textToSearch.contains(substring)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an array contains elements; that is, it must not be {@code null} and must contain at least one
+ * element.
+ *
+ * Assert.notEmpty(array, "The array must contain elements");
+ *
+ * @param array the array to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the object array is {@code null} or contains no elements.
+ */
+ public static void notEmpty(Object[] array, String message) {
+ if (array == null || array.length == 0) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an array contains no {@code null} elements. Note: Does not complain if the array is empty!
+ *
+ *
Assert.noNullElements(array, "The array must contain non-null elements");
+ *
+ * @param array the array to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the object array contains a {@code null} element.
+ */
+ public static void noNullElements(Object[] array, String message) {
+ if (array != null) {
+ for (Object element: array) {
+ if (element == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assert that a collection contains elements; that is, it must not be {@code null} and must contain at least one
+ * element.
+ *
+ * Assert.notEmpty(collection, "Collection must contain elements");
+ *
+ * @param collection the collection to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the collection is {@code null} or contains no elements.
+ */
+ public static void notEmpty(Collection> collection, String message) {
+ if (collection == null || collection.isEmpty()) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a Map contains entries; that is, it must not be {@code null} and must contain at least one entry.
+ *
+ * Assert.notEmpty(map, "Map must contain entries");
+ *
+ * @param map the map to check.
+ * @param message the exception message to use if the assertion fails.
+ *
+ * @throws IllegalArgumentException if the map is {@code null} or contains no entries.
+ */
+ public static void notEmpty(Map, ?> map, String message) {
+ if (map == null || map.isEmpty()) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ *
+ * Assert.instanceOf(Foo.class, foo, "Foo expected");
+ *
+ * @param type the type to check against.
+ * @param obj the object to check.
+ * @param message a message which will be prepended to provide further context. If it is empty or ends in ":" or
+ * ";" or "," or ".", a full exception message will be appended. If it ends in a space, the name of the
+ * offending object's type will be appended. In any other case, a ":" with a space and the name of the offending
+ * object's type will be appended.
+ *
+ * @throws IllegalArgumentException if the object is not an instance of type.
+ */
+ public static void isInstanceOf(Class> type, Object obj, String message) {
+ notNull(type, "Type to check against must not be null");
+
+ if (!type.isInstance(obj)) {
+ instanceCheckFailed(type, obj, message);
+ }
+ }
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ *
+ * Assert.instanceOf(Foo.class, foo);
+ *
+ * @param type the type to check against.
+ * @param obj the object to check.
+ *
+ * @throws IllegalArgumentException if the object is not an instance of type.
+ */
+ public static void isInstanceOf(Class> type, Object obj) {
+ isInstanceOf(type, obj, "");
+ }
+
+ /**
+ * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
+ *
+ * Assert.isAssignable(Number.class, myClass, "Number expected");
+ *
+ * @param superType the super type to check against.
+ * @param subType the sub type to check.
+ * @param message a message which will be prepended to provide further context. If it is empty or ends in ":" or
+ * ";" or "," or ".", a full exception message will be appended. If it ends in a space, the name of the
+ * offending sub type will be appended. In any other case, a ":" with a space and the name of the offending sub
+ * type will be appended.
+ *
+ * @throws IllegalArgumentException if the classes are not assignable.
+ */
+ public static void isAssignable(Class> superType, Class> subType, String message) {
+ notNull(superType, "Super type to check against must not be null");
+
+ if (subType == null || !superType.isAssignableFrom(subType)) {
+ assignableCheckFailed(superType, subType, message);
+ }
+ }
+
+ /**
+ * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
+ *
+ * Assert.isAssignable(Number.class, myClass);
+ *
+ * @param superType the super type to check.
+ * @param subType the sub type to check.
+ *
+ * @throws IllegalArgumentException if the classes are not assignable.
+ */
+ public static void isAssignable(Class> superType, Class> subType) {
+ isAssignable(superType, subType, "");
+ }
+
+ private static void instanceCheckFailed(Class> type, Object obj, String msg) {
+ String className = (obj != null ? obj.getClass().getName() : "null");
+ String result = "";
+ boolean defaultMessage = true;
+ if (!msg.isEmpty()) {
+ if (endsWithSeparator(msg)) {
+ result = msg + " ";
+ } else {
+ result = messageWithTypeName(msg, className);
+ defaultMessage = false;
+ }
+ }
+ if (defaultMessage) {
+ result = result + ("Object of class [" + className + "] must be an instance of " + type);
+ }
+ throw new IllegalArgumentException(result);
+ }
+
+ private static void assignableCheckFailed(Class> superType, Class> subType, String msg) {
+ String result = "";
+ boolean defaultMessage = true;
+ if (!msg.isEmpty()) {
+ if (endsWithSeparator(msg)) {
+ result = msg + " ";
+ } else {
+ result = messageWithTypeName(msg, subType);
+ defaultMessage = false;
+ }
+ }
+ if (defaultMessage) {
+ result = result + (subType + " is not assignable to " + superType);
+ }
+ throw new IllegalArgumentException(result);
+ }
+
+ private static boolean endsWithSeparator(String msg) {
+ return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith("."));
+ }
+
+ private static String messageWithTypeName(String msg, Object typeName) {
+ return msg + (msg.endsWith(" ") ? "" : ": ") + typeName;
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/yanzhenjie/andserver/util/CollectionUtils.java b/api/src/main/java/com/yanzhenjie/andserver/util/CollectionUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d80c7a3596c0496d52b3237b3355e012a1344018
--- /dev/null
+++ b/api/src/main/java/com/yanzhenjie/andserver/util/CollectionUtils.java
@@ -0,0 +1,527 @@
+/*
+ * 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.util;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * Created by Zhenjie Yan on 2018/7/11.
+ *
+ * @deprecated use apache commons-collection instead.
+ */
+@Deprecated
+public abstract class CollectionUtils {
+
+ /**
+ * Return {@code true} if the supplied Collection is {@code null} or empty. Otherwise, return {@code false}.
+ *
+ * @param collection the Collection to check.
+ *
+ * @return whether the given Collection is empty.
+ */
+ public static boolean isEmpty(Collection> collection) {
+ return (collection == null || collection.isEmpty());
+ }
+
+ /**
+ * Return {@code true} if the supplied Map is {@code null} or empty. Otherwise, return {@code false}.
+ *
+ * @param map the Map to check.
+ *
+ * @return whether the given Map is empty.
+ */
+ public static boolean isEmpty(Map, ?> map) {
+ return (map == null || map.isEmpty());
+ }
+
+ /**
+ * Convert the supplied array into a List. StandardCookieProcessor primitive array gets converted into a List of the
+ * appropriate wrapper type.
+ *
+ * NOTE: Generally prefer the standard {@link Arrays#asList} method. This {@code arrayToList} method is
+ * just meant to deal with an incoming Object value that might be an {@code Object[]} or a primitive array at
+ * runtime.
+ *
+ *
StandardCookieProcessor {@code null} source value will be converted to an empty List.
+ *
+ * @param source the (potentially primitive) array.
+ *
+ * @return the converted List result.
+ */
+ public static List arrayToList(Object source) {
+ return Arrays.asList(ObjectUtils.toObjectArray(source));
+ }
+
+ /**
+ * Merge the given array into the given Collection.
+ *
+ * @param array the array to merge (may be {@code null}).
+ * @param collection the target Collection to merge the array into.
+ */
+ @SuppressWarnings("unchecked")
+ public static void mergeArrayIntoCollection(Object array, Collection collection) {
+ if (collection == null) {
+ throw new IllegalArgumentException("Collection must not be null");
+ }
+ Object[] arr = ObjectUtils.toObjectArray(array);
+ for (Object elem: arr) {
+ collection.add((E) elem);
+ }
+ }
+
+ /**
+ * Merge the given Properties instance into the given Map, copying all properties (key-value pairs) over. Uses
+ * {@code Properties.propertyNames()} to even catch default properties linked into the original Properties
+ * instance.
+ *
+ * @param props the Properties instance to merge (may be {@code null}).
+ * @param map the target Map to merge the properties into.
+ */
+ @SuppressWarnings("unchecked")
+ public static void mergePropertiesIntoMap(Properties props, Map map) {
+ if (map == null) {
+ throw new IllegalArgumentException("Map must not be null");
+ }
+ if (props != null) {
+ for (Enumeration> en = props.propertyNames(); en.hasMoreElements(); ) {
+ String key = (String) en.nextElement();
+ Object value = props.get(key);
+ if (value == null) {
+ // Allow for defaults fallback or potentially overridden accessor...
+ value = props.getProperty(key);
+ }
+ map.put((K) key, (V) value);
+ }
+ }
+ }
+
+
+ /**
+ * Check whether the given Iterator contains the given element.
+ *
+ * @param iterator the Iterator to check.
+ * @param element the element to look for.
+ *
+ * @return {@code true} if found, {@code false} else.
+ */
+ public static boolean contains(Iterator> iterator, Object element) {
+ if (iterator != null) {
+ while (iterator.hasNext()) {
+ Object candidate = iterator.next();
+ if (ObjectUtils.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Enumeration contains the given element.
+ *
+ * @param enumeration the Enumeration to check.
+ * @param element the element to look for.
+ *
+ * @return {@code true} if found, {@code false} else.
+ */
+ public static boolean contains(Enumeration> enumeration, Object element) {
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ Object candidate = enumeration.nextElement();
+ if (ObjectUtils.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Collection contains the given element instance. Enforces the given instance to be
+ * present, rather than returning {@code true} for an equal element as well.
+ *
+ * @param collection the Collection to check.
+ * @param element the element to look for.
+ *
+ * @return {@code true} if found, {@code false} else.
+ */
+ public static boolean containsInstance(Collection> collection, Object element) {
+ if (collection != null) {
+ for (Object candidate: collection) {
+ if (candidate == element) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return {@code true} if any element in '{@code candidates}' is contained in '{@code source}'; otherwise returns
+ * {@code false}.
+ *
+ * @param source the source Collection.
+ * @param candidates the candidates to search for.
+ *
+ * @return whether any of the candidates has been found.
+ */
+ public static boolean containsAny(Collection> source, Collection> candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return false;
+ }
+ for (Object candidate: candidates) {
+ if (source.contains(candidate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the first element in '{@code candidates}' that is contained in '{@code source}'. If no element in '{@code
+ * candidates}' is present in '{@code source}' returns {@code null}. Iteration order is {@link Collection}
+ * implementation specific.
+ *
+ * @param source the source Collection.
+ * @param candidates the candidates to search for.
+ *
+ * @return the first present object, or {@code null} if not found.
+ */
+ @SuppressWarnings("unchecked")
+ public static E findFirstMatch(Collection> source, Collection