init
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sentinel-transport</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandlerProvider;
|
||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||
import com.alibaba.csp.sentinel.transport.log.CommandCenterLog;
|
||||
import com.alibaba.csp.sentinel.transport.CommandCenter;
|
||||
import com.alibaba.csp.sentinel.transport.command.http.HttpEventTask;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/***
|
||||
* The simple command center provides service to exchange information.
|
||||
*
|
||||
* @author youji.zj
|
||||
*/
|
||||
public class SimpleHttpCommandCenter implements CommandCenter {
|
||||
|
||||
private static final int PORT_UNINITIALIZED = -1;
|
||||
|
||||
private static final int DEFAULT_SERVER_SO_TIMEOUT = 3000;
|
||||
private static final int DEFAULT_PORT = 8719;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final Map<String, CommandHandler> handlerMap = new ConcurrentHashMap<String, CommandHandler>();
|
||||
|
||||
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor(
|
||||
new NamedThreadFactory("sentinel-command-center-executor", true));
|
||||
private ExecutorService bizExecutor;
|
||||
|
||||
private ServerSocket socketReference;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void beforeStart() throws Exception {
|
||||
// Register handlers
|
||||
Map<String, CommandHandler> handlers = CommandHandlerProvider.getInstance().namedHandlers();
|
||||
registerCommands(handlers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
int nThreads = Runtime.getRuntime().availableProcessors();
|
||||
this.bizExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(10),
|
||||
new NamedThreadFactory("sentinel-command-center-service-executor", true),
|
||||
new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
CommandCenterLog.info("EventTask rejected");
|
||||
throw new RejectedExecutionException();
|
||||
}
|
||||
});
|
||||
|
||||
Runnable serverInitTask = new Runnable() {
|
||||
int port;
|
||||
|
||||
{
|
||||
try {
|
||||
port = Integer.parseInt(TransportConfig.getPort());
|
||||
} catch (Exception e) {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean success = false;
|
||||
ServerSocket serverSocket = getServerSocketFromBasePort(port);
|
||||
|
||||
if (serverSocket != null) {
|
||||
CommandCenterLog.info("[CommandCenter] Begin listening at port " + serverSocket.getLocalPort());
|
||||
socketReference = serverSocket;
|
||||
executor.submit(new ServerThread(serverSocket));
|
||||
success = true;
|
||||
port = serverSocket.getLocalPort();
|
||||
} else {
|
||||
CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work");
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
port = PORT_UNINITIALIZED;
|
||||
}
|
||||
|
||||
TransportConfig.setRuntimePort(port);
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
new Thread(serverInitTask).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a server socket from an available port from a base port.<br>
|
||||
* Increasing on port number will occur when the port has already been used.
|
||||
*
|
||||
* @param basePort base port to start
|
||||
* @return new socket with available port
|
||||
*/
|
||||
private static ServerSocket getServerSocketFromBasePort(int basePort) {
|
||||
int tryCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
ServerSocket server = new ServerSocket(basePort + tryCount / 3, 100);
|
||||
server.setReuseAddress(true);
|
||||
return server;
|
||||
} catch (IOException e) {
|
||||
tryCount++;
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(30);
|
||||
} catch (InterruptedException e1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (socketReference != null) {
|
||||
try {
|
||||
socketReference.close();
|
||||
} catch (IOException e) {
|
||||
CommandCenterLog.warn("Error when releasing the server socket", e);
|
||||
}
|
||||
}
|
||||
bizExecutor.shutdownNow();
|
||||
executor.shutdownNow();
|
||||
TransportConfig.setRuntimePort(PORT_UNINITIALIZED);
|
||||
handlerMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name set of all registered commands.
|
||||
*/
|
||||
public static Set<String> getCommands() {
|
||||
return handlerMap.keySet();
|
||||
}
|
||||
|
||||
class ServerThread extends Thread {
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
ServerThread(ServerSocket s) {
|
||||
this.serverSocket = s;
|
||||
setName("sentinel-courier-server-accept-thread");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
Socket socket = null;
|
||||
try {
|
||||
socket = this.serverSocket.accept();
|
||||
setSocketSoTimeout(socket);
|
||||
HttpEventTask eventTask = new HttpEventTask(socket);
|
||||
bizExecutor.submit(eventTask);
|
||||
} catch (Exception e) {
|
||||
CommandCenterLog.info("Server error", e);
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception e1) {
|
||||
CommandCenterLog.info("Error when closing an opened socket", e1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
// In case of infinite log.
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e1) {
|
||||
// Indicates the task should stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static CommandHandler getHandler(String commandName) {
|
||||
return handlerMap.get(commandName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void registerCommands(Map<String, CommandHandler> handlerMap) {
|
||||
if (handlerMap != null) {
|
||||
for (Entry<String, CommandHandler> e : handlerMap.entrySet()) {
|
||||
registerCommand(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void registerCommand(String commandName, CommandHandler handler) {
|
||||
if (StringUtil.isEmpty(commandName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handlerMap.containsKey(commandName)) {
|
||||
CommandCenterLog.warn("Register failed (duplicate command): " + commandName);
|
||||
return;
|
||||
}
|
||||
|
||||
handlerMap.put(commandName, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid server thread hang, 3 seconds timeout by default.
|
||||
*/
|
||||
private void setSocketSoTimeout(Socket socket) throws SocketException {
|
||||
if (socket != null) {
|
||||
socket.setSoTimeout(DEFAULT_SERVER_SO_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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 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.transport.log.CommandCenterLog;
|
||||
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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The task handles incoming command request in HTTP protocol.
|
||||
*
|
||||
* @author youji.zj
|
||||
* @author Eric Zhao
|
||||
* @author Jason Joo
|
||||
*/
|
||||
public class HttpEventTask implements Runnable {
|
||||
|
||||
public static final String SERVER_ERROR_MESSAGE = "Command server error";
|
||||
public static final String INVALID_COMMAND_MESSAGE = "Invalid command";
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private boolean writtenHead = false;
|
||||
|
||||
public HttpEventTask(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (socket == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PrintWriter printWriter = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
inputStream = new BufferedInputStream(socket.getInputStream());
|
||||
OutputStream outputStream = socket.getOutputStream();
|
||||
|
||||
printWriter = new PrintWriter(
|
||||
new OutputStreamWriter(outputStream, Charset.forName(SentinelConfig.charset())));
|
||||
|
||||
String firstLine = readLine(inputStream);
|
||||
CommandCenterLog.info("[SimpleHttpCommandCenter] Socket income: " + firstLine
|
||||
+ ", addr: " + socket.getInetAddress());
|
||||
CommandRequest request = processQueryString(firstLine);
|
||||
|
||||
if (firstLine.length() > 4 && StringUtil.equalsIgnoreCase("POST", firstLine.substring(0, 4))) {
|
||||
// Deal with post method
|
||||
processPostRequest(inputStream, request);
|
||||
}
|
||||
|
||||
// Validate the target command.
|
||||
String commandName = HttpCommandUtils.getTarget(request);
|
||||
if (StringUtil.isBlank(commandName)) {
|
||||
writeResponse(printWriter, StatusCode.BAD_REQUEST, INVALID_COMMAND_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the matching command handler.
|
||||
CommandHandler<?> commandHandler = SimpleHttpCommandCenter.getHandler(commandName);
|
||||
if (commandHandler != null) {
|
||||
CommandResponse<?> response = commandHandler.handle(request);
|
||||
handleResponse(response, printWriter);
|
||||
} else {
|
||||
// No matching command handler.
|
||||
writeResponse(printWriter, StatusCode.BAD_REQUEST, "Unknown command `" + commandName + '`');
|
||||
}
|
||||
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
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) {
|
||||
writeResponse(printWriter, StatusCode.INTERNAL_SERVER_ERROR, errorMessage);
|
||||
} else {
|
||||
printWriter.println(errorMessage);
|
||||
}
|
||||
printWriter.flush();
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
CommandCenterLog.warn("Failed to write error response", e1);
|
||||
}
|
||||
} finally {
|
||||
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<String, String> headerMap = parsePostHeaders(in);
|
||||
|
||||
if (headerMap == null) {
|
||||
// illegal request
|
||||
CommandCenterLog.warn("Illegal request read: null headerMap");
|
||||
throw new RequestException(StatusCode.BAD_REQUEST, "");
|
||||
}
|
||||
|
||||
if (headerMap.containsKey("content-type") && !checkContentTypeSupported(headerMap.get("content-type"))) {
|
||||
// not supported Content-type
|
||||
CommandCenterLog.warn("Request not supported: unsupported 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
|
||||
CommandCenterLog.warn("Request not supported: 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<String, String> parsePostHeaders(InputStream in) throws IOException {
|
||||
Map<String, String> headerMap = new HashMap<String, String>(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 checkContentTypeSupported(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")) {
|
||||
// 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) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
CommandCenterLog.warn("[SimpleHttpCommandCenter] Close resource failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void handleResponse(CommandResponse<T> response, final PrintWriter printWriter) throws Exception {
|
||||
if (response.isSuccess()) {
|
||||
if (response.getResult() == null) {
|
||||
writeResponse(printWriter, StatusCode.OK, null);
|
||||
return;
|
||||
}
|
||||
// Here we directly use `toString` to encode the result to plain text.
|
||||
byte[] buffer = response.getResult().toString().getBytes(SentinelConfig.charset());
|
||||
writeResponse(printWriter, StatusCode.OK, new String(buffer));
|
||||
} else {
|
||||
String msg = SERVER_ERROR_MESSAGE;
|
||||
if (response.getException() != null) {
|
||||
msg = response.getException().getMessage();
|
||||
}
|
||||
writeResponse(printWriter, StatusCode.BAD_REQUEST, msg);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw HTTP request line to a {@link CommandRequest}.
|
||||
*
|
||||
* @param line HTTP request line
|
||||
* @return parsed command request
|
||||
*/
|
||||
protected static CommandRequest processQueryString(String line) {
|
||||
CommandRequest request = new CommandRequest();
|
||||
if (StringUtil.isBlank(line)) {
|
||||
return request;
|
||||
}
|
||||
int start = line.indexOf('/');
|
||||
int ask = line.indexOf('?') == -1 ? line.lastIndexOf(' ') : line.indexOf('?');
|
||||
int space = line.lastIndexOf(' ');
|
||||
String target = line.substring(start != -1 ? start + 1 : 0, ask != -1 ? ask : line.length());
|
||||
request.addMetadata(HttpCommandUtils.REQUEST_TARGET, target);
|
||||
if (ask == -1 || ask == space) {
|
||||
return request;
|
||||
}
|
||||
String parameterStr = line.substring(ask != -1 ? ask + 1 : 0, space != -1 ? space : line.length());
|
||||
parseParams(parameterStr, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Jason Joo
|
||||
*/
|
||||
public enum StatusCode {
|
||||
/**
|
||||
* 200 OK.
|
||||
*/
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.heartbeat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.util.AppNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.HostNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* Heart beat message entity.
|
||||
* The message consists of key-value pair parameters.
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class HeartbeatMessage {
|
||||
|
||||
private final Map<String, String> message = new HashMap<String, String>();
|
||||
|
||||
public HeartbeatMessage() {
|
||||
message.put("hostname", HostNameUtil.getHostName());
|
||||
message.put("ip", TransportConfig.getHeartbeatClientIp());
|
||||
message.put("app", AppNameUtil.getAppName());
|
||||
// Put application type (since 1.6.0).
|
||||
message.put("app_type", String.valueOf(SentinelConfig.getAppType()));
|
||||
message.put("port", String.valueOf(TransportConfig.getPort()));
|
||||
}
|
||||
|
||||
public HeartbeatMessage registerInformation(String key, String value) {
|
||||
message.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> generateCurrentMessage() {
|
||||
// Version of Sentinel.
|
||||
message.put("v", Constants.SENTINEL_VERSION);
|
||||
// Actually timestamp.
|
||||
message.put("version", String.valueOf(TimeUtil.currentTimeMillis()));
|
||||
message.put("port", String.valueOf(TransportConfig.getPort()));
|
||||
return message;
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.heartbeat;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpClient;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpRequest;
|
||||
import com.alibaba.csp.sentinel.transport.heartbeat.client.SimpleHttpResponse;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The heartbeat sender provides basic API for sending heartbeat request to provided target.
|
||||
* This implementation is based on a trivial HTTP client.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @author Carpenter Lee
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SimpleHttpHeartbeatSender implements HeartbeatSender {
|
||||
|
||||
private static final int OK_STATUS = 200;
|
||||
|
||||
private static final long DEFAULT_INTERVAL = 1000 * 10;
|
||||
|
||||
private final HeartbeatMessage heartBeat = new HeartbeatMessage();
|
||||
private final SimpleHttpClient httpClient = new SimpleHttpClient();
|
||||
|
||||
private final List<Endpoint> addressList;
|
||||
|
||||
private int currentAddressIdx = 0;
|
||||
|
||||
public SimpleHttpHeartbeatSender() {
|
||||
// Retrieve the list of default addresses.
|
||||
List<Endpoint> newAddrs = TransportConfig.getConsoleServerList();
|
||||
if (newAddrs.isEmpty()) {
|
||||
RecordLog.warn("[SimpleHttpHeartbeatSender] Dashboard server address not configured or not available");
|
||||
} else {
|
||||
RecordLog.info("[SimpleHttpHeartbeatSender] Default console address list retrieved: {}", newAddrs);
|
||||
}
|
||||
this.addressList = newAddrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendHeartbeat() throws Exception {
|
||||
if (TransportConfig.getRuntimePort() <= 0) {
|
||||
RecordLog.info("[SimpleHttpHeartbeatSender] Command server port not initialized, won't send heartbeat");
|
||||
return false;
|
||||
}
|
||||
Endpoint addrInfo = getAvailableAddress();
|
||||
if (addrInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SimpleHttpRequest request = new SimpleHttpRequest(addrInfo, TransportConfig.getHeartbeatApiPath());
|
||||
request.setParams(heartBeat.generateCurrentMessage());
|
||||
try {
|
||||
SimpleHttpResponse response = httpClient.post(request);
|
||||
if (response.getStatusCode() == OK_STATUS) {
|
||||
return true;
|
||||
} else if (clientErrorCode(response.getStatusCode()) || serverErrorCode(response.getStatusCode())) {
|
||||
RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo
|
||||
+ ", http status code: " + response.getStatusCode());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[SimpleHttpHeartbeatSender] Failed to send heartbeat to " + addrInfo, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long intervalMs() {
|
||||
return DEFAULT_INTERVAL;
|
||||
}
|
||||
|
||||
private Endpoint getAvailableAddress() {
|
||||
if (addressList == null || addressList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (currentAddressIdx < 0) {
|
||||
currentAddressIdx = 0;
|
||||
}
|
||||
int index = currentAddressIdx % addressList.size();
|
||||
return addressList.get(index);
|
||||
}
|
||||
|
||||
private boolean clientErrorCode(int code) {
|
||||
return code > 399 && code < 500;
|
||||
}
|
||||
|
||||
private boolean serverErrorCode(int code) {
|
||||
return code > 499 && code < 600;
|
||||
}
|
||||
}
|
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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.heartbeat.client;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A very simple HTTP client that only supports GET/POST method and plain text request body.
|
||||
* The Content-Type header is always set as <pre>application/x-www-form-urlencoded</pre>.
|
||||
* All parameters in the request will be encoded using {@link URLEncoder#encode(String, String)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* The result of a HTTP invocation will be wrapped as a {@link SimpleHttpResponse}. Content in response body
|
||||
* will be automatically decoded to string with provided charset.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is a blocking and synchronous client, so an invocation will await the response until timeout exceed.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is a very NAIVE client, {@code Content-Length} must be specified in the
|
||||
* HTTP response header, otherwise, the response body will be dropped. All other body type such as
|
||||
* {@code Transfer-Encoding: chunked}, {@code Transfer-Encoding: deflate} are not supported.
|
||||
* </p>
|
||||
*
|
||||
* @author leyou
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SimpleHttpClient {
|
||||
|
||||
/**
|
||||
* Execute a GET HTTP request.
|
||||
*
|
||||
* @param request HTTP request
|
||||
* @return the response if the request is successful
|
||||
* @throws IOException when connection cannot be established or the connection is interrupted
|
||||
*/
|
||||
public SimpleHttpResponse get(SimpleHttpRequest request) throws IOException {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return request(request.getEndpoint(),
|
||||
RequestMethod.GET, request.getRequestPath(), request.getParams(),
|
||||
request.getCharset(), request.getSoTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a POST HTTP request.
|
||||
*
|
||||
* @param request HTTP request
|
||||
* @return the response if the request is successful
|
||||
* @throws IOException when connection cannot be established or the connection is interrupted
|
||||
*/
|
||||
public SimpleHttpResponse post(SimpleHttpRequest request) throws IOException {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return request(request.getEndpoint(),
|
||||
RequestMethod.POST, request.getRequestPath(),
|
||||
request.getParams(), request.getCharset(),
|
||||
request.getSoTimeout());
|
||||
}
|
||||
|
||||
private SimpleHttpResponse request(Endpoint endpoint,
|
||||
RequestMethod type, String requestPath,
|
||||
Map<String, String> paramsMap, Charset charset, int soTimeout)
|
||||
throws IOException {
|
||||
Socket socket = null;
|
||||
BufferedWriter writer;
|
||||
InetSocketAddress socketAddress = new InetSocketAddress(endpoint.getHost(), endpoint.getPort());
|
||||
try {
|
||||
socket = SocketFactory.getSocket(endpoint.getProtocol());
|
||||
socket.setSoTimeout(soTimeout);
|
||||
socket.connect(socketAddress, soTimeout);
|
||||
|
||||
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charset));
|
||||
requestPath = getRequestPath(type, requestPath, paramsMap, charset);
|
||||
writer.write(getStatusLine(type, requestPath) + "\r\n");
|
||||
if (charset != null) {
|
||||
writer.write("Content-Type: application/x-www-form-urlencoded; charset=" + charset.name() + "\r\n");
|
||||
} else {
|
||||
writer.write("Content-Type: application/x-www-form-urlencoded\r\n");
|
||||
}
|
||||
writer.write("Host: " + socketAddress.getHostName() + "\r\n");
|
||||
if (type == RequestMethod.GET) {
|
||||
writer.write("Content-Length: 0\r\n");
|
||||
writer.write("\r\n");
|
||||
} else {
|
||||
// POST method.
|
||||
String params = encodeRequestParams(paramsMap, charset);
|
||||
writer.write("Content-Length: " + params.getBytes(charset).length + "\r\n");
|
||||
writer.write("\r\n");
|
||||
writer.write(params);
|
||||
}
|
||||
writer.flush();
|
||||
|
||||
SimpleHttpResponse response = new SimpleHttpResponseParser().parse(socket.getInputStream());
|
||||
socket.close();
|
||||
socket = null;
|
||||
return response;
|
||||
} finally {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("Error when closing {} request to {} in SimpleHttpClient", type, socketAddress, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getRequestPath(RequestMethod type, String requestPath,
|
||||
Map<String, String> paramsMap, Charset charset) {
|
||||
if (type == RequestMethod.GET) {
|
||||
if (requestPath.contains("?")) {
|
||||
return requestPath + "&" + encodeRequestParams(paramsMap, charset);
|
||||
}
|
||||
return requestPath + "?" + encodeRequestParams(paramsMap, charset);
|
||||
}
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
private String getStatusLine(RequestMethod type, String requestPath) {
|
||||
if (type == RequestMethod.POST) {
|
||||
return "POST " + requestPath + " HTTP/1.1";
|
||||
}
|
||||
return "GET " + requestPath + " HTTP/1.1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode and get the URL request parameters.
|
||||
*
|
||||
* @param paramsMap pair of parameters
|
||||
* @param charset charset
|
||||
* @return encoded request parameters, or empty string ("") if no parameters are provided
|
||||
*/
|
||||
private String encodeRequestParams(Map<String, String> paramsMap, Charset charset) {
|
||||
if (charset == null) {
|
||||
throw new IllegalArgumentException("charset is not allowed to be null");
|
||||
}
|
||||
if (paramsMap == null || paramsMap.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
StringBuilder paramsBuilder = new StringBuilder();
|
||||
for (Entry<String, String> entry : paramsMap.entrySet()) {
|
||||
if (entry.getKey() == null || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
paramsBuilder.append(URLEncoder.encode(entry.getKey(), charset.name()))
|
||||
.append("=")
|
||||
.append(URLEncoder.encode(entry.getValue(), charset.name()))
|
||||
.append("&");
|
||||
}
|
||||
if (paramsBuilder.length() > 0) {
|
||||
// Remove the last '&'.
|
||||
paramsBuilder.delete(paramsBuilder.length() - 1, paramsBuilder.length());
|
||||
}
|
||||
return paramsBuilder.toString();
|
||||
} catch (Throwable e) {
|
||||
RecordLog.warn("Encode request params fail", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private enum RequestMethod {
|
||||
GET,
|
||||
POST
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.heartbeat.client;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
|
||||
|
||||
/**
|
||||
* Simple HTTP request representation.
|
||||
*
|
||||
* @author leyou
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SimpleHttpRequest {
|
||||
|
||||
private Endpoint endpoint;
|
||||
private String requestPath = "";
|
||||
private int soTimeout = 3000;
|
||||
private Map<String, String> params;
|
||||
private Charset charset = Charset.forName(SentinelConfig.charset());
|
||||
|
||||
public SimpleHttpRequest(Endpoint endpoint, String requestPath) {
|
||||
this.endpoint = endpoint;
|
||||
this.requestPath = requestPath;
|
||||
}
|
||||
|
||||
public Endpoint getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setEndpoint(Endpoint endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRequestPath() {
|
||||
return requestPath;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setRequestPath(String requestPath) {
|
||||
this.requestPath = requestPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSoTimeout() {
|
||||
return soTimeout;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setSoTimeout(int soTimeout) {
|
||||
this.soTimeout = soTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setParams(Map<String, String> params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleHttpRequest addParam(String key, String value) {
|
||||
if (StringUtil.isBlank(key)) {
|
||||
throw new IllegalArgumentException("Parameter key cannot be empty");
|
||||
}
|
||||
if (params == null) {
|
||||
params = new HashMap<String, String>();
|
||||
}
|
||||
params.put(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.heartbeat.client;
|
||||
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Simple HTTP response representation.
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class SimpleHttpResponse {
|
||||
|
||||
private Charset charset = Charset.forName(SentinelConfig.charset());
|
||||
|
||||
private String statusLine;
|
||||
private int statusCode;
|
||||
private Map<String, String> headers;
|
||||
private byte[] body;
|
||||
|
||||
public SimpleHttpResponse(String statusLine, Map<String, String> headers) {
|
||||
this.statusLine = statusLine;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public SimpleHttpResponse(String statusLine, Map<String, String> headers, byte[] body) {
|
||||
this.statusLine = statusLine;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
private void parseCharset() {
|
||||
String contentType = getHeader("Content-Type");
|
||||
for (String str : contentType.split(" ")) {
|
||||
if (str.toLowerCase().startsWith("charset=")) {
|
||||
charset = Charset.forName(str.split("=")[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseCode() {
|
||||
this.statusCode = Integer.parseInt(statusLine.split(" ")[1]);
|
||||
}
|
||||
|
||||
public void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getStatusLine() {
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
public Integer getStatusCode() {
|
||||
if (statusCode == 0) {
|
||||
parseCode();
|
||||
}
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header of the key ignoring case.
|
||||
*
|
||||
* @param key header key
|
||||
* @return header value
|
||||
*/
|
||||
public String getHeader(String key) {
|
||||
if (headers == null) {
|
||||
return null;
|
||||
}
|
||||
String value = headers.get(key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(key)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getBodyAsString() {
|
||||
parseCharset();
|
||||
return new String(body, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(statusLine)
|
||||
.append("\r\n");
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
buf.append(entry.getKey()).append(": ").append(entry.getValue())
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
buf.append("\r\n");
|
||||
buf.append(getBodyAsString());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.heartbeat.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The parser provides functionality to parse raw bytes HTTP response to a {@link SimpleHttpResponse}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is a very NAIVE parser, {@code Content-Length} must be specified in the
|
||||
* HTTP response header, otherwise, the body will be dropped. All other body type such as
|
||||
* {@code Transfer-Encoding: chunked}, {@code Transfer-Encoding: deflate} are not supported.
|
||||
* </p>
|
||||
*
|
||||
* @author leyou
|
||||
*/
|
||||
public class SimpleHttpResponseParser {
|
||||
|
||||
private static final int MAX_BODY_SIZE = 1024 * 1024 * 4;
|
||||
private byte[] buf;
|
||||
|
||||
public SimpleHttpResponseParser(int maxSize) {
|
||||
if (maxSize < 0) {
|
||||
throw new IllegalArgumentException("maxSize must > 0");
|
||||
}
|
||||
this.buf = new byte[maxSize];
|
||||
}
|
||||
|
||||
public SimpleHttpResponseParser() {
|
||||
this(1024 * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse bytes from an input stream to a {@link SimpleHttpResponse}.
|
||||
*
|
||||
* @param in input stream
|
||||
* @return parsed HTTP response entity
|
||||
* @throws IOException when an IO error occurs
|
||||
*/
|
||||
public SimpleHttpResponse parse(InputStream in) throws IOException {
|
||||
int bg = 0;
|
||||
int len;
|
||||
String statusLine = null;
|
||||
Map<String, String> headers = new HashMap<String, String>();
|
||||
Charset charset = Charset.forName("utf-8");
|
||||
int contentLength = -1;
|
||||
SimpleHttpResponse response;
|
||||
while (true) {
|
||||
if (bg >= buf.length) {
|
||||
throw new IndexOutOfBoundsException("buf index out of range: " + bg + ", buf.length=" + buf.length);
|
||||
}
|
||||
if ((len = in.read(buf, bg, buf.length - bg)) > 0) {
|
||||
bg += len;
|
||||
len = bg;
|
||||
int idx;
|
||||
int parseBg = 0;
|
||||
while ((idx = indexOfCRLF(parseBg, len)) >= 0) {
|
||||
String line = new String(buf, parseBg, idx - parseBg, charset);
|
||||
parseBg = idx + 2;
|
||||
if (statusLine == null) {
|
||||
statusLine = line;
|
||||
} else {
|
||||
if (line.isEmpty()) {
|
||||
//When the `Content-Length` is absent, parse the rest of the bytes as body directly.
|
||||
//if (contentLength == -1) {
|
||||
// contentLength = MAX_BODY_SIZE;
|
||||
//}
|
||||
|
||||
// Parse HTTP body.
|
||||
// When the `Content-Length` is absent, drop the body, return directly.
|
||||
response = new SimpleHttpResponse(statusLine, headers);
|
||||
if (contentLength <= 0) {
|
||||
return response;
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
// `Content-Length` is not equal to exact length.
|
||||
if (contentLength < len - parseBg) {
|
||||
throw new IllegalStateException("Invalid content length: " + contentLength);
|
||||
}
|
||||
out.write(buf, parseBg, len - parseBg);
|
||||
if (out.size() > MAX_BODY_SIZE) {
|
||||
throw new IllegalStateException(
|
||||
"Request body is too big, limit size is " + MAX_BODY_SIZE);
|
||||
}
|
||||
int cap = Math.min(contentLength - out.size(), buf.length);
|
||||
while (cap > 0 && (len = in.read(buf, 0, cap)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
cap = Math.min(contentLength - out.size(), buf.length);
|
||||
}
|
||||
response.setBody(out.toByteArray());
|
||||
return response;
|
||||
} else if (!line.trim().isEmpty()) {
|
||||
// Parse HTTP header.
|
||||
int idx2 = line.indexOf(":");
|
||||
String key = line.substring(0, idx2).trim();
|
||||
String value = line.substring(idx2 + 1).trim();
|
||||
headers.put(key, value);
|
||||
if ("Content-Length".equalsIgnoreCase(key)) {
|
||||
contentLength = Integer.parseInt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move remaining bytes to the beginning.
|
||||
if (parseBg != 0) {
|
||||
System.arraycopy(buf, parseBg, buf, 0, len - parseBg);
|
||||
}
|
||||
bg = len - parseBg;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of CRLF separator.
|
||||
*
|
||||
* @param bg begin offset
|
||||
* @param ed end offset
|
||||
* @return the index, or {@code -1} if no CRLF is found
|
||||
*/
|
||||
private int indexOfCRLF(int bg, int ed) {
|
||||
if (ed - bg < 2) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = bg; i < ed - 1; i++) {
|
||||
if (buf[i] == '\r' && buf[i + 1] == '\n') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.alibaba.csp.sentinel.transport.heartbeat.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
|
||||
import com.alibaba.csp.sentinel.transport.ssl.SslFactory;
|
||||
|
||||
/**
|
||||
* @author Leo Li
|
||||
*/
|
||||
public class SocketFactory {
|
||||
|
||||
private static class SSLSocketFactoryInstance {
|
||||
private static final SSLSocketFactory SSL_SOCKET_FACTORY = SslFactory.getSslConnectionSocketFactory().getSocketFactory();
|
||||
}
|
||||
|
||||
public static Socket getSocket(Protocol protocol) throws IOException {
|
||||
return protocol == Protocol.HTTP ? new Socket() : SSLSocketFactoryInstance.SSL_SOCKET_FACTORY.createSocket();
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.command.SimpleHttpCommandCenter
|
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender
|
@@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user