当字符集为 UTF-8 时使用 OutputStream 发送特殊字符时出错

Error when sending special characters using OutputStream when the charset is UTF-8

我正在使用 HttpServer 创建一个简单的服务。当我使用不含特殊字符的字符串时,该服务正常工作。

public static void main (String arg []) throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0);
    server.createContext("/notification", new MyHandler());
    server.setExecutor(null); // creates a default executor
    server.start();
}

static class MyHandler implements HttpHandler {
    public void handle(HttpExchange t) throws IOException {
        String response;

        response = "with special characters éáã "; // it doesn't work
        response = "without special characters"; // it works!

        String encoding = "UTF-8";

        System.out.println(response);

        t.getResponseHeaders().set("Content-Type", "application/json; charset=" + encoding);

        t.sendResponseHeaders(200, response.length());
        byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
        OutputStream os = t.getResponseBody();
        os.write(bytes);
        os.flush();

        os.close();
    }
}

当我的 UTF-8 字符串有特殊字符时,它 return 这个错误:

java.io.IOException: 写入流的字节太多 在 sun.net.httpserver.FixedLengthOutputStream.write(FixedLengthOutputStream.java:76) 在 java.io.FilterOutputStream.write(FilterOutputStream.java:97) 在 sun.net.httpserver.PlaceholderOutputStream.write(ExchangeImpl.java:439) 在内部服务器$MyHandler.handle(InternalServer.java:86) 在 com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) 在 sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:83) 在 com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:82) 在 sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:675) 在 com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) 在 sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:647) 在 sun.net.httpserver.ServerImpl$DefaultExecutor.execute(ServerImpl.java:158) 在 sun.net.httpserver.ServerImpl$Dispatcher.handle(ServerImpl.java:431) 在 sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:396) 在 java.lang.Thread.run(Thread.java:748)

问题来了。

 t.sendResponseHeaders(200, response.length());

sendResponseHeaders 的第二个参数必须是您要发送的内容的确切大小字节。但是你传递的是字符串的长度 in characters.

在 UTF-8 中,任何大于 U+0080 的字符都将被编码为 2 个或更多字节。您的第二个示例字符串包含大于 U+0080 的字符,因此当它以 UTF-8 编码时,字符数和字节数不同。您将在响应中设置不正确的内容长度 header

看起来 HttpExchange 提供的输出流正在检查您发送的字节数没有超过您在响应 header 中设置的字节数。 (这将违反 HTTP 协议。)

解决方案:

 ...
 byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
 t.sendResponseHeaders(200, bytes.length);
 ...

也可以传递0作为内容长度。这将导致使用 chunked transfer encoding.

发送 body