diff --git a/.gitignore b/.gitignore
index 84c94f69..44f72053 100755
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@
out
gen
+# Visual Studio Code
+.history/
+
# Maven
target/
pom.xml.tag
diff --git a/sentinel-transport/sentinel-transport-simple-http/pom.xml b/sentinel-transport/sentinel-transport-simple-http/pom.xml
index a588e490..b7121aba 100755
--- a/sentinel-transport/sentinel-transport-simple-http/pom.xml
+++ b/sentinel-transport/sentinel-transport-simple-http/pom.xml
@@ -17,5 +17,10 @@
sentinel-transport-common
${project.version}
+
+ junit
+ junit
+ test
+
\ No newline at end of file
diff --git a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/exception/RequestException.java b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/exception/RequestException.java
new file mode 100644
index 00000000..3b778090
--- /dev/null
+++ b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/exception/RequestException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.csp.sentinel.transport.command.exception;
+
+import com.alibaba.csp.sentinel.transport.command.http.StatusCode;
+
+/**
+ * Represent exception with status code processing a request
+ *
+ * @author jason
+ *
+ */
+public class RequestException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private StatusCode statusCode = StatusCode.BAD_REQUEST;
+
+ public RequestException() {
+ super();
+ }
+
+ public RequestException(StatusCode statusCode, String msg) {
+ super(msg);
+ this.statusCode = statusCode;
+ }
+
+ public StatusCode getStatusCode() {
+ return statusCode;
+ }
+}
diff --git a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTask.java b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTask.java
index 7658955b..e9fb9e2b 100755
--- a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTask.java
+++ b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTask.java
@@ -15,9 +15,22 @@
*/
package com.alibaba.csp.sentinel.transport.command.http;
-import java.io.BufferedReader;
+import com.alibaba.csp.sentinel.command.CommandHandler;
+import com.alibaba.csp.sentinel.command.CommandRequest;
+import com.alibaba.csp.sentinel.command.CommandResponse;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.log.CommandCenterLog;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter;
+import com.alibaba.csp.sentinel.transport.command.exception.RequestException;
+import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.Closeable;
-import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
@@ -25,15 +38,9 @@ import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
-import com.alibaba.csp.sentinel.command.CommandHandler;
-import com.alibaba.csp.sentinel.command.CommandRequest;
-import com.alibaba.csp.sentinel.command.CommandResponse;
-import com.alibaba.csp.sentinel.config.SentinelConfig;
-import com.alibaba.csp.sentinel.log.CommandCenterLog;
-import com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter;
-import com.alibaba.csp.sentinel.transport.util.HttpCommandUtils;
-import com.alibaba.csp.sentinel.util.StringUtil;
/***
* The task handles incoming command request in HTTP protocol.
@@ -42,6 +49,7 @@ import com.alibaba.csp.sentinel.util.StringUtil;
* @author Eric Zhao
*/
public class HttpEventTask implements Runnable {
+ private static final String SERVER_ERROR_MESSAGE = "Command server error";
private final Socket socket;
@@ -61,86 +69,30 @@ public class HttpEventTask implements Runnable {
return;
}
- BufferedReader in = null;
PrintWriter printWriter = null;
+ InputStream inputStream = null;
try {
long start = System.currentTimeMillis();
- in = new BufferedReader(new InputStreamReader(socket.getInputStream(), SentinelConfig.charset()));
+ inputStream = new BufferedInputStream(socket.getInputStream());
OutputStream outputStream = socket.getOutputStream();
printWriter = new PrintWriter(
new OutputStreamWriter(outputStream, Charset.forName(SentinelConfig.charset())));
- String line = in.readLine();
- CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + line
+ String firstLine = readLine(inputStream);
+ CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + firstLine
+ ", addr: " + socket.getInetAddress());
- CommandRequest request = parseRequest(line);
+ CommandRequest request = processQueryString(firstLine);
- if (line.length() > 4 && StringUtil.equalsIgnoreCase("POST", line.substring(0, 4))) {
+ if (firstLine.length() > 4 && StringUtil.equalsIgnoreCase("POST", firstLine.substring(0, 4))) {
// Deal with post method
- // Now simple-http only support form-encoded post request.
- String bodyLine = null;
- boolean bodyNext = false;
- boolean supported = false;
- int maxLength = 8192;
- while (true) {
- // Body processing
- if (bodyNext) {
- if (!supported) {
- break;
- }
- char[] bodyBytes = new char[maxLength];
- int read = in.read(bodyBytes);
- String postData = new String(bodyBytes, 0, read);
- parseParams(postData, request);
- break;
- }
-
- bodyLine = in.readLine();
- if (bodyLine == null) {
- break;
- }
- // Body separator
- if (StringUtil.isEmpty(bodyLine)) {
- bodyNext = true;
- continue;
- }
- // Header processing
- int index = bodyLine.indexOf(":");
- if (index < 1) {
- continue;
- }
- String headerName = bodyLine.substring(0, index);
- String header = bodyLine.substring(index + 1).trim();
- if (StringUtil.equalsIgnoreCase("content-type", headerName)) {
- int idx = header.indexOf(";");
- if (idx > 0) {
- header = header.substring(0, idx).trim();
- }
- if (StringUtil.equals("application/x-www-form-urlencoded", header)) {
- supported = true;
- } else {
- CommandCenterLog.warn("Content-Type not supported: " + header);
- // not support request
- break;
- }
- } else if (StringUtil.equalsIgnoreCase("content-length", headerName)) {
- try {
- int len = Integer.parseInt(header);
- if (len > 0) {
- maxLength = len;
- }
- } catch (Exception e) {
- CommandCenterLog.warn("Malformed content-length header value: " + header);
- }
- }
- }
+ processPostRequest(inputStream, request);
}
// Validate the target command.
String commandName = HttpCommandUtils.getTarget(request);
if (StringUtil.isBlank(commandName)) {
- badRequest(printWriter, "Invalid command");
+ writeResponse(printWriter, StatusCode.BAD_REQUEST, "Invalid command");
return;
}
@@ -148,23 +100,25 @@ public class HttpEventTask implements Runnable {
CommandHandler> commandHandler = SimpleHttpCommandCenter.getHandler(commandName);
if (commandHandler != null) {
CommandResponse> response = commandHandler.handle(request);
- handleResponse(response, printWriter, outputStream);
+ handleResponse(response, printWriter);
} else {
// No matching command handler.
- badRequest(printWriter, "Unknown command `" + commandName + '`');
+ writeResponse(printWriter, StatusCode.BAD_REQUEST, "Unknown command `" + commandName + '`');
}
- printWriter.flush();
long cost = System.currentTimeMillis() - start;
- CommandCenterLog.info("[SimpleHttpCommandCenter] Deal a socket task: " + line
+ CommandCenterLog.info("[SimpleHttpCommandCenter] Deal a socket task: " + firstLine
+ ", address: " + socket.getInetAddress() + ", time cost: " + cost + " ms");
+ } catch (RequestException e) {
+ writeResponse(printWriter, e.getStatusCode(), e.getMessage());
} catch (Throwable e) {
CommandCenterLog.warn("[SimpleHttpCommandCenter] CommandCenter error", e);
try {
if (printWriter != null) {
String errorMessage = SERVER_ERROR_MESSAGE;
+ e.printStackTrace();
if (!writtenHead) {
- internalError(printWriter, errorMessage);
+ writeResponse(printWriter, StatusCode.INTERNAL_SERVER_ERROR, errorMessage);
} else {
printWriter.println(errorMessage);
}
@@ -174,11 +128,169 @@ public class HttpEventTask implements Runnable {
CommandCenterLog.warn("[SimpleHttpCommandCenter] Close server socket failed", e);
}
} finally {
- closeResource(in);
+ closeResource(inputStream);
closeResource(printWriter);
closeResource(socket);
}
}
+
+ private static String readLine(InputStream in) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(64);
+ int data;
+ while (true) {
+ data = in.read();
+ if (data < 0) {
+ break;
+ }
+ if (data == '\n') {
+ break;
+ }
+ bos.write(data);
+ }
+ byte[] arr = bos.toByteArray();
+ if (arr.length > 0 && arr[arr.length - 1] == '\r') {
+ return new String(arr, 0, arr.length - 1, SentinelConfig.charset());
+ }
+ return new String(arr, SentinelConfig.charset());
+ }
+
+ /**
+ * Try to process the body of POST request additionally.
+ *
+ * @param in
+ * @param request
+ * @throws RequestException
+ * @throws IOException
+ */
+ protected static void processPostRequest(InputStream in, CommandRequest request)
+ throws RequestException, IOException {
+ Map headerMap = parsePostHeaders(in);
+
+ if (headerMap == null) {
+ // illegal request
+ RecordLog.warn("Illegal request read");
+ throw new RequestException(StatusCode.BAD_REQUEST, "");
+ }
+
+ if (headerMap.containsKey("content-type") && !checkSupport(headerMap.get("content-type"))) {
+ // not support Content-type
+ RecordLog.warn("Not supported Content-Type: {}", headerMap.get("content-type"));
+ throw new RequestException(StatusCode.UNSUPPORTED_MEDIA_TYPE,
+ "Only form-encoded post request is supported");
+ }
+
+ int bodyLength = 0;
+ try {
+ bodyLength = Integer.parseInt(headerMap.get("content-length"));
+ } catch (Exception e) {
+ }
+ if (bodyLength < 1) {
+ // illegal request without Content-length header
+ RecordLog.warn("No available Content-Length in headers");
+ throw new RequestException(StatusCode.LENGTH_REQUIRED, "No legal Content-Length");
+ }
+
+ parseParams(readBody(in, bodyLength), request);
+ }
+
+ /**
+ * Process header line in request
+ *
+ * @param in
+ * @return return headers in a Map, null for illegal request
+ * @throws IOException
+ */
+ protected static Map parsePostHeaders(InputStream in) throws IOException {
+ Map headerMap = new HashMap(4);
+ String line;
+ while (true) {
+ line = readLine(in);
+ if (line == null || line.length() == 0) {
+ // empty line
+ return headerMap;
+ }
+ int index = line.indexOf(":");
+ if (index < 1) {
+ // empty value, abandon
+ continue;
+ }
+ String headerName = line.substring(0, index).trim().toLowerCase();
+ String headerValue = line.substring(index + 1).trim();
+ if (headerValue.length() > 0) {
+ headerMap.put(headerName, headerValue);
+ }
+ }
+ }
+
+ private static boolean checkSupport(String contentType) {
+ int idx = contentType.indexOf(";");
+ String type;
+ if (idx > 0) {
+ type = contentType.substring(0, idx).toLowerCase().trim();
+ } else {
+ type = contentType.toLowerCase();
+ }
+ // Actually in RFC "x-*" shouldn't have any properties like "type/subtype; key=val"
+ // But some library do add it. So we will be compatible with that but force to
+ // encoding specified in configuration as legacy processing will do.
+ if (!type.contains("application/x-www-form-urlencoded")) {
+ CommandCenterLog.warn("Content-Type not supported: " + contentType);
+ // Not supported request type
+ // Now simple-http only support form-encoded post request.
+ return false;
+ }
+ return true;
+ }
+
+ private static String readBody(InputStream in, int bodyLength)
+ throws IOException, RequestException {
+ byte[] buf = new byte[bodyLength];
+ int pos = 0;
+ while (pos < bodyLength) {
+ int l = in.read(buf, pos, Math.min(512, bodyLength - pos));
+ if (l < 0) {
+ break;
+ }
+ if (l == 0) {
+ continue;
+ }
+ pos += l;
+ }
+ // Only allow partial
+ return new String(buf, 0, pos, SentinelConfig.charset());
+ }
+
+ /**
+ * Consume all the body submitted and parse params into {@link CommandRequest}
+ *
+ * @param queryString
+ * @param request
+ */
+ protected static void parseParams(String queryString, CommandRequest request) {
+ if (queryString == null || queryString.length() < 1) {
+ return;
+ }
+
+ int offset = 0, pos = -1;
+
+ // check anchor
+ queryString = removeAnchor(queryString);
+
+ while (true) {
+ offset = pos + 1;
+ pos = queryString.indexOf('&', offset);
+ if (offset == pos) {
+ // empty
+ continue;
+ }
+ parseSingleParam(queryString.substring(offset, pos == -1 ? queryString.length() : pos), request);
+
+ if (pos < 0) {
+ // reach the end
+ break;
+ }
+ }
+ }
private void closeResource(Closeable closeable) {
if (closeable != null) {
@@ -190,56 +302,31 @@ public class HttpEventTask implements Runnable {
}
}
- private void handleResponse(CommandResponse response, /*@NonNull*/ final PrintWriter printWriter,
- /*@NonNull*/ final OutputStream rawOutputStream) throws Exception {
+ private void handleResponse(CommandResponse response, final PrintWriter printWriter) throws Exception {
if (response.isSuccess()) {
if (response.getResult() == null) {
- writeOkStatusLine(printWriter);
+ writeResponse(printWriter, StatusCode.OK, null);
return;
}
- // Write 200 OK status line.
- writeOkStatusLine(printWriter);
// Here we directly use `toString` to encode the result to plain text.
byte[] buffer = response.getResult().toString().getBytes(SentinelConfig.charset());
- rawOutputStream.write(buffer);
- rawOutputStream.flush();
+ writeResponse(printWriter, StatusCode.OK, new String(buffer));
} else {
String msg = SERVER_ERROR_MESSAGE;
if (response.getException() != null) {
msg = response.getException().getMessage();
}
- badRequest(printWriter, msg);
+ writeResponse(printWriter, StatusCode.BAD_REQUEST, msg);
}
}
-
- /**
- * Write `400 Bad Request` HTTP response status line and message body, then flush.
- */
- private void badRequest(/*@NonNull*/ final PrintWriter out, String message) {
- out.print("HTTP/1.1 400 Bad Request\r\n"
- + "Connection: close\r\n\r\n");
- out.print(message);
- out.flush();
- writtenHead = true;
- }
-
- /**
- * Write `500 Internal Server Error` HTTP response status line and message body, then flush.
- */
- private void internalError(/*@NonNull*/ final PrintWriter out, String message) {
- out.print("HTTP/1.1 500 Internal Server Error\r\n"
- + "Connection: close\r\n\r\n");
- out.print(message);
- out.flush();
- writtenHead = true;
- }
-
- /**
- * Write `200 OK` HTTP response status line and flush.
- */
- private void writeOkStatusLine(/*@NonNull*/ final PrintWriter out) {
- out.print("HTTP/1.1 200 OK\r\n"
- + "Connection: close\r\n\r\n");
+
+ private void writeResponse(PrintWriter out, StatusCode statusCode, String message) {
+ out.print("HTTP/1.0 " + statusCode.toString() + "\r\n"
+ + "Content-Length: " + (message == null ? 0 : message.getBytes().length) + "\r\n"
+ + "Connection: close\r\n\r\n");
+ if (message != null) {
+ out.print(message);
+ }
out.flush();
writtenHead = true;
}
@@ -250,7 +337,7 @@ public class HttpEventTask implements Runnable {
* @param line HTTP request line
* @return parsed command request
*/
- private CommandRequest parseRequest(String line) {
+ protected static CommandRequest processQueryString(String line) {
CommandRequest request = new CommandRequest();
if (StringUtil.isBlank(line)) {
return request;
@@ -267,28 +354,49 @@ public class HttpEventTask implements Runnable {
parseParams(parameterStr, request);
return request;
}
-
- private void parseParams(String queryString, CommandRequest request) {
- for (String parameter : queryString.split("&")) {
- if (StringUtil.isBlank(parameter)) {
- continue;
- }
-
- String[] keyValue = parameter.split("=");
- if (keyValue.length != 2) {
- continue;
- }
-
- String value = StringUtil.trim(keyValue[1]);
- try {
- value = URLDecoder.decode(value, SentinelConfig.charset());
- } catch (UnsupportedEncodingException e) {
- }
-
- request.addParam(StringUtil.trim(keyValue[0]), value);
+
+ /**
+ * Truncate query from "a=1&b=2#mark" to "a=1&b=2"
+ *
+ * @param str
+ * @return
+ */
+ protected static String removeAnchor(String str) {
+ if (str == null || str.length() == 0) {
+ return str;
}
+
+ int anchor = str.indexOf('#');
+
+ if (anchor == 0) {
+ return "";
+ } else if (anchor > 0) {
+ return str.substring(0, anchor);
+ }
+
+ return str;
}
+
+ protected static void parseSingleParam(String single, CommandRequest request) {
+ if (single == null || single.length() < 3) {
+ return;
+ }
+
+ int index = single.indexOf('=');
+ if (index <= 0 || index >= single.length() - 1) {
+ // empty key/val or nothing found
+ return;
+ }
- private static final String SERVER_ERROR_MESSAGE = "Command server error";
+ String value = StringUtil.trim(single.substring(index + 1));
+ String key = StringUtil.trim(single.substring(0, index));
+ try {
+ key = URLDecoder.decode(key, SentinelConfig.charset());
+ value = URLDecoder.decode(value, SentinelConfig.charset());
+ } catch (UnsupportedEncodingException e) {
+ }
+
+ request.addParam(key, value);
+ }
}
diff --git a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java
new file mode 100644
index 00000000..c8eada37
--- /dev/null
+++ b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/http/StatusCode.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.csp.sentinel.transport.command.http;
+
+public enum StatusCode {
+ OK(200, "OK"),
+ BAD_REQUEST(400, "Bad Request"),
+ REQUEST_TIMEOUT(408, "Request Timeout"),
+ LENGTH_REQUIRED(411, "Length Required"),
+ UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
+ INTERNAL_SERVER_ERROR(500, "Internal Server Error");
+
+ private int code;
+ private String desc;
+ private String representation;
+
+ private StatusCode(int code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ this.representation = code + " " + desc;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public String toString() {
+ return representation;
+ }
+}
diff --git a/sentinel-transport/sentinel-transport-simple-http/src/test/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTaskTest.java b/sentinel-transport/sentinel-transport-simple-http/src/test/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTaskTest.java
new file mode 100644
index 00000000..3b669223
--- /dev/null
+++ b/sentinel-transport/sentinel-transport-simple-http/src/test/java/com/alibaba/csp/sentinel/transport/command/http/HttpEventTaskTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.csp.sentinel.transport.command.http;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.alibaba.csp.sentinel.command.CommandRequest;
+import com.alibaba.csp.sentinel.transport.command.exception.RequestException;
+
+public class HttpEventTaskTest {
+
+ @Test
+ public void processQueryString() {
+ CommandRequest request;
+
+ request = HttpEventTask.processQueryString(null);
+ assertNotNull(request);
+
+ request = HttpEventTask.processQueryString(null);
+ assertNotNull(request);
+
+ request = HttpEventTask.processQueryString("get /?a=1&b=2&c=3#mark HTTP/1.0");
+ assertNotNull(request);
+ assertEquals("1", request.getParam("a"));
+ assertEquals("2", request.getParam("b"));
+ assertEquals("3", request.getParam("c"));
+
+ request = HttpEventTask.processQueryString("post /test?a=3&b=4&c=3#mark HTTP/1.0");
+ assertNotNull(request);
+ assertEquals("3", request.getParam("a"));
+ assertEquals("4", request.getParam("b"));
+ assertEquals("3", request.getParam("c"));
+ }
+
+ @Test
+ public void removeAnchor() {
+ assertNull(HttpEventTask.removeAnchor(null));
+ assertEquals("", HttpEventTask.removeAnchor(""));
+ assertEquals("", HttpEventTask.removeAnchor("#mark"));
+ assertEquals("a", HttpEventTask.removeAnchor("a#mark"));
+ }
+
+ @Test
+ public void parseSingleParam() {
+ CommandRequest request;
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam(null, request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("a", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("=", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("a=", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("=a", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("test=", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("=test", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("a=1", request);
+ assertEquals(1, request.getParameters().size());
+ assertEquals("1", request.getParam("a"));
+
+ request = new CommandRequest();
+ HttpEventTask.parseSingleParam("a_+=1+", request);
+ assertEquals(1, request.getParameters().size());
+ assertEquals("1 ", request.getParam("a_ "));
+ }
+
+ @Test
+ public void parseParams() {
+ CommandRequest request;
+
+ // mixed
+ request = new CommandRequest();
+ HttpEventTask.parseParams("a=1&&b&=3&&c=4&a_+1=3_3%20&%E7%9A%84=test%E7%9A%84#mark", request);
+ assertEquals(4, request.getParameters().size());
+ assertEquals("1", request.getParam("a"));
+ assertNull(request.getParam("b"));
+ assertEquals("4", request.getParam("c"));
+ assertEquals("3_3 ", request.getParam("a_ 1"));
+ assertEquals("test的", request.getParam("的"));
+
+ request = new CommandRequest();
+ HttpEventTask.parseParams(null, request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseParams("", request);
+ assertEquals(0, request.getParameters().size());
+
+ request = new CommandRequest();
+ HttpEventTask.parseParams("&&b&=3&", request);
+ assertEquals(0, request.getParameters().size());
+ }
+
+ @Test
+ public void parsePostHeaders() throws IOException {
+ Map map;
+
+ map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("".getBytes()));
+ assertTrue(map.size() == 0);
+
+ map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("Content-type: test \r\n\r\nbody".getBytes()));
+ assertEquals("test", map.get("content-type"));
+
+ map = HttpEventTask.parsePostHeaders(new ByteArrayInputStream("Content-Encoding: utf-8\r\n\r\nbody".getBytes()));
+ assertEquals("utf-8", map.get("content-encoding"));
+ }
+
+ @Test
+ public void processPostRequest() throws IOException {
+ CommandRequest request;
+
+ request = new CommandRequest();
+ request.addParam("a", "1");
+
+ // illegal(empty) request
+ try {
+ HttpEventTask.processPostRequest(new ByteArrayInputStream("".getBytes()), request);
+ assertFalse(true); // should not reach here
+ } catch (Exception e) {
+ assertTrue(e instanceof RequestException);
+ }
+ assertEquals("1", request.getParam("a"));
+
+ // normal request
+ try {
+ HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
+ "Accept: */*\r\n" +
+ "Accept-Language: en-us\r\n" +
+ "Accept-Encoding: gzip, deflate\r\n" +
+ "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Content-Length: 10\r\n" +
+ "\r\n" +
+ "a=3&b=5的").getBytes()), request);
+ assertEquals("3", request.getParam("a"));
+ assertEquals("5的", request.getParam("b"));
+ } catch (Exception e) {
+ assertTrue(false); // should not reach here
+ }
+
+ // not supported request
+ try {
+ HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
+ "Accept: */*\r\n" +
+ "Accept-Language: en-us\r\n" +
+ "Accept-Encoding: gzip, deflate\r\n" +
+ "Content-Type: application/json\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Content-Length: 7\r\n" +
+ "\r\n" +
+ "a=1&b=2").getBytes()), request);
+ assertTrue(false); // should not reach here
+ } catch (RequestException e) {
+ assertTrue(e.getStatusCode() == StatusCode.UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ // Capacity test
+ char[] buf = new char[1024 * 1024];
+ Arrays.fill(buf, '&');
+ String padding = new String(buf);
+ try {
+ request = new CommandRequest();
+ HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
+ "Accept: */*\r\n" +
+ "Accept-Language: en-us\r\n" +
+ "Accept-Encoding: gzip, deflate\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Content-Length: 7\r\n" +
+ "\r\n" +
+ padding +
+ "a=1&b=2").getBytes()), request);
+ assertEquals(0, request.getParameters().size());
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ try {
+ String querystring = "a+=+&b=%E7%9A%84的";
+ request = new CommandRequest();
+ HttpEventTask.processPostRequest(new ByteArrayInputStream(("Host: demo.com\r\n" +
+ "Accept: */*\r\n" +
+ "Accept-Language: en-us\r\n" +
+ "Accept-Encoding: gzip, deflate\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: keep-alive\r\n" +
+ "Content-Length: " + (padding.length() + querystring.getBytes().length) + "\r\n" +
+ "\r\n" +
+ padding +
+ querystring).getBytes()), request);
+ assertEquals(2, request.getParameters().size());
+ assertEquals(" ", request.getParam("a "));
+ assertEquals("的的", request.getParam("b"));
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ }
+}