首先,一般普通导包:
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
其次,静态导包:
import static java.net.URLDecoder.decode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.stream;
import static java.util.List.of;
最后,2段不同功效的function:
static Map<String, Set<String>> extractQueryParams(String queryString, boolean isOrdered) {
final Map<String, Set<String>> paramsMap;
if ( isOrdered ) {
//这是用于有序串行提取query字符串参数的,其中的LinkedHashMap和LinkedHashSet内的元素皆为按序存储
paramsMap = new LinkedHashMap<>(32);
stream(queryString.split("&"))
.map(param -> param.split("="))
.forEachOrdered(pair -> paramsMap.computeIfAbsent(decode(pair[0], UTF_8),
k -> new LinkedHashSet<>(8)).add((pair.length > 1) ? decode(pair[1], UTF_8) : "")
);
}else {
//这是用于无序并行提取query字符串参数的,其中ConcurrentHashMap并发控制使用 synchronized 和 CAS 来操作,精细程度更高(Hashtable使用的是全表锁,效率更低)
paramsMap = new ConcurrentHashMap<>(32);
of(queryString.split("&"))
.parallelStream()
.map(param -> param.split("="))
.forEach(pair -> paramsMap.computeIfAbsent(decode(pair[0], UTF_8),
k -> new ConcurrentSkipListSet<>()).add((pair.length > 1) ? decode(pair[1], UTF_8) : "")
);
}
return paramsMap;
}
以上代码皆是纯原生Java实现,没有使用任何组件库!
最后再提一嘴:
Java想要直接提取请求行中的Query字符串其实相当的简单:只需要在com.sun.net.httpserver.HttpHandler方法块里面,通过传入的HttpExchange参数,调用其自带的.getRequestURI(),就可以获取相应的 java.net.URI 实例对象。然后当然是直接调用URI对象的 .getQuery() 方法就可以返回Query的 String 了。
例如:
final var server = HttpServer.create(new InetSocketAddress("localhost", 8001), 0, "/test", exchange -> {
switch (exchange.getRequestMethod()) {
case "GET" -> {
final var uri = exchange.getRequestURI();
var stringBuilder = new StringBuilder(256);
stringBuilder.append("GET:").append(System.lineSeparator())
.append("\tPath:").append(uri.getPath())
.append(",\tPort:").append(uri.getPort())
.append(",\tHost:").append(uri.getHost())
.append(",\tQuery:").append(uri.getQuery()).append(System.lineSeparator())
.append("\tUserInfo:").append(uri.getUserInfo())
.append(",\tFragment:").append(uri.getFragment())
.append(",\tScheme:").append(uri.getScheme())
.append(",\tAuthority:").append(uri.getAuthority()).append(System.lineSeparator());
if ( uri.getQuery() != null ) {
extractQueryParams(uri.getQuery()).forEach(
(k, v) -> stringBuilder.append('\t').append(k).append(':').append(System.lineSeparator()).append("\t\t").append(v).append(System.lineSeparator())
);
}
responseInfoOut(stringBuilder, exchange);
try (final var outs = new BufferedOutputStream(exchange.getResponseBody())) {
outs.write(response.getBytes(UTF_8));
}
println(stringBuilder.toString());
}
case "POST" -> {
var stringBuilder = new StringBuilder(1024);
final var uri = exchange.getRequestURI();
stringBuilder.append("POST:").append(System.lineSeparator())
.append("\tPath:").append(uri.getPath())
.append(",\tPort:").append(uri.getPort())
.append(",\tHost:").append(uri.getHost())
.append(",\tQuery:").append(uri.getQuery()).append(System.lineSeparator())
.append("\tUserInfo:").append(uri.getUserInfo())
.append(",\tFragment:").append(uri.getFragment())
.append(",\tScheme:").append(uri.getScheme())
.append(",\tAuthority:").append(uri.getAuthority()).append(System.lineSeparator());
if ( uri.getQuery() != null ) {
extractQueryParams(uri.getQuery()).forEach(
(k, v) -> stringBuilder.append('\t').append(k).append(':').append(System.lineSeparator()).append("\t\t").append(v).append(System.lineSeparator())
);
}
switch (exchange.getRequestHeaders().get(CONTENT_TYPE).get(0)) {
case "application/x-www-form-urlencoded" -> {
try (final var ins = new BufferedInputStream(exchange.getRequestBody())
) {
extractQueryParams(new String(ins.readAllBytes(), UTF_8)).forEach(
(k, v) -> stringBuilder.append('\t').append(k).append(':').append(System.lineSeparator()).append("\t\t").append(v).append(System.lineSeparator())
);
}
}
case "application/json" -> {
try (final var ins = new BufferedInputStream(exchange.getRequestBody())
) {
stringBuilder.append(new String(ins.readAllBytes(), UTF_8));
}
}
default -> stringBuilder.append(exchange.getRequestHeaders().get(CONTENT_TYPE));
}
responseInfoOut(stringBuilder, exchange);
try (final var outs = new BufferedOutputStream(exchange.getResponseBody())) {
outs.write(((response + "\n").repeat(8)).getBytes(UTF_8));
}
println(stringBuilder.toString());
}
case "PUT" -> {
var stringBuilder = new StringBuilder(256)
.append("PUT:").append(System.lineSeparator())
.append('\t').append(CONTEXT_ATTRIBUTES).append(System.lineSeparator());
exchange.getHttpContext().getAttributes().forEach(
(k, v) -> stringBuilder.append("\t\t").append(k).append(':').append(v).append(System.lineSeparator())
);
stringBuilder.append(System.lineSeparator());
exchange.sendResponseHeaders(200, 0);
try (final var outs = new BufferedOutputStream(exchange.getResponseBody())) {
outs.write((response + response).getBytes(UTF_8));
}
println(stringBuilder.toString());
}
case "DELETE" -> {
var stringBuilder = new StringBuilder(128)
.append("DELETE:").append(System.lineSeparator())
.append('\t').append(CONTEXT_ATTRIBUTES).append(System.lineSeparator());
exchange.getHttpContext().getAttributes().forEach(
(k, v) -> stringBuilder.append("\t\t").append(k).append(':').append(v).append(System.lineSeparator())
);
stringBuilder.append(System.lineSeparator());
exchange.sendResponseHeaders(200, 0);
try (final var outs = new BufferedOutputStream(exchange.getResponseBody())) {
outs.write("DELETE".getBytes(UTF_8));
}
println(stringBuilder.toString());
}
default -> {
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().close();
}
}
}, Filter.adaptRequest("Add Foo header.", (Request r) -> r.with("Foo", of("Bar"))), new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
exchange.getResponseHeaders().add("Accept-Charset", "UTF-8");
exchange.getResponseHeaders().add(CONTENT_TYPE, "text/txt;Charset=UTF-8");
println("Filter 起作用啦!!!");
chain.doFilter(exchange);
}
@Override
public String description() {
return "Add response header";
}
});
关于 omputeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) 方法,这里官方的解释是:
If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.
If the mapping function returns null, no mapping is recorded. If the mapping function itself throws an (unchecked) exception, the exception is rethrown, and no mapping is recorded. The most common usage is to construct a new object serving as an initial mapped value or memoized result, as in:
map.computeIfAbsent(key, k -> new Value(f(k)));
Or to implement a multi-value map, Map<K,Collection>, supporting multiple values per key:
map.computeIfAbsent(key, k -> new HashSet()).add(v);
The mapping function should not modify this map during computation.
This method will, on a best-effort basis, throw a ConcurrentModificationException if it is detected that the mapping function modifies this map during computation.
抛出:
ConcurrentModificationException – if it is detected that the mapping function modified this map
翻译:
如果指定的键尚未与值关联(或映射到null ),则尝试使用给定的映射函数计算其值并将其输入到此映射中,除非为null 。
如果映射函数返回null ,则不记录映射。如果映射函数本身抛出(未经检查的)异常,则重新抛出异常,并且不记录映射。最常见的用法是构造一个新对象作为初始映射值或记忆结果,如下所示:
map.computeIfAbsent(key, k -> new Value(f(k)));
或者实现一个多值映射, Map < K , Collection < V >> ,每个键支持多个值:
map.computeIfAbsent(key, k -> new HashSet()).add(v);
映射函数不应在计算期间修改此映射。
如果检测到映射函数在计算期间修改了此映射,则此方法将尽最大努力抛出ConcurrentModificationException 。
问:
接口Map中的computeIfAbsent
参形:
key – 与指定值关联的键 mappingFunction – 计算值的映射函数
返回值:
与指定键关联的当前(现有或计算的)值,如果计算的值为 null,则为 null
抛出:
ConcurrentModificationException – 如果检测到映射函数修改了此映射