mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-02 12:10:14 +00:00
8268464: Remove dependancy of TestHttpsServer, HttpTransaction, HttpCallback from open/test/jdk/sun/net/www/protocol/https/ tests
Reviewed-by: dfuchs, michaelm
This commit is contained in:
parent
f3ba2690c5
commit
7621fa37ef
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class provides a partial implementation of the HttpCallback
|
||||
* interface. Use this class if you want to use the requestURI as a means
|
||||
* of tracking multiple invocations of a request (on the server).
|
||||
* In this case, you implement the modified request() method, which includes
|
||||
* an integer count parameter. This parameter indicates the number of times
|
||||
* (starting at zero) the request URI has been received.
|
||||
*/
|
||||
|
||||
public abstract class AbstractCallback implements HttpCallback {
|
||||
|
||||
Map requests;
|
||||
|
||||
static class Request {
|
||||
URI uri;
|
||||
int count;
|
||||
|
||||
Request (URI u) {
|
||||
uri = u;
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AbstractCallback () {
|
||||
requests = Collections.synchronizedMap (new HashMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the given request and generate an appropriate response.
|
||||
* @param msg the transaction containing the request from the
|
||||
* client and used to send the response
|
||||
*/
|
||||
public void request (HttpTransaction msg) {
|
||||
URI uri = msg.getRequestURI();
|
||||
Request req = (Request) requests.get (uri);
|
||||
if (req == null) {
|
||||
req = new Request (uri);
|
||||
requests.put (uri, req);
|
||||
}
|
||||
request (msg, req.count++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as HttpCallback interface except that the integer n
|
||||
* is provided to indicate sequencing of repeated requests using
|
||||
* the same request URI. n starts at zero and is incremented
|
||||
* for each successive call.
|
||||
*
|
||||
* @param msg the transaction containing the request from the
|
||||
* client and used to send the response
|
||||
* @param n value is 0 at first call, and is incremented by 1 for
|
||||
* each subsequent call using the same request URI.
|
||||
*/
|
||||
abstract public void request (HttpTransaction msg, int n);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,8 +24,7 @@
|
||||
/**
|
||||
* @test
|
||||
* @bug 5026745
|
||||
* @modules java.base/sun.net.www
|
||||
* @build TestHttpsServer HttpCallback
|
||||
* @library /test/lib
|
||||
* @run main/othervm ChunkedOutputStream
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true ChunkedOutputStream
|
||||
*
|
||||
@ -34,12 +33,35 @@
|
||||
* @summary Cannot flush output stream when writing to an HttpUrlConnection
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import javax.net.ssl.*;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpRetryException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.KeyStore;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ChunkedOutputStream implements HttpCallback {
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpsConfigurator;
|
||||
import com.sun.net.httpserver.HttpsServer;
|
||||
|
||||
public class ChunkedOutputStream implements HttpHandler {
|
||||
/*
|
||||
* Where do we find the keystores for ssl?
|
||||
*/
|
||||
@ -57,98 +79,100 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
static final String str2 = "Helloworld1234567890abcdefghijklmnopqrstuvwxyz"+
|
||||
"1234567890";
|
||||
|
||||
public void request(HttpTransaction req) {
|
||||
try {
|
||||
// this is needed (count++ doesn't work), 'cause we
|
||||
// are doing concurrent tests
|
||||
String path = req.getRequestURI().getPath();
|
||||
if (path.equals("/d0")) {
|
||||
count = 0;
|
||||
} else if (path.equals("/d01")) {
|
||||
count = 1;
|
||||
} else if (path.equals("/d3")) {
|
||||
count = 2;
|
||||
} else if (path.equals("/d4") || path.equals("/d5")) {
|
||||
count = 3;
|
||||
} else if (path.equals("/d6")) {
|
||||
count = 3;
|
||||
} else if (path.equals("/d7")) {
|
||||
count = 4;
|
||||
} else if (path.equals("/d8")) {
|
||||
count = 5;
|
||||
}
|
||||
private static String getAuthority() {
|
||||
InetAddress address = server.getAddress().getAddress();
|
||||
String hostaddr = address.getHostAddress();
|
||||
if (address.isAnyLocalAddress()) hostaddr = "localhost";
|
||||
if (hostaddr.indexOf(':') > -1) hostaddr = "[" + hostaddr + "]";
|
||||
return hostaddr + ":" + server.getAddress().getPort();
|
||||
}
|
||||
|
||||
switch (count) {
|
||||
public void handle(HttpExchange req) throws IOException {
|
||||
// this is needed (count++ doesn't work), 'cause we
|
||||
// are doing concurrent tests
|
||||
System.out.println("Request Received");
|
||||
String path = req.getRequestURI().getPath();
|
||||
if (path.equals("/d0")) {
|
||||
count = 0;
|
||||
} else if (path.equals("/d01")) {
|
||||
count = 1;
|
||||
} else if (path.equals("/d3")) {
|
||||
count = 2;
|
||||
} else if (path.equals("/d4") || path.equals("/d5")) {
|
||||
count = 3;
|
||||
} else if (path.equals("/d6")) {
|
||||
count = 3;
|
||||
} else if (path.equals("/d7")) {
|
||||
count = 4;
|
||||
} else if (path.equals("/d8")) {
|
||||
count = 5;
|
||||
}
|
||||
|
||||
switch (count) {
|
||||
case 0: /* test1 -- keeps conn alive */
|
||||
case 1: /* test2 -- closes conn */
|
||||
String reqbody = req.getRequestEntityBody();
|
||||
|
||||
String reqbody = "";
|
||||
try(InputStream inputStream = req.getRequestBody()) {
|
||||
reqbody = new String(inputStream.readAllBytes(), Charset.forName("ISO8859_1"));
|
||||
}
|
||||
if (!reqbody.equals(str1)) {
|
||||
req.sendResponse(500, "Internal server error");
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(500, -1);
|
||||
break;
|
||||
}
|
||||
String chunk = req.getRequestHeader("Transfer-encoding");
|
||||
String chunk = req.getRequestHeaders().getFirst("Transfer-encoding");
|
||||
if (!"chunked".equals(chunk)) {
|
||||
req.sendResponse(501, "Internal server error");
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(501, -1);
|
||||
break;
|
||||
}
|
||||
req.setResponseEntityBody(reqbody);
|
||||
if (count == 1) {
|
||||
req.setResponseHeader("Connection", "close");
|
||||
req.getResponseHeaders().set("Connection", "close");
|
||||
}
|
||||
req.sendResponse(200, "OK");
|
||||
if (count == 1) {
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(200, 0);
|
||||
try (OutputStream os = req.getResponseBody()) {
|
||||
os.write(reqbody.getBytes(Charset.forName("ISO8859_1")));
|
||||
}
|
||||
break;
|
||||
case 2: /* test 3 */
|
||||
reqbody = req.getRequestEntityBody();
|
||||
reqbody = new String(req.getRequestBody().readAllBytes(), Charset.forName("ISO8859_1"));
|
||||
if (!reqbody.equals(str2)) {
|
||||
req.sendResponse(500, "Internal server error");
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(500, -1);
|
||||
break;
|
||||
}
|
||||
int clen = Integer.parseInt (
|
||||
req.getRequestHeader("Content-length"));
|
||||
int clen = Integer.parseInt (req.getRequestHeaders().getFirst("Content-length"));
|
||||
if (clen != str2.length()) {
|
||||
req.sendResponse(501, "Internal server error");
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(501, -1);
|
||||
break;
|
||||
}
|
||||
req.getResponseHeaders().set("Connection", "close");
|
||||
req.sendResponseHeaders(200, 0);
|
||||
try (OutputStream os = req.getResponseBody()) {
|
||||
os.write(reqbody.getBytes(Charset.forName("ISO8859_1")));
|
||||
}
|
||||
req.setResponseEntityBody (reqbody);
|
||||
req.setResponseHeader("Connection", "close");
|
||||
req.sendResponse(200, "OK");
|
||||
req.orderlyClose();
|
||||
break;
|
||||
case 3: /* test 6 */
|
||||
req.setResponseHeader("Location", "https://foo.bar/");
|
||||
req.setResponseHeader("Connection", "close");
|
||||
req.sendResponse(307, "Temporary Redirect");
|
||||
req.orderlyClose();
|
||||
if (path.equals("/d6")) {
|
||||
reqbody = new String(req.getRequestBody().readAllBytes(), Charset.forName("ISO8859_1"));
|
||||
}
|
||||
req.getResponseHeaders().set("Location", "https://foo.bar/");
|
||||
req.getResponseHeaders().set("Connection", "close");
|
||||
req.sendResponseHeaders(307, -1);
|
||||
break;
|
||||
case 4: /* test 7 */
|
||||
case 5: /* test 8 */
|
||||
reqbody = req.getRequestEntityBody();
|
||||
reqbody = new String(req.getRequestBody().readAllBytes(), Charset.forName("ISO8859_1"));
|
||||
if (reqbody != null && !"".equals(reqbody)) {
|
||||
req.sendResponse(501, "Internal server error");
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(501, -1);
|
||||
break;
|
||||
}
|
||||
req.setResponseHeader("Connection", "close");
|
||||
req.sendResponse(200, "OK");
|
||||
req.orderlyClose();
|
||||
req.getResponseHeaders().set("Connection", "close");
|
||||
req.sendResponseHeaders(200, -1);
|
||||
break;
|
||||
default:
|
||||
req.sendResponse(404, "Not Found");
|
||||
req.orderlyClose();
|
||||
req.sendResponseHeaders(404, -1);
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean dropPlainTextConnections() {
|
||||
System.out.println("Unrecognized SSL message, plaintext connection?");
|
||||
System.out.println("TestHttpsServer receveived rogue connection: ignoring it.");
|
||||
rogueCount.incrementAndGet();
|
||||
return true;
|
||||
req.close();
|
||||
}
|
||||
|
||||
static void readAndCompare(InputStream is, String cmp) throws IOException {
|
||||
@ -179,9 +203,6 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
} catch (SocketException x) {
|
||||
// we expect that the server will drop the connection and
|
||||
// close the accepted socket, so we should get a SocketException
|
||||
// on the client side, and confirm that this::dropPlainTextConnections
|
||||
// has ben called.
|
||||
if (rogueCount.get() == rogue) throw x;
|
||||
System.out.println("Got expected exception: " + x);
|
||||
}
|
||||
}
|
||||
@ -196,7 +217,7 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
urlc.setDoOutput(true);
|
||||
urlc.setRequestMethod("POST");
|
||||
OutputStream os = urlc.getOutputStream();
|
||||
os.write(str1.getBytes());
|
||||
os.write(str1.getBytes(Charset.forName("ISO8859_1")));
|
||||
os.close();
|
||||
InputStream is = urlc.getInputStream();
|
||||
readAndCompare(is, str1);
|
||||
@ -213,7 +234,7 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
urlc.setDoOutput(true);
|
||||
urlc.setRequestMethod("POST");
|
||||
OutputStream os = urlc.getOutputStream();
|
||||
os.write (str2.getBytes());
|
||||
os.write (str2.getBytes(Charset.forName("ISO8859_1")));
|
||||
os.close();
|
||||
InputStream is = urlc.getInputStream();
|
||||
readAndCompare(is, str2);
|
||||
@ -230,7 +251,7 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
urlc.setDoOutput(true);
|
||||
urlc.setRequestMethod("POST");
|
||||
OutputStream os = urlc.getOutputStream();
|
||||
os.write(str2.getBytes());
|
||||
os.write(str2.getBytes(Charset.forName("ISO8859_1")));
|
||||
try {
|
||||
os.close();
|
||||
throw new Exception("should have thrown IOException");
|
||||
@ -248,7 +269,7 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
urlc.setRequestMethod("POST");
|
||||
OutputStream os = urlc.getOutputStream();
|
||||
try {
|
||||
os.write(str2.getBytes());
|
||||
os.write(str2.getBytes(Charset.forName("ISO8859_1")));
|
||||
throw new Exception("should have thrown IOException");
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
@ -263,7 +284,7 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
urlc.setDoOutput(true);
|
||||
urlc.setRequestMethod("POST");
|
||||
OutputStream os = urlc.getOutputStream();
|
||||
os.write(str1.getBytes());
|
||||
os.write(str1.getBytes(Charset.forName("ISO8859_1")));
|
||||
os.close();
|
||||
try {
|
||||
InputStream is = urlc.getInputStream();
|
||||
@ -310,9 +331,10 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
}
|
||||
}
|
||||
|
||||
static TestHttpsServer server;
|
||||
static HttpsServer server;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream();
|
||||
// setup properties to do ssl
|
||||
String keyFilename =
|
||||
System.getProperty("test.src", "./") + "/" + pathToStores +
|
||||
@ -333,26 +355,48 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(new NameVerifier());
|
||||
|
||||
try {
|
||||
server = new TestHttpsServer(
|
||||
new ChunkedOutputStream(), 1, 10, loopback, 0);
|
||||
System.out.println("Server started: listening on: " + server.getAuthority());
|
||||
testPlainText(server.getAuthority());
|
||||
// create and initialize a SSLContext
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
KeyStore ts = KeyStore.getInstance("JKS");
|
||||
char[] passphrase = "passphrase".toCharArray();
|
||||
|
||||
ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), passphrase);
|
||||
ts.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), passphrase);
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
kmf.init(ks, passphrase);
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
||||
tmf.init(ts);
|
||||
|
||||
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
||||
|
||||
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
|
||||
server = HttpsServer.create(new InetSocketAddress(loopback, 0), 10);
|
||||
server.setHttpsConfigurator(new HttpsConfigurator(sslCtx));
|
||||
server.createContext("/", chunkedOutputStream);
|
||||
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||
server.start();
|
||||
|
||||
System.out.println("Server started: listening on: " + getAuthority());
|
||||
testPlainText(getAuthority());
|
||||
// the test server doesn't support keep-alive yet
|
||||
// test1("http://" + server.getAuthority() + "/d0");
|
||||
test1("https://" + server.getAuthority() + "/d01");
|
||||
test3("https://" + server.getAuthority() + "/d3");
|
||||
test4("https://" + server.getAuthority() + "/d4");
|
||||
test5("https://" + server.getAuthority() + "/d5");
|
||||
test6("https://" + server.getAuthority() + "/d6");
|
||||
test7("https://" + server.getAuthority() + "/d7");
|
||||
test8("https://" + server.getAuthority() + "/d8");
|
||||
test1("https://" + getAuthority() + "/d01");
|
||||
test3("https://" + getAuthority() + "/d3");
|
||||
test4("https://" + getAuthority() + "/d4");
|
||||
test5("https://" + getAuthority() + "/d5");
|
||||
test6("https://" + getAuthority() + "/d6");
|
||||
test7("https://" + getAuthority() + "/d7");
|
||||
test8("https://" + getAuthority() + "/d8");
|
||||
} catch (Exception e) {
|
||||
if (server != null) {
|
||||
server.terminate();
|
||||
server.stop(1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
server.terminate();
|
||||
server.stop(1);
|
||||
} finally {
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(reservedHV);
|
||||
}
|
||||
@ -365,7 +409,7 @@ public class ChunkedOutputStream implements HttpCallback {
|
||||
}
|
||||
|
||||
public static void except(String s) {
|
||||
server.terminate();
|
||||
server.stop(1);
|
||||
throw new RuntimeException(s);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This interface is implemented by classes that wish to handle incoming HTTP
|
||||
* requests and generate responses. This could be a general purpose HTTP server
|
||||
* or a test case that expects specific requests from a client.
|
||||
* <p>
|
||||
* The incoming request fields can be examined via the {@link HttpTransaction}
|
||||
* object, and a response can also be generated and sent via the request object.
|
||||
*/
|
||||
public interface HttpCallback {
|
||||
/**
|
||||
* handle the given request and generate an appropriate response.
|
||||
* @param msg the transaction containing the request from the
|
||||
* client and used to send the response
|
||||
*/
|
||||
void request (HttpTransaction msg);
|
||||
|
||||
/**
|
||||
* Tells whether the server should simply close the
|
||||
* connection and ignore the request when the first
|
||||
* byte received by the server looks like a plain
|
||||
* text connection.
|
||||
* @return true if the request should be ignored.
|
||||
**/
|
||||
default boolean dropPlainTextConnections() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1,330 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.net.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
/**
|
||||
* This class encapsulates a HTTP request received and a response to be
|
||||
* generated in one transaction. It provides methods for examaining the
|
||||
* request from the client, and for building and sending a reply.
|
||||
*/
|
||||
|
||||
public class HttpTransaction {
|
||||
|
||||
String command;
|
||||
URI requesturi;
|
||||
TestHttpsServer.ServerWorker server;
|
||||
MessageHeader reqheaders, reqtrailers;
|
||||
String reqbody;
|
||||
byte[] rspbody;
|
||||
MessageHeader rspheaders, rsptrailers;
|
||||
SocketChannel ch;
|
||||
int rspbodylen;
|
||||
boolean rspchunked;
|
||||
|
||||
HttpTransaction (TestHttpsServer.ServerWorker server, String command,
|
||||
URI requesturi, MessageHeader headers,
|
||||
String body, MessageHeader trailers, SocketChannel ch) {
|
||||
this.command = command;
|
||||
this.requesturi = requesturi;
|
||||
this.reqheaders = headers;
|
||||
this.reqbody = body;
|
||||
this.reqtrailers = trailers;
|
||||
this.ch = ch;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a request header whose name is specified by the
|
||||
* String argument.
|
||||
*
|
||||
* @param key the name of the request header
|
||||
* @return the value of the header or null if it does not exist
|
||||
*/
|
||||
public String getRequestHeader (String key) {
|
||||
return reqheaders.findValue (key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a response header whose name is specified by the
|
||||
* String argument.
|
||||
*
|
||||
* @param key the name of the response header
|
||||
* @return the value of the header or null if it does not exist
|
||||
*/
|
||||
public String getResponseHeader (String key) {
|
||||
return rspheaders.findValue (key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request URI
|
||||
*
|
||||
* @return the request URI
|
||||
*/
|
||||
public URI getRequestURI () {
|
||||
return requesturi;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append ("Request from: ").append (ch.toString()).append("\r\n");
|
||||
buf.append ("Command: ").append (command).append("\r\n");
|
||||
buf.append ("Request URI: ").append (requesturi).append("\r\n");
|
||||
buf.append ("Headers: ").append("\r\n");
|
||||
buf.append (reqheaders.toString()).append("\r\n");
|
||||
buf.append ("Body: ").append (reqbody).append("\r\n");
|
||||
buf.append ("---------Response-------\r\n");
|
||||
buf.append ("Headers: ").append("\r\n");
|
||||
if (rspheaders != null) {
|
||||
buf.append (rspheaders.toString()).append("\r\n");
|
||||
}
|
||||
String rbody = rspbody == null? "": new String (rspbody);
|
||||
buf.append ("Body: ").append (rbody).append("\r\n");
|
||||
return new String (buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a request trailer whose name is specified by
|
||||
* the String argument.
|
||||
*
|
||||
* @param key the name of the request trailer
|
||||
* @return the value of the trailer or null if it does not exist
|
||||
*/
|
||||
public String getRequestTrailer (String key) {
|
||||
return reqtrailers.findValue (key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a response header to the response. Multiple calls with the same
|
||||
* key value result in multiple header lines with the same key identifier
|
||||
* @param key the name of the request header to add
|
||||
* @param val the value of the header
|
||||
*/
|
||||
public void addResponseHeader (String key, String val) {
|
||||
if (rspheaders == null)
|
||||
rspheaders = new MessageHeader ();
|
||||
rspheaders.add (key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a response header. Searches for first header with named key
|
||||
* and replaces its value with val
|
||||
* @param key the name of the request header to add
|
||||
* @param val the value of the header
|
||||
*/
|
||||
public void setResponseHeader (String key, String val) {
|
||||
if (rspheaders == null)
|
||||
rspheaders = new MessageHeader ();
|
||||
rspheaders.set (key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a response trailer to the response. Multiple calls with the same
|
||||
* key value result in multiple trailer lines with the same key identifier
|
||||
* @param key the name of the request trailer to add
|
||||
* @param val the value of the trailer
|
||||
*/
|
||||
public void addResponseTrailer (String key, String val) {
|
||||
if (rsptrailers == null)
|
||||
rsptrailers = new MessageHeader ();
|
||||
rsptrailers.add (key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request method
|
||||
*
|
||||
* @return the request method
|
||||
*/
|
||||
public String getRequestMethod (){
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an orderly close of the TCP connection associated with this
|
||||
* request. This method guarantees that any response already sent will
|
||||
* not be reset (by this end). The implementation does a shutdownOutput()
|
||||
* of the TCP connection and for a period of time consumes and discards
|
||||
* data received on the reading side of the connection. This happens
|
||||
* in the background. After the period has expired the
|
||||
* connection is completely closed.
|
||||
*/
|
||||
|
||||
public void orderlyClose () {
|
||||
try {
|
||||
server.orderlyCloseChannel (ch);
|
||||
} catch (IOException e) {
|
||||
System.out.println (e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do an immediate abortive close of the TCP connection associated
|
||||
* with this request.
|
||||
*/
|
||||
public void abortiveClose () {
|
||||
try {
|
||||
server.abortiveCloseChannel(ch);
|
||||
} catch (IOException e) {
|
||||
System.out.println (e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SocketChannel associated with this request
|
||||
*
|
||||
* @return the socket channel
|
||||
*/
|
||||
public SocketChannel channel() {
|
||||
return ch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request entity body associated with this request
|
||||
* as a single String.
|
||||
*
|
||||
* @return the entity body in one String
|
||||
*/
|
||||
public String getRequestEntityBody (){
|
||||
return reqbody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entity response body with the given string
|
||||
* The content length is set to the length of the string
|
||||
* @param body the string to send in the response
|
||||
*/
|
||||
public void setResponseEntityBody (String body){
|
||||
rspbody = body.getBytes();
|
||||
rspbodylen = body.length();
|
||||
rspchunked = false;
|
||||
addResponseHeader ("Content-length", Integer.toString (rspbodylen));
|
||||
}
|
||||
/**
|
||||
* Set the entity response body with the given byte[]
|
||||
* The content length is set to the gven length
|
||||
* @param body the string to send in the response
|
||||
*/
|
||||
public void setResponseEntityBody (byte[] body, int len){
|
||||
rspbody = body;
|
||||
rspbodylen = len;
|
||||
rspchunked = false;
|
||||
addResponseHeader ("Content-length", Integer.toString (rspbodylen));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the entity response body by reading the given inputstream
|
||||
*
|
||||
* @param is the inputstream from which to read the body
|
||||
*/
|
||||
public void setResponseEntityBody (InputStream is) throws IOException {
|
||||
byte[] buf = new byte [2048];
|
||||
byte[] total = new byte [2048];
|
||||
int total_len = 2048;
|
||||
int c, len=0;
|
||||
while ((c=is.read (buf)) != -1) {
|
||||
if (len+c > total_len) {
|
||||
byte[] total1 = new byte [total_len * 2];
|
||||
System.arraycopy (total, 0, total1, 0, len);
|
||||
total = total1;
|
||||
total_len = total_len * 2;
|
||||
}
|
||||
System.arraycopy (buf, 0, total, len, c);
|
||||
len += c;
|
||||
}
|
||||
setResponseEntityBody (total, len);
|
||||
}
|
||||
|
||||
/* chunked */
|
||||
|
||||
/**
|
||||
* Set the entity response body with the given array of strings
|
||||
* The content encoding is set to "chunked" and each array element
|
||||
* is sent as one chunk.
|
||||
* @param body the array of string chunks to send in the response
|
||||
*/
|
||||
public void setResponseEntityBody (String[] body) {
|
||||
StringBuffer buf = new StringBuffer ();
|
||||
int len = 0;
|
||||
for (int i=0; i<body.length; i++) {
|
||||
String chunklen = Integer.toHexString (body[i].length());
|
||||
len += body[i].length();
|
||||
buf.append (chunklen).append ("\r\n");
|
||||
buf.append (body[i]).append ("\r\n");
|
||||
}
|
||||
buf.append ("0\r\n");
|
||||
rspbody = new String (buf).getBytes();
|
||||
rspbodylen = rspbody.length;
|
||||
rspchunked = true;
|
||||
addResponseHeader ("Transfer-encoding", "chunked");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the response with the current set of response parameters
|
||||
* but using the response code and string tag line as specified
|
||||
* @param rCode the response code to send
|
||||
* @param rTag the response string to send with the response code
|
||||
*/
|
||||
public void sendResponse (int rCode, String rTag) throws IOException {
|
||||
OutputStream os = new TestHttpsServer.NioOutputStream(channel(), server.getSSLEngine(), server.outNetBB(), server.outAppBB());
|
||||
PrintStream ps = new PrintStream (os);
|
||||
ps.print ("HTTP/1.1 " + rCode + " " + rTag + "\r\n");
|
||||
if (rspheaders != null) {
|
||||
rspheaders.print (ps);
|
||||
} else {
|
||||
ps.print ("\r\n");
|
||||
}
|
||||
ps.flush ();
|
||||
if (rspbody != null) {
|
||||
os.write (rspbody, 0, rspbodylen);
|
||||
os.flush();
|
||||
}
|
||||
if (rsptrailers != null) {
|
||||
rsptrailers.print (ps);
|
||||
} else if (rspchunked) {
|
||||
ps.print ("\r\n");
|
||||
}
|
||||
ps.flush();
|
||||
}
|
||||
|
||||
/* sends one byte less than intended */
|
||||
|
||||
public void sendPartialResponse (int rCode, String rTag)throws IOException {
|
||||
OutputStream os = new TestHttpsServer.NioOutputStream(channel(), server.getSSLEngine(), server.outNetBB(), server.outAppBB());
|
||||
PrintStream ps = new PrintStream (os);
|
||||
ps.print ("HTTP/1.1 " + rCode + " " + rTag + "\r\n");
|
||||
ps.flush();
|
||||
if (rspbody != null) {
|
||||
os.write (rspbody, 0, rspbodylen-1);
|
||||
os.flush();
|
||||
}
|
||||
if (rsptrailers != null) {
|
||||
rsptrailers.print (ps);
|
||||
}
|
||||
ps.flush();
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -34,22 +34,39 @@
|
||||
* @library .. /test/lib
|
||||
* @build jdk.test.lib.NetworkConfiguration
|
||||
* jdk.test.lib.Platform
|
||||
* HttpCallback TestHttpsServer ClosedChannelList
|
||||
* HttpTransaction TunnelProxy
|
||||
* ClosedChannelList
|
||||
* TunnelProxy
|
||||
* @key intermittent
|
||||
* @run main/othervm B6216082
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import javax.net.ssl.*;
|
||||
import java.util.*;
|
||||
import java.io.FileInputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpsConfigurator;
|
||||
import com.sun.net.httpserver.HttpsServer;
|
||||
import jdk.test.lib.NetworkConfiguration;
|
||||
|
||||
public class B6216082 {
|
||||
static SimpleHttpTransaction httpTrans;
|
||||
static TestHttpsServer server;
|
||||
static HttpsServer server;
|
||||
static TunnelProxy proxy;
|
||||
|
||||
// it seems there's no proxy ever if a url points to 'localhost',
|
||||
@ -66,25 +83,17 @@ public class B6216082 {
|
||||
if (!setupEnv()) {
|
||||
return;
|
||||
}
|
||||
|
||||
startHttpServer();
|
||||
|
||||
// https.proxyPort can only be set after the TunnelProxy has been
|
||||
// created as it will use an ephemeral port.
|
||||
System.setProperty("https.proxyPort",
|
||||
(new Integer(proxy.getLocalPort())).toString() );
|
||||
|
||||
ProxySelector.setDefault(ProxySelector.of(new InetSocketAddress(firstNonLoAddress, proxy.getLocalPort())));
|
||||
makeHttpCall();
|
||||
|
||||
if (httpTrans.hasBadRequest) {
|
||||
throw new RuntimeException("Test failed : bad http request");
|
||||
}
|
||||
} finally {
|
||||
if (proxy != null) {
|
||||
proxy.terminate();
|
||||
}
|
||||
if (server != null) {
|
||||
server.terminate();
|
||||
server.stop(1);
|
||||
}
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(reservedHV);
|
||||
}
|
||||
@ -135,21 +144,47 @@ public class B6216082 {
|
||||
return oaddr.orElseGet(() -> null);
|
||||
}
|
||||
|
||||
public static void startHttpServer() throws IOException {
|
||||
public static void startHttpServer() throws Exception {
|
||||
// Both the https server and the proxy let the
|
||||
// system pick up an ephemeral port.
|
||||
httpTrans = new SimpleHttpTransaction();
|
||||
server = new TestHttpsServer(httpTrans, 1, 10, firstNonLoAddress, 0);
|
||||
// create and initialize a SSLContext
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
KeyStore ts = KeyStore.getInstance("JKS");
|
||||
char[] passphrase = "passphrase".toCharArray();
|
||||
|
||||
ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), passphrase);
|
||||
ts.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), passphrase);
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
kmf.init(ks, passphrase);
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
||||
tmf.init(ts);
|
||||
|
||||
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
||||
|
||||
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
|
||||
server = HttpsServer.create(new InetSocketAddress(firstNonLoAddress, 0), 10);
|
||||
server.setHttpsConfigurator(new HttpsConfigurator(sslCtx));
|
||||
server.createContext("/", httpTrans);
|
||||
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||
server.start();
|
||||
proxy = new TunnelProxy(1, 10, firstNonLoAddress, 0);
|
||||
}
|
||||
|
||||
public static void makeHttpCall() throws Exception {
|
||||
System.out.println("https server listen on: " + server.getLocalPort());
|
||||
System.out.println("https server listen on: " + server.getAddress().getPort());
|
||||
System.out.println("https proxy listen on: " + proxy.getLocalPort());
|
||||
URL url = new URL("https" , firstNonLoAddress.getHostAddress(),
|
||||
server.getLocalPort(), "/");
|
||||
server.getAddress().getPort(), "/");
|
||||
HttpURLConnection uc = (HttpURLConnection)url.openConnection();
|
||||
System.out.println(uc.getResponseCode());
|
||||
if(uc.getResponseCode() != 200) {
|
||||
uc.disconnect();
|
||||
throw new RuntimeException("Test failed : bad http request with response code : "+ uc.getResponseCode());
|
||||
}
|
||||
uc.disconnect();
|
||||
}
|
||||
|
||||
@ -160,31 +195,21 @@ public class B6216082 {
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleHttpTransaction implements HttpCallback {
|
||||
public boolean hasBadRequest = false;
|
||||
class SimpleHttpTransaction implements HttpHandler {
|
||||
|
||||
/*
|
||||
* Our http server which simply redirect first call
|
||||
*/
|
||||
public void request(HttpTransaction trans) {
|
||||
public void handle(HttpExchange trans) {
|
||||
try {
|
||||
String path = trans.getRequestURI().getPath();
|
||||
if (path.equals("/")) {
|
||||
// the first call, redirect it
|
||||
String location = "/redirect";
|
||||
trans.addResponseHeader("Location", location);
|
||||
trans.sendResponse(302, "Moved Temporarily");
|
||||
trans.getResponseHeaders().set("Location", location);
|
||||
trans.sendResponseHeaders(302, -1);
|
||||
} else {
|
||||
// if the bug exsits, it'll send 2 GET commands
|
||||
// check 2nd GET here
|
||||
String duplicatedGet = trans.getRequestHeader(null);
|
||||
if (duplicatedGet != null &&
|
||||
duplicatedGet.toUpperCase().indexOf("GET") >= 0) {
|
||||
trans.sendResponse(400, "Bad Request");
|
||||
hasBadRequest = true;
|
||||
} else {
|
||||
trans.sendResponse(200, "OK");
|
||||
}
|
||||
trans.sendResponseHeaders(200, -1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,12 +25,27 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import sun.net.www.MessageHeader;
|
||||
import java.util.*;
|
||||
|
||||
public class TunnelProxy {
|
||||
|
||||
@ -43,7 +58,6 @@ public class TunnelProxy {
|
||||
* Create a <code>TunnelProxy<code> instance with the specified callback object
|
||||
* for handling requests. One thread is created to handle requests,
|
||||
* and up to ten TCP connections will be handled simultaneously.
|
||||
* @param cb the callback object which is invoked to handle each
|
||||
* incoming request
|
||||
*/
|
||||
|
||||
@ -55,8 +69,6 @@ public class TunnelProxy {
|
||||
* Create a <code>TunnelProxy<code> instance with the specified number of
|
||||
* threads and maximum number of connections per thread. This functions
|
||||
* the same as the 4 arg constructor, where the port argument is set to zero.
|
||||
* @param cb the callback object which is invoked to handle each
|
||||
* incoming request
|
||||
* @param threads the number of threads to create to handle requests
|
||||
* in parallel
|
||||
* @param cperthread the number of simultaneous TCP connections to
|
||||
@ -74,8 +86,6 @@ public class TunnelProxy {
|
||||
* the specified port. The specified number of threads are created to
|
||||
* handle incoming requests, and each thread is allowed
|
||||
* to handle a number of simultaneous TCP connections.
|
||||
* @param cb the callback object which is invoked to handle
|
||||
* each incoming request
|
||||
* @param threads the number of threads to create to handle
|
||||
* requests in parallel
|
||||
* @param cperthread the number of simultaneous TCP connections
|
||||
@ -95,8 +105,6 @@ public class TunnelProxy {
|
||||
* the specified port. The specified number of threads are created to
|
||||
* handle incoming requests, and each thread is allowed
|
||||
* to handle a number of simultaneous TCP connections.
|
||||
* @param cb the callback object which is invoked to handle
|
||||
* each incoming request
|
||||
* @param threads the number of threads to create to handle
|
||||
* requests in parallel
|
||||
* @param cperthread the number of simultaneous TCP connections
|
||||
@ -249,7 +257,6 @@ public class TunnelProxy {
|
||||
/* return true if the connection is closed, false otherwise */
|
||||
|
||||
private boolean read (SocketChannel chan, SelectionKey key) {
|
||||
HttpTransaction msg;
|
||||
boolean res;
|
||||
try {
|
||||
InputStream is = new BufferedInputStream (new NioInputStream (chan));
|
||||
|
||||
@ -1,983 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
import java.util.*;
|
||||
import javax.net.ssl.*;
|
||||
import javax.net.ssl.SSLEngineResult.*;
|
||||
import java.security.*;
|
||||
|
||||
/**
|
||||
* This class implements a simple HTTPS server. It uses multiple threads to
|
||||
* handle connections in parallel, and will spin off a new thread to handle
|
||||
* each request. (this is easier to implement with SSLEngine)
|
||||
* <p>
|
||||
* It must be instantiated with a {@link HttpCallback} object to which
|
||||
* requests are given and must be handled.
|
||||
* <p>
|
||||
* Simple synchronization between the client(s) and server can be done
|
||||
* using the {@link #waitForCondition(String)}, {@link #setCondition(String)} and
|
||||
* {@link #rendezvous(String,int)} methods.
|
||||
*
|
||||
* NOTE NOTE NOTE NOTE NOTE NOTE NOTE
|
||||
*
|
||||
* If you make a change in here, please don't forget to make the
|
||||
* corresponding change in the J2SE equivalent.
|
||||
*
|
||||
* NOTE NOTE NOTE NOTE NOTE NOTE NOTE
|
||||
*/
|
||||
|
||||
public class TestHttpsServer {
|
||||
|
||||
ServerSocketChannel schan;
|
||||
int threads;
|
||||
int cperthread;
|
||||
HttpCallback cb;
|
||||
Server[] servers;
|
||||
|
||||
// ssl related fields
|
||||
static SSLContext sslCtx;
|
||||
|
||||
/**
|
||||
* Create a <code>TestHttpsServer<code> instance with the specified callback object
|
||||
* for handling requests. One thread is created to handle requests,
|
||||
* and up to ten TCP connections will be handled simultaneously.
|
||||
* @param cb the callback object which is invoked to handle each
|
||||
* incoming request
|
||||
*/
|
||||
|
||||
public TestHttpsServer(HttpCallback cb) throws IOException {
|
||||
this(cb, 1, 10, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>TestHttpsServer<code> instance with the specified number of
|
||||
* threads and maximum number of connections per thread. This functions
|
||||
* the same as the 4 arg constructor, where the port argument is set to zero.
|
||||
* @param cb the callback object which is invoked to handle each
|
||||
* incoming request
|
||||
* @param threads the number of threads to create to handle requests
|
||||
* in parallel
|
||||
* @param cperthread the number of simultaneous TCP connections to
|
||||
* handle per thread
|
||||
*/
|
||||
|
||||
public TestHttpsServer(HttpCallback cb, int threads, int cperthread)
|
||||
throws IOException {
|
||||
this(cb, threads, cperthread, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>TestHttpsServer<code> instance with the specified number
|
||||
* of threads and maximum number of connections per thread and running on
|
||||
* the specified port. The specified number of threads are created to
|
||||
* handle incoming requests, and each thread is allowed
|
||||
* to handle a number of simultaneous TCP connections.
|
||||
* @param cb the callback object which is invoked to handle
|
||||
* each incoming request
|
||||
* @param threads the number of threads to create to handle
|
||||
* requests in parallel
|
||||
* @param cperthread the number of simultaneous TCP connections
|
||||
* to handle per thread
|
||||
* @param port the port number to bind the server to. <code>Zero</code>
|
||||
* means choose any free port.
|
||||
*/
|
||||
public TestHttpsServer(HttpCallback cb, int threads, int cperthread, int port)
|
||||
throws IOException {
|
||||
this(cb, threads, cperthread, null, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>TestHttpsServer<code> instance with the specified number
|
||||
* of threads and maximum number of connections per thread and running on
|
||||
* the specified port. The specified number of threads are created to
|
||||
* handle incoming requests, and each thread is allowed
|
||||
* to handle a number of simultaneous TCP connections.
|
||||
* @param cb the callback object which is invoked to handle
|
||||
* each incoming request
|
||||
* @param threads the number of threads to create to handle
|
||||
* requests in parallel
|
||||
* @param cperthread the number of simultaneous TCP connections
|
||||
* to handle per thread
|
||||
* @param address the InetAddress to bind to. {@code Null} means the
|
||||
* wildcard address.
|
||||
* @param port the port number to bind the server to. {@code Zero}
|
||||
* means choose any free port.
|
||||
*/
|
||||
|
||||
public TestHttpsServer(HttpCallback cb, int threads, int cperthread, InetAddress address, int port)
|
||||
throws IOException {
|
||||
schan = ServerSocketChannel.open();
|
||||
InetSocketAddress addr = new InetSocketAddress(address, port);
|
||||
schan.socket().bind(addr);
|
||||
this.threads = threads;
|
||||
this.cb = cb;
|
||||
this.cperthread = cperthread;
|
||||
|
||||
try {
|
||||
// create and initialize a SSLContext
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
KeyStore ts = KeyStore.getInstance("JKS");
|
||||
char[] passphrase = "passphrase".toCharArray();
|
||||
|
||||
ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), passphrase);
|
||||
ts.load(new FileInputStream(System.getProperty("javax.net.ssl.trustStore")), passphrase);
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
kmf.init(ks, passphrase);
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
||||
tmf.init(ts);
|
||||
|
||||
sslCtx = SSLContext.getInstance("TLS");
|
||||
|
||||
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
|
||||
servers = new Server[threads];
|
||||
for (int i=0; i<threads; i++) {
|
||||
servers[i] = new Server(cb, schan, cperthread);
|
||||
servers[i].start();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("test failed. cause: "+ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Tell all threads in the server to exit within 5 seconds.
|
||||
* This is an abortive termination. Just prior to the thread exiting
|
||||
* all channels in that thread waiting to be closed are forceably closed.
|
||||
*/
|
||||
|
||||
public void terminate() {
|
||||
for (int i=0; i<threads; i++) {
|
||||
servers[i].terminate ();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return the local port number to which the server is bound.
|
||||
* @return the local port number
|
||||
*/
|
||||
|
||||
public int getLocalPort () {
|
||||
return schan.socket().getLocalPort ();
|
||||
}
|
||||
|
||||
public String getAuthority() {
|
||||
InetAddress address = schan.socket().getInetAddress();
|
||||
String hostaddr = address.getHostAddress();
|
||||
if (address.isAnyLocalAddress()) hostaddr = "localhost";
|
||||
if (hostaddr.indexOf(':') > -1) hostaddr = "[" + hostaddr + "]";
|
||||
return hostaddr + ":" + getLocalPort();
|
||||
}
|
||||
|
||||
static class Server extends Thread {
|
||||
|
||||
ServerSocketChannel schan;
|
||||
Selector selector;
|
||||
SelectionKey listenerKey;
|
||||
SelectionKey key; /* the current key being processed */
|
||||
HttpCallback cb;
|
||||
ByteBuffer consumeBuffer;
|
||||
int maxconn;
|
||||
int nconn;
|
||||
ClosedChannelList clist;
|
||||
boolean shutdown;
|
||||
|
||||
Server(HttpCallback cb, ServerSocketChannel schan, int maxconn) {
|
||||
this.schan = schan;
|
||||
this.maxconn = maxconn;
|
||||
this.cb = cb;
|
||||
nconn = 0;
|
||||
consumeBuffer = ByteBuffer.allocate(512);
|
||||
clist = new ClosedChannelList();
|
||||
try {
|
||||
selector = Selector.open();
|
||||
schan.configureBlocking(false);
|
||||
listenerKey = schan.register(selector, SelectionKey.OP_ACCEPT);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Server could not start: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Stop the thread as soon as possible */
|
||||
public synchronized void terminate() {
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
selector.select(1000);
|
||||
Set selected = selector.selectedKeys();
|
||||
Iterator iter = selected.iterator();
|
||||
while (iter.hasNext()) {
|
||||
key = (SelectionKey)iter.next();
|
||||
if (key.equals (listenerKey)) {
|
||||
SocketChannel sock = schan.accept();
|
||||
if (sock == null) {
|
||||
/* false notification */
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
sock.configureBlocking(true);
|
||||
SSLEngine sslEng = sslCtx.createSSLEngine();
|
||||
sslEng.setUseClientMode(false);
|
||||
new ServerWorker(cb, sock, sslEng).start();
|
||||
nconn ++;
|
||||
if (nconn == maxconn) {
|
||||
/* deregister */
|
||||
listenerKey.cancel();
|
||||
listenerKey = null;
|
||||
}
|
||||
} else {
|
||||
if (key.isReadable()) {
|
||||
boolean closed = false;
|
||||
SocketChannel chan = (SocketChannel)key.channel();
|
||||
if (key.attachment() != null) {
|
||||
closed = consume(chan);
|
||||
}
|
||||
|
||||
if (closed) {
|
||||
chan.close();
|
||||
key.cancel();
|
||||
if (nconn == maxconn) {
|
||||
listenerKey = schan.register(selector, SelectionKey.OP_ACCEPT);
|
||||
}
|
||||
nconn --;
|
||||
}
|
||||
}
|
||||
}
|
||||
iter.remove();
|
||||
}
|
||||
clist.check();
|
||||
|
||||
synchronized (this) {
|
||||
if (shutdown) {
|
||||
clist.terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("Server exception: " + e);
|
||||
// TODO finish
|
||||
}
|
||||
}
|
||||
|
||||
/* read all the data off the channel without looking at it
|
||||
* return true if connection closed
|
||||
*/
|
||||
boolean consume(SocketChannel chan) {
|
||||
try {
|
||||
consumeBuffer.clear();
|
||||
int c = chan.read(consumeBuffer);
|
||||
if (c == -1)
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static class ServerWorker extends Thread {
|
||||
private ByteBuffer inNetBB;
|
||||
private ByteBuffer outNetBB;
|
||||
private ByteBuffer inAppBB;
|
||||
private ByteBuffer outAppBB;
|
||||
|
||||
SSLEngine sslEng;
|
||||
SocketChannel schan;
|
||||
HttpCallback cb;
|
||||
HandshakeStatus currentHSStatus;
|
||||
boolean initialHSComplete;
|
||||
boolean handshakeStarted;
|
||||
/*
|
||||
* All inbound data goes through this buffer.
|
||||
*
|
||||
* It might be nice to use a cache of ByteBuffers so we're
|
||||
* not alloc/dealloc'ing all over the place.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Application buffers, also used for handshaking
|
||||
*/
|
||||
private int appBBSize;
|
||||
|
||||
ServerWorker(HttpCallback cb, SocketChannel schan, SSLEngine sslEng) {
|
||||
this.sslEng = sslEng;
|
||||
this.schan = schan;
|
||||
this.cb = cb;
|
||||
currentHSStatus = HandshakeStatus.NEED_UNWRAP;
|
||||
initialHSComplete = false;
|
||||
int netBBSize = sslEng.getSession().getPacketBufferSize();
|
||||
inNetBB = ByteBuffer.allocate(netBBSize);
|
||||
outNetBB = ByteBuffer.allocate(netBBSize);
|
||||
appBBSize = sslEng.getSession().getApplicationBufferSize();
|
||||
inAppBB = ByteBuffer.allocate(appBBSize);
|
||||
outAppBB = ByteBuffer.allocate(appBBSize);
|
||||
}
|
||||
|
||||
public SSLEngine getSSLEngine() {
|
||||
return sslEng;
|
||||
}
|
||||
|
||||
public ByteBuffer outNetBB() {
|
||||
return outNetBB;
|
||||
}
|
||||
|
||||
public ByteBuffer outAppBB() {
|
||||
return outAppBB;
|
||||
}
|
||||
|
||||
public void run () {
|
||||
try {
|
||||
SSLEngineResult result;
|
||||
|
||||
while (!initialHSComplete) {
|
||||
|
||||
switch (currentHSStatus) {
|
||||
|
||||
case NEED_UNWRAP:
|
||||
int bytes = schan.read(inNetBB);
|
||||
if (!handshakeStarted && bytes > 0) {
|
||||
handshakeStarted = true;
|
||||
int byte0 = inNetBB.get(0);
|
||||
if (byte0 != 0x16) {
|
||||
// first byte of a TLS connection is supposed to be
|
||||
// 0x16. If not it may be a plain text connection.
|
||||
//
|
||||
// Sometime a rogue client may try to open a plain
|
||||
// connection with our server. Calling this method
|
||||
// gives a chance to the test logic to ignore such
|
||||
// rogue connections.
|
||||
//
|
||||
if (cb.dropPlainTextConnections()) {
|
||||
try { schan.close(); } catch (IOException x) { };
|
||||
return;
|
||||
}
|
||||
// else sslEng.unwrap will throw later on...
|
||||
}
|
||||
}
|
||||
|
||||
needIO:
|
||||
while (currentHSStatus == HandshakeStatus.NEED_UNWRAP) {
|
||||
/*
|
||||
* Don't need to resize requestBB, since no app data should
|
||||
* be generated here.
|
||||
*/
|
||||
inNetBB.flip();
|
||||
result = sslEng.unwrap(inNetBB, inAppBB);
|
||||
inNetBB.compact();
|
||||
currentHSStatus = result.getHandshakeStatus();
|
||||
|
||||
switch (result.getStatus()) {
|
||||
|
||||
case OK:
|
||||
switch (currentHSStatus) {
|
||||
case NOT_HANDSHAKING:
|
||||
throw new IOException(
|
||||
"Not handshaking during initial handshake");
|
||||
|
||||
case NEED_TASK:
|
||||
Runnable task;
|
||||
while ((task = sslEng.getDelegatedTask()) != null) {
|
||||
task.run();
|
||||
currentHSStatus = sslEng.getHandshakeStatus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BUFFER_UNDERFLOW:
|
||||
break needIO;
|
||||
|
||||
default: // BUFFER_OVERFLOW/CLOSED:
|
||||
throw new IOException("Received" + result.getStatus() +
|
||||
"during initial handshaking");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Just transitioned from read to write.
|
||||
*/
|
||||
if (currentHSStatus != HandshakeStatus.NEED_WRAP) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Fall through and fill the write buffer.
|
||||
|
||||
case NEED_WRAP:
|
||||
/*
|
||||
* The flush above guarantees the out buffer to be empty
|
||||
*/
|
||||
outNetBB.clear();
|
||||
result = sslEng.wrap(inAppBB, outNetBB);
|
||||
outNetBB.flip();
|
||||
schan.write (outNetBB);
|
||||
outNetBB.compact();
|
||||
currentHSStatus = result.getHandshakeStatus();
|
||||
|
||||
switch (result.getStatus()) {
|
||||
case OK:
|
||||
|
||||
if (currentHSStatus == HandshakeStatus.NEED_TASK) {
|
||||
Runnable task;
|
||||
while ((task = sslEng.getDelegatedTask()) != null) {
|
||||
task.run();
|
||||
currentHSStatus = sslEng.getHandshakeStatus();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:
|
||||
throw new IOException("Received" + result.getStatus() +
|
||||
"during initial handshaking");
|
||||
}
|
||||
break;
|
||||
|
||||
case FINISHED:
|
||||
initialHSComplete = true;
|
||||
break;
|
||||
default: // NOT_HANDSHAKING/NEED_TASK
|
||||
throw new RuntimeException("Invalid Handshaking State" +
|
||||
currentHSStatus);
|
||||
} // switch
|
||||
}
|
||||
// read the application data; using non-blocking mode
|
||||
schan.configureBlocking(false);
|
||||
read(schan, sslEng);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/* return true if the connection is closed, false otherwise */
|
||||
|
||||
private boolean read(SocketChannel chan, SSLEngine sslEng) {
|
||||
HttpTransaction msg;
|
||||
boolean res;
|
||||
try {
|
||||
InputStream is = new BufferedInputStream(new NioInputStream(chan, sslEng, inNetBB, inAppBB));
|
||||
String requestline = readLine(is);
|
||||
MessageHeader mhead = new MessageHeader(is);
|
||||
String clen = mhead.findValue("Content-Length");
|
||||
String trferenc = mhead.findValue("Transfer-Encoding");
|
||||
String data = null;
|
||||
if (trferenc != null && trferenc.equals("chunked"))
|
||||
data = new String(readChunkedData(is));
|
||||
else if (clen != null)
|
||||
data = new String(readNormalData(is, Integer.parseInt(clen)));
|
||||
String[] req = requestline.split(" ");
|
||||
if (req.length < 2) {
|
||||
/* invalid request line */
|
||||
return false;
|
||||
}
|
||||
String cmd = req[0];
|
||||
URI uri = null;
|
||||
try {
|
||||
uri = new URI(req[1]);
|
||||
msg = new HttpTransaction(this, cmd, uri, mhead, data, null, chan);
|
||||
cb.request(msg);
|
||||
} catch (URISyntaxException e) {
|
||||
System.err.println ("Invalid URI: " + e);
|
||||
msg = new HttpTransaction(this, cmd, null, null, null, null, chan);
|
||||
msg.sendResponse(501, "Whatever");
|
||||
}
|
||||
res = false;
|
||||
} catch (IOException e) {
|
||||
res = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
byte[] readNormalData(InputStream is, int len) throws IOException {
|
||||
byte[] buf = new byte[len];
|
||||
int c, off=0, remain=len;
|
||||
while (remain > 0 && ((c=is.read (buf, off, remain))>0)) {
|
||||
remain -= c;
|
||||
off += c;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private void readCRLF(InputStream is) throws IOException {
|
||||
int cr = is.read();
|
||||
int lf = is.read();
|
||||
|
||||
if (((cr & 0xff) != 0x0d) ||
|
||||
((lf & 0xff) != 0x0a)) {
|
||||
throw new IOException(
|
||||
"Expected <CR><LF>: got '" + cr + "/" + lf + "'");
|
||||
}
|
||||
}
|
||||
|
||||
byte[] readChunkedData(InputStream is) throws IOException {
|
||||
LinkedList l = new LinkedList();
|
||||
int total = 0;
|
||||
for (int len=readChunkLen(is); len!=0; len=readChunkLen(is)) {
|
||||
l.add(readNormalData(is, len));
|
||||
total += len;
|
||||
readCRLF(is); // CRLF at end of chunk
|
||||
}
|
||||
readCRLF(is); // CRLF at end of Chunked Stream.
|
||||
byte[] buf = new byte[total];
|
||||
Iterator i = l.iterator();
|
||||
int x = 0;
|
||||
while (i.hasNext()) {
|
||||
byte[] b = (byte[])i.next();
|
||||
System.arraycopy(b, 0, buf, x, b.length);
|
||||
x += b.length;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private int readChunkLen(InputStream is) throws IOException {
|
||||
int c, len=0;
|
||||
boolean done=false, readCR=false;
|
||||
while (!done) {
|
||||
c = is.read();
|
||||
if (c == '\n' && readCR) {
|
||||
done = true;
|
||||
} else {
|
||||
if (c == '\r' && !readCR) {
|
||||
readCR = true;
|
||||
} else {
|
||||
int x=0;
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
x = c - 'a' + 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
x = c - 'A' + 10;
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
x = c - '0';
|
||||
}
|
||||
len = len * 16 + x;
|
||||
}
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
private String readLine(InputStream is) throws IOException {
|
||||
boolean done=false, readCR=false;
|
||||
byte[] b = new byte[512];
|
||||
int c, l = 0;
|
||||
|
||||
while (!done) {
|
||||
c = is.read();
|
||||
if (c == '\n' && readCR) {
|
||||
done = true;
|
||||
} else {
|
||||
if (c == '\r' && !readCR) {
|
||||
readCR = true;
|
||||
} else {
|
||||
b[l++] = (byte)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new String(b);
|
||||
}
|
||||
|
||||
/** close the channel associated with the current key by:
|
||||
* 1. shutdownOutput (send a FIN)
|
||||
* 2. mark the key so that incoming data is to be consumed and discarded
|
||||
* 3. After a period, close the socket
|
||||
*/
|
||||
|
||||
synchronized void orderlyCloseChannel(SocketChannel ch) throws IOException {
|
||||
ch.socket().shutdownOutput();
|
||||
}
|
||||
|
||||
synchronized void abortiveCloseChannel(SocketChannel ch) throws IOException {
|
||||
Socket s = ch.socket();
|
||||
s.setSoLinger(true, 0);
|
||||
ch.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements blocking reading semantics on top of a non-blocking channel
|
||||
*/
|
||||
|
||||
static class NioInputStream extends InputStream {
|
||||
SSLEngine sslEng;
|
||||
SocketChannel channel;
|
||||
Selector selector;
|
||||
ByteBuffer inNetBB;
|
||||
ByteBuffer inAppBB;
|
||||
SelectionKey key;
|
||||
int available;
|
||||
byte[] one;
|
||||
boolean closed;
|
||||
ByteBuffer markBuf; /* reads may be satisifed from this buffer */
|
||||
boolean marked;
|
||||
boolean reset;
|
||||
int readlimit;
|
||||
|
||||
public NioInputStream(SocketChannel chan, SSLEngine sslEng, ByteBuffer inNetBB, ByteBuffer inAppBB) throws IOException {
|
||||
this.sslEng = sslEng;
|
||||
this.channel = chan;
|
||||
selector = Selector.open();
|
||||
this.inNetBB = inNetBB;
|
||||
this.inAppBB = inAppBB;
|
||||
key = chan.register(selector, SelectionKey.OP_READ);
|
||||
available = 0;
|
||||
one = new byte[1];
|
||||
closed = marked = reset = false;
|
||||
}
|
||||
|
||||
public synchronized int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
public synchronized int read() throws IOException {
|
||||
return read(one, 0, 1);
|
||||
}
|
||||
|
||||
public synchronized int read(byte[] b, int off, int srclen) throws IOException {
|
||||
|
||||
int canreturn, willreturn;
|
||||
|
||||
if (closed)
|
||||
return -1;
|
||||
|
||||
if (reset) { /* satisfy from markBuf */
|
||||
canreturn = markBuf.remaining();
|
||||
willreturn = canreturn > srclen ? srclen : canreturn;
|
||||
markBuf.get(b, off, willreturn);
|
||||
if (canreturn == willreturn) {
|
||||
reset = false;
|
||||
}
|
||||
} else { /* satisfy from channel */
|
||||
canreturn = available();
|
||||
if (canreturn == 0) {
|
||||
block();
|
||||
canreturn = available();
|
||||
}
|
||||
willreturn = canreturn > srclen ? srclen : canreturn;
|
||||
inAppBB.get(b, off, willreturn);
|
||||
available -= willreturn;
|
||||
|
||||
if (marked) { /* copy into markBuf */
|
||||
try {
|
||||
markBuf.put(b, off, willreturn);
|
||||
} catch (BufferOverflowException e) {
|
||||
marked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return willreturn;
|
||||
}
|
||||
|
||||
public synchronized int available() throws IOException {
|
||||
if (closed)
|
||||
throw new IOException("Stream is closed");
|
||||
|
||||
if (reset)
|
||||
return markBuf.remaining();
|
||||
|
||||
if (available > 0)
|
||||
return available;
|
||||
|
||||
inAppBB.clear();
|
||||
int bytes = channel.read(inNetBB);
|
||||
|
||||
int needed = sslEng.getSession().getApplicationBufferSize();
|
||||
if (needed > inAppBB.remaining()) {
|
||||
inAppBB = ByteBuffer.allocate(needed);
|
||||
}
|
||||
inNetBB.flip();
|
||||
SSLEngineResult result = sslEng.unwrap(inNetBB, inAppBB);
|
||||
inNetBB.compact();
|
||||
available = result.bytesProduced();
|
||||
|
||||
if (available > 0)
|
||||
inAppBB.flip();
|
||||
else if (available == -1)
|
||||
throw new IOException("Stream is closed");
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* block() only called when available==0 and buf is empty
|
||||
*/
|
||||
private synchronized void block() throws IOException {
|
||||
//assert available == 0;
|
||||
int n = selector.select();
|
||||
//assert n == 1;
|
||||
selector.selectedKeys().clear();
|
||||
available();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (closed)
|
||||
return;
|
||||
channel.close();
|
||||
closed = true;
|
||||
}
|
||||
|
||||
public synchronized void mark(int readlimit) {
|
||||
if (closed)
|
||||
return;
|
||||
this.readlimit = readlimit;
|
||||
markBuf = ByteBuffer.allocate(readlimit);
|
||||
marked = true;
|
||||
reset = false;
|
||||
}
|
||||
|
||||
public synchronized void reset() throws IOException {
|
||||
if (closed )
|
||||
return;
|
||||
if (!marked)
|
||||
throw new IOException("Stream not marked");
|
||||
marked = false;
|
||||
reset = true;
|
||||
markBuf.flip();
|
||||
}
|
||||
}
|
||||
|
||||
static class NioOutputStream extends OutputStream {
|
||||
SSLEngine sslEng;
|
||||
SocketChannel channel;
|
||||
ByteBuffer outNetBB;
|
||||
ByteBuffer outAppBB;
|
||||
SelectionKey key;
|
||||
Selector selector;
|
||||
boolean closed;
|
||||
byte[] one;
|
||||
|
||||
public NioOutputStream(SocketChannel channel, SSLEngine sslEng, ByteBuffer outNetBB, ByteBuffer outAppBB) throws IOException {
|
||||
this.sslEng = sslEng;
|
||||
this.channel = channel;
|
||||
this.outNetBB = outNetBB;
|
||||
this.outAppBB = outAppBB;
|
||||
selector = Selector.open();
|
||||
key = channel.register(selector, SelectionKey.OP_WRITE);
|
||||
closed = false;
|
||||
one = new byte[1];
|
||||
}
|
||||
|
||||
public synchronized void write(int b) throws IOException {
|
||||
one[0] = (byte)b;
|
||||
write(one, 0, 1);
|
||||
}
|
||||
|
||||
public synchronized void write(byte[] b) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
public synchronized void write(byte[] b, int off, int len) throws IOException {
|
||||
if (closed)
|
||||
throw new IOException("stream is closed");
|
||||
|
||||
outAppBB = ByteBuffer.allocate(len);
|
||||
outAppBB.put(b, off, len);
|
||||
outAppBB.flip();
|
||||
int n;
|
||||
outNetBB.clear();
|
||||
int needed = sslEng.getSession().getPacketBufferSize();
|
||||
if (outNetBB.capacity() < needed) {
|
||||
outNetBB = ByteBuffer.allocate(needed);
|
||||
}
|
||||
SSLEngineResult ret = sslEng.wrap(outAppBB, outNetBB);
|
||||
outNetBB.flip();
|
||||
int newLen = ret.bytesProduced();
|
||||
while ((n = channel.write (outNetBB)) < newLen) {
|
||||
newLen -= n;
|
||||
if (newLen == 0)
|
||||
return;
|
||||
selector.select();
|
||||
selector.selectedKeys().clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (closed)
|
||||
return;
|
||||
channel.close();
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities for synchronization. A condition is
|
||||
* identified by a string name, and is initialized
|
||||
* upon first use (ie. setCondition() or waitForCondition()). Threads
|
||||
* are blocked until some thread calls (or has called) setCondition() for the same
|
||||
* condition.
|
||||
* <P>
|
||||
* A rendezvous built on a condition is also provided for synchronizing
|
||||
* N threads.
|
||||
*/
|
||||
|
||||
private static HashMap conditions = new HashMap();
|
||||
|
||||
/*
|
||||
* Modifiable boolean object
|
||||
*/
|
||||
private static class BValue {
|
||||
boolean v;
|
||||
}
|
||||
|
||||
/*
|
||||
* Modifiable int object
|
||||
*/
|
||||
private static class IValue {
|
||||
int v;
|
||||
IValue(int i) {
|
||||
v =i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static BValue getCond(String condition) {
|
||||
synchronized (conditions) {
|
||||
BValue cond = (BValue) conditions.get(condition);
|
||||
if (cond == null) {
|
||||
cond = new BValue();
|
||||
conditions.put(condition, cond);
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the condition to true. Any threads that are currently blocked
|
||||
* waiting on the condition, will be unblocked and allowed to continue.
|
||||
* Threads that subsequently call waitForCondition() will not block.
|
||||
* If the named condition did not exist prior to the call, then it is created
|
||||
* first.
|
||||
*/
|
||||
|
||||
public static void setCondition(String condition) {
|
||||
BValue cond = getCond(condition);
|
||||
synchronized (cond) {
|
||||
if (cond.v) {
|
||||
return;
|
||||
}
|
||||
cond.v = true;
|
||||
cond.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the named condition does not exist, then it is created and initialized
|
||||
* to false. If the condition exists or has just been created and its value
|
||||
* is false, then the thread blocks until another thread sets the condition.
|
||||
* If the condition exists and is already set to true, then this call returns
|
||||
* immediately without blocking.
|
||||
*/
|
||||
|
||||
public static void waitForCondition(String condition) {
|
||||
BValue cond = getCond(condition);
|
||||
synchronized (cond) {
|
||||
if (!cond.v) {
|
||||
try {
|
||||
cond.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* conditions must be locked when accessing this */
|
||||
static HashMap rv = new HashMap();
|
||||
|
||||
/**
|
||||
* Force N threads to rendezvous (ie. wait for each other) before proceeding.
|
||||
* The first thread(s) to call are blocked until the last
|
||||
* thread makes the call. Then all threads continue.
|
||||
* <p>
|
||||
* All threads that call with the same condition name, must use the same value
|
||||
* for N (or the results may be not be as expected).
|
||||
* <P>
|
||||
* Obviously, if fewer than N threads make the rendezvous then the result
|
||||
* will be a hang.
|
||||
*/
|
||||
|
||||
public static void rendezvous(String condition, int N) {
|
||||
BValue cond;
|
||||
IValue iv;
|
||||
String name = "RV_"+condition;
|
||||
|
||||
/* get the condition */
|
||||
|
||||
synchronized (conditions) {
|
||||
cond = (BValue)conditions.get(name);
|
||||
if (cond == null) {
|
||||
/* we are first caller */
|
||||
if (N < 2) {
|
||||
throw new RuntimeException("rendezvous must be called with N >= 2");
|
||||
}
|
||||
cond = new BValue();
|
||||
conditions.put(name, cond);
|
||||
iv = new IValue(N-1);
|
||||
rv.put(name, iv);
|
||||
} else {
|
||||
/* already initialised, just decrement the counter */
|
||||
iv = (IValue) rv.get(name);
|
||||
iv.v--;
|
||||
}
|
||||
}
|
||||
|
||||
if (iv.v > 0) {
|
||||
waitForCondition(name);
|
||||
} else {
|
||||
setCondition(name);
|
||||
synchronized (conditions) {
|
||||
clearCondition(name);
|
||||
rv.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the named condition exists and is set then remove it, so it can
|
||||
* be re-initialized and used again. If the condition does not exist, or
|
||||
* exists but is not set, then the call returns without doing anything.
|
||||
* Note, some higher level synchronization
|
||||
* may be needed between clear and the other operations.
|
||||
*/
|
||||
|
||||
public static void clearCondition(String condition) {
|
||||
BValue cond;
|
||||
synchronized (conditions) {
|
||||
cond = (BValue) conditions.get(condition);
|
||||
if (cond == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (cond) {
|
||||
if (cond.v) {
|
||||
conditions.remove(condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user