8282354: Remove dependancy of TestHttpServer, HttpTransaction, HttpCallback from open/test/jdk/ tests
Reviewed-by: dfuchs
This commit is contained in:
parent
f99193ae3f
commit
95ca94436d
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -27,15 +27,25 @@
|
|||||||
* @summary PIT: Can no launch jnlp application via 127.0.0.1 address on the web server.
|
* @summary PIT: Can no launch jnlp application via 127.0.0.1 address on the web server.
|
||||||
* This test might fail intermittently as it needs a server that
|
* This test might fail intermittently as it needs a server that
|
||||||
* binds to the wildcard address.
|
* binds to the wildcard address.
|
||||||
* @modules java.base/sun.net.www
|
* @library /test/lib
|
||||||
* @library ../../../sun/net/www/httptest/ /test/lib
|
|
||||||
* @build ClosedChannelList TestHttpServer HttpTransaction HttpCallback
|
|
||||||
* @compile LoopbackAddresses.java
|
* @compile LoopbackAddresses.java
|
||||||
* @run main/othervm LoopbackAddresses
|
* @run main/othervm LoopbackAddresses
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.net.*;
|
import java.io.IOException;
|
||||||
import java.io.*;
|
import java.io.PrintWriter;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import jdk.test.lib.net.URIBuilder;
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,17 +53,8 @@ import jdk.test.lib.net.URIBuilder;
|
|||||||
* addresses when selecting proxies. This is the existing behaviour.
|
* addresses when selecting proxies. This is the existing behaviour.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class LoopbackAddresses implements HttpCallback {
|
public class LoopbackAddresses {
|
||||||
static TestHttpServer server;
|
static HttpServer server;
|
||||||
|
|
||||||
public void request (HttpTransaction req) {
|
|
||||||
req.setResponseEntityBody ("Hello .");
|
|
||||||
try {
|
|
||||||
req.sendResponse (200, "Ok");
|
|
||||||
req.orderlyClose();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
@ -63,15 +64,17 @@ public class LoopbackAddresses implements HttpCallback {
|
|||||||
// to answer both for the loopback and "localhost".
|
// to answer both for the loopback and "localhost".
|
||||||
// Though "localhost" usually point to the loopback there is no
|
// Though "localhost" usually point to the loopback there is no
|
||||||
// hard guarantee.
|
// hard guarantee.
|
||||||
server = new TestHttpServer (new LoopbackAddresses(), 1, 10, 0);
|
server = HttpServer.create(new InetSocketAddress(loopback, 0), 10, "/", new LoopbackAddressesHandler());
|
||||||
ProxyServer pserver = new ProxyServer(InetAddress.getByName("localhost"), server.getLocalPort());
|
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||||
|
server.start();
|
||||||
|
ProxyServer pserver = new ProxyServer(InetAddress.getByName("localhost"), server.getAddress().getPort());
|
||||||
// start proxy server
|
// start proxy server
|
||||||
new Thread(pserver).start();
|
new Thread(pserver).start();
|
||||||
|
|
||||||
System.setProperty("http.proxyHost", loopback.getHostAddress());
|
System.setProperty("http.proxyHost", loopback.getHostAddress());
|
||||||
System.setProperty("http.proxyPort", pserver.getPort()+"");
|
System.setProperty("http.proxyPort", pserver.getPort()+"");
|
||||||
|
|
||||||
URL url = new URL("http://localhost:"+server.getLocalPort());
|
URL url = new URL("http://localhost:"+server.getAddress().getPort());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpURLConnection urlc = (HttpURLConnection)url.openConnection ();
|
HttpURLConnection urlc = (HttpURLConnection)url.openConnection ();
|
||||||
@ -85,7 +88,7 @@ public class LoopbackAddresses implements HttpCallback {
|
|||||||
url = URIBuilder.newBuilder()
|
url = URIBuilder.newBuilder()
|
||||||
.scheme("http")
|
.scheme("http")
|
||||||
.host(loopback.getHostAddress())
|
.host(loopback.getHostAddress())
|
||||||
.port(server.getLocalPort())
|
.port(server.getAddress().getPort())
|
||||||
.toURL();
|
.toURL();
|
||||||
HttpURLConnection urlc = (HttpURLConnection)url.openConnection ();
|
HttpURLConnection urlc = (HttpURLConnection)url.openConnection ();
|
||||||
int respCode = urlc.getResponseCode();
|
int respCode = urlc.getResponseCode();
|
||||||
@ -97,7 +100,7 @@ public class LoopbackAddresses implements HttpCallback {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,3 +154,18 @@ public class LoopbackAddresses implements HttpCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LoopbackAddressesHandler implements HttpHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try(PrintWriter pw = new PrintWriter(exchange.getResponseBody(), false, Charset.forName("UTF-8"))) {
|
||||||
|
pw.print("Hello .");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,33 +25,37 @@
|
|||||||
* @test
|
* @test
|
||||||
* @bug 4696512
|
* @bug 4696512
|
||||||
* @summary HTTP client: Improve proxy server configuration and selection
|
* @summary HTTP client: Improve proxy server configuration and selection
|
||||||
* @modules java.base/sun.net.www
|
* @library /test/lib
|
||||||
* @library ../../../sun/net/www/httptest/ /test/lib
|
|
||||||
* @build ClosedChannelList TestHttpServer HttpTransaction HttpCallback
|
|
||||||
* @compile ProxyTest.java
|
* @compile ProxyTest.java
|
||||||
* @run main/othervm -Dhttp.proxyHost=inexistant -Dhttp.proxyPort=8080 ProxyTest
|
* @run main/othervm -Dhttp.proxyHost=inexistant -Dhttp.proxyPort=8080 ProxyTest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.net.*;
|
import java.io.IOException;
|
||||||
import java.io.*;
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import jdk.test.lib.net.URIBuilder;
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
public class ProxyTest implements HttpCallback {
|
public class ProxyTest {
|
||||||
static TestHttpServer server;
|
static HttpServer server;
|
||||||
|
|
||||||
public ProxyTest() {
|
public ProxyTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void request(HttpTransaction req) {
|
|
||||||
req.setResponseEntityBody("Hello .");
|
|
||||||
try {
|
|
||||||
req.sendResponse(200, "Ok");
|
|
||||||
req.orderlyClose();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public class MyProxySelector extends ProxySelector {
|
static public class MyProxySelector extends ProxySelector {
|
||||||
private static volatile URI lastURI;
|
private static volatile URI lastURI;
|
||||||
private final static List<Proxy> NO_PROXY = List.of(Proxy.NO_PROXY);
|
private final static List<Proxy> NO_PROXY = List.of(Proxy.NO_PROXY);
|
||||||
@ -75,11 +79,13 @@ public class ProxyTest implements HttpCallback {
|
|||||||
ProxySelector.setDefault(new MyProxySelector());
|
ProxySelector.setDefault(new MyProxySelector());
|
||||||
try {
|
try {
|
||||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
server = new TestHttpServer(new ProxyTest(), 1, 10, loopback, 0);
|
server = HttpServer.create(new InetSocketAddress(loopback, 0), 10, "/", new ProxyTestHandler());
|
||||||
|
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||||
|
server.start();
|
||||||
URL url = URIBuilder.newBuilder()
|
URL url = URIBuilder.newBuilder()
|
||||||
.scheme("http")
|
.scheme("http")
|
||||||
.loopback()
|
.loopback()
|
||||||
.port(server.getLocalPort())
|
.port(server.getAddress().getPort())
|
||||||
.toURL();
|
.toURL();
|
||||||
System.out.println("client opening connection to: " + url);
|
System.out.println("client opening connection to: " + url);
|
||||||
HttpURLConnection urlc = (HttpURLConnection)url.openConnection();
|
HttpURLConnection urlc = (HttpURLConnection)url.openConnection();
|
||||||
@ -93,8 +99,23 @@ public class ProxyTest implements HttpCallback {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ProxyTestHandler implements HttpHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try(PrintWriter pw = new PrintWriter(exchange.getResponseBody(), false, Charset.forName("UTF-8"))) {
|
||||||
|
pw.print("Hello .");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -24,42 +24,47 @@
|
|||||||
/* @test
|
/* @test
|
||||||
* @bug 4920526
|
* @bug 4920526
|
||||||
* @summary Needs per connection proxy support for URLs
|
* @summary Needs per connection proxy support for URLs
|
||||||
* @modules java.base/sun.net.www
|
* @library /test/lib
|
||||||
* @library ../../../sun/net/www/httptest/ /test/lib
|
|
||||||
* @build ClosedChannelList TestHttpServer HttpTransaction HttpCallback
|
|
||||||
* @compile PerConnectionProxy.java
|
* @compile PerConnectionProxy.java
|
||||||
* @run main/othervm -Dhttp.proxyHost=inexistant -Dhttp.proxyPort=8080 PerConnectionProxy
|
* @run main/othervm -Dhttp.proxyHost=inexistant -Dhttp.proxyPort=8080 PerConnectionProxy
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.net.*;
|
import java.io.IOException;
|
||||||
import java.io.*;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import jdk.test.lib.net.URIBuilder;
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
public class PerConnectionProxy implements HttpCallback {
|
public class PerConnectionProxy {
|
||||||
static TestHttpServer server;
|
static HttpServer server;
|
||||||
|
|
||||||
public void request (HttpTransaction req) {
|
|
||||||
req.setResponseEntityBody ("Hello .");
|
|
||||||
try {
|
|
||||||
req.sendResponse (200, "Ok");
|
|
||||||
req.orderlyClose();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
|
InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
|
||||||
server = new TestHttpServer(new PerConnectionProxy(), 1, 10, loopbackAddress, 0);
|
server = HttpServer.create(new InetSocketAddress(loopbackAddress, 0), 10, "/", new PerConnectionProxyHandler());
|
||||||
ProxyServer pserver = new ProxyServer(loopbackAddress, server.getLocalPort());
|
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||||
|
server.start();
|
||||||
|
ProxyServer pserver = new ProxyServer(loopbackAddress, server.getAddress().getPort());
|
||||||
// start proxy server
|
// start proxy server
|
||||||
new Thread(pserver).start();
|
new Thread(pserver).start();
|
||||||
|
|
||||||
URL url = URIBuilder.newBuilder()
|
URL url = URIBuilder.newBuilder()
|
||||||
.scheme("http")
|
.scheme("http")
|
||||||
.loopback()
|
.loopback()
|
||||||
.port(server.getLocalPort())
|
.port(server.getAddress().getPort())
|
||||||
.toURLUnchecked();
|
.toURLUnchecked();
|
||||||
|
|
||||||
// for non existing proxy expect an IOException
|
// for non existing proxy expect an IOException
|
||||||
@ -73,7 +78,6 @@ public class PerConnectionProxy implements HttpCallback {
|
|||||||
} catch (IOException ioex) {
|
} catch (IOException ioex) {
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
|
|
||||||
// for NO_PROXY, expect direct connection
|
// for NO_PROXY, expect direct connection
|
||||||
try {
|
try {
|
||||||
HttpURLConnection urlc = (HttpURLConnection)url.openConnection (Proxy.NO_PROXY);
|
HttpURLConnection urlc = (HttpURLConnection)url.openConnection (Proxy.NO_PROXY);
|
||||||
@ -82,7 +86,6 @@ public class PerConnectionProxy implements HttpCallback {
|
|||||||
} catch (IOException ioex) {
|
} catch (IOException ioex) {
|
||||||
throw new RuntimeException("direct connection should succeed :"+ioex.getMessage());
|
throw new RuntimeException("direct connection should succeed :"+ioex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// for a normal proxy setting expect to see connection
|
// for a normal proxy setting expect to see connection
|
||||||
// goes through that proxy
|
// goes through that proxy
|
||||||
try {
|
try {
|
||||||
@ -101,10 +104,9 @@ public class PerConnectionProxy implements HttpCallback {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
} finally {
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ProxyServer extends Thread {
|
static class ProxyServer extends Thread {
|
||||||
@ -145,7 +147,6 @@ public class PerConnectionProxy implements HttpCallback {
|
|||||||
|
|
||||||
private void processRequests() throws Exception {
|
private void processRequests() throws Exception {
|
||||||
// connection set to the tunneling mode
|
// connection set to the tunneling mode
|
||||||
|
|
||||||
Socket serverSocket = new Socket(serverInetAddr, serverPort);
|
Socket serverSocket = new Socket(serverInetAddr, serverPort);
|
||||||
ProxyTunnel clientToServer = new ProxyTunnel(
|
ProxyTunnel clientToServer = new ProxyTunnel(
|
||||||
clientSocket, serverSocket);
|
clientSocket, serverSocket);
|
||||||
@ -161,7 +162,6 @@ public class PerConnectionProxy implements HttpCallback {
|
|||||||
|
|
||||||
clientToServer.close();
|
clientToServer.close();
|
||||||
serverToClient.close();
|
serverToClient.close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -221,6 +221,20 @@ public class PerConnectionProxy implements HttpCallback {
|
|||||||
} catch (IOException ignored) { }
|
} catch (IOException ignored) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PerConnectionProxyHandler implements HttpHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
try(PrintWriter pw = new PrintWriter(exchange.getResponseBody(), false, Charset.forName("UTF-8"))) {
|
||||||
|
pw.print("Hello .");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -23,21 +23,31 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
|
* @modules java.base/sun.net.www.protocol.file
|
||||||
* @bug 5052093
|
* @bug 5052093
|
||||||
* @modules java.base/sun.net.www java.base/sun.net.www.protocol.file
|
* @library /test/lib
|
||||||
* @library ../../../sun/net/www/httptest/
|
* @run main/othervm B5052093
|
||||||
* @build HttpCallback TestHttpServer ClosedChannelList HttpTransaction
|
|
||||||
* @run main B5052093
|
|
||||||
* @summary URLConnection doesn't support large files
|
* @summary URLConnection doesn't support large files
|
||||||
*/
|
*/
|
||||||
import java.net.*;
|
|
||||||
import java.io.*;
|
|
||||||
import sun.net.www.protocol.file.FileURLConnection;
|
|
||||||
import static java.net.Proxy.NO_PROXY;
|
|
||||||
|
|
||||||
public class B5052093 implements HttpCallback {
|
import java.io.File;
|
||||||
private static TestHttpServer server;
|
import java.io.IOException;
|
||||||
private static long testSize = ((long) (Integer.MAX_VALUE)) + 2;
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import static java.net.Proxy.NO_PROXY;
|
||||||
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
import sun.net.www.protocol.file.FileURLConnection;
|
||||||
|
|
||||||
|
public class B5052093 {
|
||||||
|
private static HttpServer server;
|
||||||
|
static long testSize = ((long) (Integer.MAX_VALUE)) + 2;
|
||||||
|
|
||||||
public static class LargeFile extends File {
|
public static class LargeFile extends File {
|
||||||
public LargeFile() {
|
public LargeFile() {
|
||||||
@ -55,20 +65,18 @@ public class B5052093 implements HttpCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void request(HttpTransaction req) {
|
|
||||||
try {
|
|
||||||
req.setResponseHeader("content-length", Long.toString(testSize));
|
|
||||||
req.sendResponse(200, "OK");
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
server = new TestHttpServer(new B5052093(), 1, 10, loopback, 0);
|
server = HttpServer.create(new InetSocketAddress(loopback, 0), 10, "/", new B5052093Handler());
|
||||||
|
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||||
|
server.start();
|
||||||
try {
|
try {
|
||||||
URL url = new URL("http://" + server.getAuthority() + "/foo");
|
URL url = URIBuilder.newBuilder()
|
||||||
|
.scheme("http")
|
||||||
|
.loopback()
|
||||||
|
.port(server.getAddress().getPort())
|
||||||
|
.path("/foo")
|
||||||
|
.build().toURL();
|
||||||
URLConnection conn = url.openConnection(NO_PROXY);
|
URLConnection conn = url.openConnection(NO_PROXY);
|
||||||
int i = conn.getContentLength();
|
int i = conn.getContentLength();
|
||||||
long l = conn.getContentLengthLong();
|
long l = conn.getContentLengthLong();
|
||||||
@ -89,7 +97,20 @@ public class B5052093 implements HttpCallback {
|
|||||||
throw new RuntimeException("Wrong content-length from file");
|
throw new RuntimeException("Wrong content-length from file");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B5052093Handler implements HttpHandler {
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
exchange.getResponseHeaders().set("content-length", Long.toString(B5052093.testSize));
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
exchange.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -24,53 +24,31 @@
|
|||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @bug 4804309
|
* @bug 4804309
|
||||||
* @modules java.base/sun.net.www
|
* @library /test/lib
|
||||||
* @library ../../../sun/net/www/httptest/
|
* @run main/othervm AuthHeaderTest
|
||||||
* @build HttpCallback TestHttpServer ClosedChannelList HttpTransaction
|
|
||||||
* @run main AuthHeaderTest
|
|
||||||
* @summary AuthHeaderTest bug
|
* @summary AuthHeaderTest bug
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.net.*;
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.Authenticator;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.PasswordAuthentication;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class AuthHeaderTest implements HttpCallback {
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import jdk.test.lib.net.URIBuilder;
|
||||||
|
|
||||||
static int count = 0;
|
public class AuthHeaderTest {
|
||||||
static String authstring;
|
static HttpServer server;
|
||||||
|
|
||||||
void errorReply (HttpTransaction req, String reply) throws IOException {
|
|
||||||
req.addResponseHeader ("Connection", "close");
|
|
||||||
req.addResponseHeader ("Www-authenticate", reply);
|
|
||||||
req.sendResponse (401, "Unauthorized");
|
|
||||||
req.orderlyClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void okReply (HttpTransaction req) throws IOException {
|
|
||||||
req.setResponseEntityBody ("Hello .");
|
|
||||||
req.sendResponse (200, "Ok");
|
|
||||||
req.orderlyClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void request (HttpTransaction req) {
|
|
||||||
try {
|
|
||||||
authstring = req.getRequestHeader ("Authorization");
|
|
||||||
System.out.println (authstring);
|
|
||||||
switch (count) {
|
|
||||||
case 0:
|
|
||||||
errorReply (req, "Basic realm=\"wallyworld\"");
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
/* client stores a username/pw for wallyworld
|
|
||||||
*/
|
|
||||||
okReply (req);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
count ++;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void read (InputStream is) throws IOException {
|
static void read (InputStream is) throws IOException {
|
||||||
int c;
|
int c;
|
||||||
@ -91,19 +69,27 @@ public class AuthHeaderTest implements HttpCallback {
|
|||||||
is.close();
|
is.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
static TestHttpServer server;
|
|
||||||
|
|
||||||
public static void main (String[] args) throws Exception {
|
public static void main (String[] args) throws Exception {
|
||||||
MyAuthenticator auth = new MyAuthenticator ();
|
MyAuthenticator auth = new MyAuthenticator ();
|
||||||
Authenticator.setDefault (auth);
|
Authenticator.setDefault (auth);
|
||||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||||
try {
|
try {
|
||||||
server = new TestHttpServer (new AuthHeaderTest(), 1, 10, loopback, 0);
|
server = HttpServer.create(new InetSocketAddress(loopback, 0), 10, "/", new AuthHeaderTestHandler());
|
||||||
System.out.println ("Server: listening on port: " + server.getAuthority());
|
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||||
client ("http://" + server.getAuthority() + "/d1/foo.html");
|
server.start();
|
||||||
|
System.out.println ("Server: listening on port: " + server.getAddress().getPort());
|
||||||
|
|
||||||
|
String serverURL = URIBuilder.newBuilder()
|
||||||
|
.scheme("http")
|
||||||
|
.loopback()
|
||||||
|
.port(server.getAddress().getPort())
|
||||||
|
.path("/")
|
||||||
|
.build()
|
||||||
|
.toString();
|
||||||
|
client (serverURL + "d1/foo.html");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -111,11 +97,11 @@ public class AuthHeaderTest implements HttpCallback {
|
|||||||
if (f != 1) {
|
if (f != 1) {
|
||||||
except ("Authenticator was called "+f+" times. Should be 1");
|
except ("Authenticator was called "+f+" times. Should be 1");
|
||||||
}
|
}
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void except (String s) {
|
public static void except (String s) {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
throw new RuntimeException (s);
|
throw new RuntimeException (s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,3 +124,45 @@ public class AuthHeaderTest implements HttpCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AuthHeaderTestHandler implements HttpHandler {
|
||||||
|
static int count = 0;
|
||||||
|
static String authstring;
|
||||||
|
|
||||||
|
void errorReply (HttpExchange req, String reply) throws IOException {
|
||||||
|
req.getResponseHeaders().set("Connection", "close");
|
||||||
|
req.getResponseHeaders().set("Www-authenticate", reply);
|
||||||
|
req.sendResponseHeaders(401, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void okReply (HttpExchange req) throws IOException {
|
||||||
|
req.sendResponseHeaders (200, 0);
|
||||||
|
try(PrintWriter pw = new PrintWriter(req.getResponseBody(), false, Charset.forName("UTF-8"))) {
|
||||||
|
pw.print("Hello .");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange exchange) throws IOException {
|
||||||
|
try {
|
||||||
|
if(exchange.getRequestHeaders().get("Authorization") != null) {
|
||||||
|
authstring = exchange.getRequestHeaders().get("Authorization").get(0);
|
||||||
|
System.out.println (authstring);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (count) {
|
||||||
|
case 0:
|
||||||
|
errorReply (exchange, "Basic realm=\"wallyworld\"");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* client stores a username/pw for wallyworld
|
||||||
|
*/
|
||||||
|
okReply (exchange);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count ++;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -24,19 +24,30 @@
|
|||||||
/*
|
/*
|
||||||
* @test
|
* @test
|
||||||
* @bug 5045306 6356004 6993490 8255124
|
* @bug 5045306 6356004 6993490 8255124
|
||||||
* @modules java.base/sun.net.www
|
* @library /test/lib
|
||||||
* java.management
|
|
||||||
* @library ../../httptest/
|
|
||||||
* @build HttpCallback TestHttpServer HttpTransaction
|
|
||||||
* @run main/othervm B5045306
|
* @run main/othervm B5045306
|
||||||
* @summary Http keep-alive implementation is not efficient
|
* @summary Http keep-alive implementation is not efficient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.net.*;
|
import java.io.IOException;
|
||||||
import java.io.*;
|
import java.io.InputStream;
|
||||||
import java.lang.management.*;
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.lang.management.ThreadInfo;
|
||||||
|
import java.lang.management.ThreadMXBean;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
|
||||||
/* Part 1:
|
/* Part 1:
|
||||||
* The http client makes a connection to a URL whos content contains a lot of
|
* The http client makes a connection to a URL whos content contains a lot of
|
||||||
@ -51,20 +62,19 @@ import java.util.List;
|
|||||||
* Content-length header.
|
* Content-length header.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class B5045306
|
public class B5045306 {
|
||||||
{
|
static HttpServer server;
|
||||||
static SimpleHttpTransaction httpTrans;
|
|
||||||
static TestHttpServer server;
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) {
|
||||||
startHttpServer();
|
startHttpServer();
|
||||||
clientHttpCalls();
|
clientHttpCalls();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startHttpServer() {
|
public static void startHttpServer() {
|
||||||
try {
|
try {
|
||||||
httpTrans = new SimpleHttpTransaction();
|
server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost(), 0), 10, "/", new SimpleHttpTransactionHandler());
|
||||||
server = new TestHttpServer(httpTrans, 1, 10, InetAddress.getLocalHost(), 0);
|
server.setExecutor(Executors.newSingleThreadExecutor());
|
||||||
|
server.start();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@ -76,10 +86,10 @@ public class B5045306
|
|||||||
uncaught.add(ex);
|
uncaught.add(ex);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
System.out.println("http server listen on: " + server.getLocalPort());
|
System.out.println("http server listen on: " + server.getAddress().getPort());
|
||||||
String hostAddr = InetAddress.getLocalHost().getHostAddress();
|
String hostAddr = InetAddress.getLocalHost().getHostAddress();
|
||||||
if (hostAddr.indexOf(':') > -1) hostAddr = "[" + hostAddr + "]";
|
if (hostAddr.indexOf(':') > -1) hostAddr = "[" + hostAddr + "]";
|
||||||
String baseURLStr = "http://" + hostAddr + ":" + server.getLocalPort() + "/";
|
String baseURLStr = "http://" + hostAddr + ":" + server.getAddress().getPort() + "/";
|
||||||
|
|
||||||
URL bigDataURL = new URL (baseURLStr + "firstCall");
|
URL bigDataURL = new URL (baseURLStr + "firstCall");
|
||||||
URL smallDataURL = new URL (baseURLStr + "secondCall");
|
URL smallDataURL = new URL (baseURLStr + "secondCall");
|
||||||
@ -98,7 +108,7 @@ public class B5045306
|
|||||||
uc = (HttpURLConnection)smallDataURL.openConnection(Proxy.NO_PROXY);
|
uc = (HttpURLConnection)smallDataURL.openConnection(Proxy.NO_PROXY);
|
||||||
uc.getResponseCode();
|
uc.getResponseCode();
|
||||||
|
|
||||||
if (SimpleHttpTransaction.failed)
|
if (SimpleHttpTransactionHandler.failed)
|
||||||
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
|
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
|
||||||
|
|
||||||
// Part 2
|
// Part 2
|
||||||
@ -137,7 +147,7 @@ public class B5045306
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
server.terminate();
|
server.stop(1);
|
||||||
}
|
}
|
||||||
if (!uncaught.isEmpty()) {
|
if (!uncaught.isEmpty()) {
|
||||||
throw new RuntimeException("Unhandled exception:", uncaught.get(0));
|
throw new RuntimeException("Unhandled exception:", uncaught.get(0));
|
||||||
@ -145,9 +155,9 @@ public class B5045306
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimpleHttpTransaction implements HttpCallback
|
class SimpleHttpTransactionHandler implements HttpHandler
|
||||||
{
|
{
|
||||||
static boolean failed = false;
|
static volatile boolean failed = false;
|
||||||
|
|
||||||
// Need to have enough data here that is too large for the socket buffer to hold.
|
// Need to have enough data here that is too large for the socket buffer to hold.
|
||||||
// Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
|
// Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
|
||||||
@ -155,48 +165,46 @@ class SimpleHttpTransaction implements HttpCallback
|
|||||||
|
|
||||||
int port1;
|
int port1;
|
||||||
|
|
||||||
public void request(HttpTransaction trans) {
|
public void handle(HttpExchange trans) {
|
||||||
try {
|
try {
|
||||||
String path = trans.getRequestURI().getPath();
|
String path = trans.getRequestURI().getPath();
|
||||||
if (path.equals("/firstCall")) {
|
if (path.equals("/firstCall")) {
|
||||||
port1 = trans.channel().socket().getPort();
|
port1 = trans.getLocalAddress().getPort();
|
||||||
System.out.println("First connection on client port = " + port1);
|
System.out.println("First connection on client port = " + port1);
|
||||||
|
|
||||||
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
|
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
|
||||||
for (int i=0; i<responseBody.length; i++)
|
for (int i=0; i<responseBody.length; i++)
|
||||||
responseBody[i] = 0x41;
|
responseBody[i] = 0x41;
|
||||||
trans.setResponseEntityBody (responseBody, responseBody.length);
|
trans.sendResponseHeaders(200, 0);
|
||||||
trans.sendResponse(200, "OK");
|
try(PrintWriter pw = new PrintWriter(trans.getResponseBody(), false, Charset.forName("UTF-8"))) {
|
||||||
|
pw.print(responseBody);
|
||||||
|
}
|
||||||
} else if (path.equals("/secondCall")) {
|
} else if (path.equals("/secondCall")) {
|
||||||
int port2 = trans.channel().socket().getPort();
|
int port2 = trans.getLocalAddress().getPort();
|
||||||
System.out.println("Second connection on client port = " + port2);
|
System.out.println("Second connection on client port = " + port2);
|
||||||
|
|
||||||
if (port1 != port2)
|
if (port1 != port2)
|
||||||
failed = true;
|
failed = true;
|
||||||
|
|
||||||
trans.setResponseHeader ("Content-length", Integer.toString(0));
|
|
||||||
|
|
||||||
/* Force the server to not respond for more that the timeout
|
/* Force the server to not respond for more that the timeout
|
||||||
* set by the keepalive cleaner (5000 millis). This ensures the
|
* set by the keepalive cleaner (5000 millis). This ensures the
|
||||||
* timeout is correctly resets the default read timeout,
|
* timeout is correctly resets the default read timeout,
|
||||||
* infinity. See 6993490. */
|
* infinity. See 6993490. */
|
||||||
System.out.println("server sleeping...");
|
System.out.println("server sleeping...");
|
||||||
try {Thread.sleep(6000); } catch (InterruptedException e) {}
|
try {Thread.sleep(6000); } catch (InterruptedException e) {}
|
||||||
|
trans.sendResponseHeaders(200, -1);
|
||||||
trans.sendResponse(200, "OK");
|
|
||||||
} else if(path.equals("/part2")) {
|
} else if(path.equals("/part2")) {
|
||||||
System.out.println("Call to /part2");
|
System.out.println("Call to /part2");
|
||||||
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
|
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
|
||||||
for (int i=0; i<responseBody.length; i++)
|
for (int i=0; i<responseBody.length; i++)
|
||||||
responseBody[i] = 0x41;
|
responseBody[i] = 0x41;
|
||||||
trans.setResponseEntityBody (responseBody, responseBody.length);
|
|
||||||
|
|
||||||
// override the Content-length header to be greater than the actual response body
|
// override the Content-length header to be greater than the actual response body
|
||||||
trans.setResponseHeader("Content-length", Integer.toString(responseBody.length+1));
|
trans.sendResponseHeaders(200, responseBody.length+1);
|
||||||
trans.sendResponse(200, "OK");
|
try(PrintWriter pw = new PrintWriter(trans.getResponseBody(), false, Charset.forName("UTF-8"))) {
|
||||||
|
pw.print(responseBody);
|
||||||
|
}
|
||||||
// now close the socket
|
// now close the socket
|
||||||
trans.channel().socket().close();
|
trans.close();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -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,77 +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.io.*;
|
|
||||||
import java.nio.channels.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
class ClosedChannelList {
|
|
||||||
|
|
||||||
static final long TIMEOUT = 10 * 1000; /* 10 sec */
|
|
||||||
|
|
||||||
static class Element {
|
|
||||||
long expiry;
|
|
||||||
SelectionKey key;
|
|
||||||
Element (long l, SelectionKey key) {
|
|
||||||
expiry = l;
|
|
||||||
this.key = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedList list;
|
|
||||||
|
|
||||||
public ClosedChannelList () {
|
|
||||||
list = new LinkedList ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* close chan after TIMEOUT milliseconds */
|
|
||||||
|
|
||||||
public synchronized void add (SelectionKey key) {
|
|
||||||
long exp = System.currentTimeMillis () + TIMEOUT;
|
|
||||||
list.add (new Element (exp, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void check () {
|
|
||||||
check (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void terminate () {
|
|
||||||
check (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void check (boolean forceClose) {
|
|
||||||
Iterator iter = list.iterator ();
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
while (iter.hasNext ()) {
|
|
||||||
Element elm = (Element)iter.next();
|
|
||||||
if (forceClose || elm.expiry <= now) {
|
|
||||||
SelectionKey k = elm.key;
|
|
||||||
try {
|
|
||||||
k.channel().close ();
|
|
||||||
} catch (IOException e) {}
|
|
||||||
k.cancel();
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
TestHttpServer.Server server;
|
|
||||||
MessageHeader reqheaders, reqtrailers;
|
|
||||||
String reqbody;
|
|
||||||
byte[] rspbody;
|
|
||||||
MessageHeader rspheaders, rsptrailers;
|
|
||||||
SelectionKey key;
|
|
||||||
int rspbodylen;
|
|
||||||
boolean rspchunked;
|
|
||||||
|
|
||||||
HttpTransaction (TestHttpServer.Server server, String command,
|
|
||||||
URI requesturi, MessageHeader headers,
|
|
||||||
String body, MessageHeader trailers, SelectionKey key) {
|
|
||||||
this.command = command;
|
|
||||||
this.requesturi = requesturi;
|
|
||||||
this.reqheaders = headers;
|
|
||||||
this.reqbody = body;
|
|
||||||
this.reqtrailers = trailers;
|
|
||||||
this.key = key;
|
|
||||||
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 (key.channel().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 (key);
|
|
||||||
} 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(key);
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.out.println (e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the SocketChannel associated with this request
|
|
||||||
*
|
|
||||||
* @return the socket channel
|
|
||||||
*/
|
|
||||||
public SocketChannel channel() {
|
|
||||||
return (SocketChannel) key.channel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 TestHttpServer.NioOutputStream(channel());
|
|
||||||
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 TestHttpServer.NioOutputStream(channel());
|
|
||||||
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,797 +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.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class implements a simple HTTP server. It uses multiple threads to
|
|
||||||
* handle connections in parallel, and also multiple connections/requests
|
|
||||||
* can be handled per thread.
|
|
||||||
* <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 changes are made here, please sure they are propagated to
|
|
||||||
* the HTTPS equivalent in the JSSE regression test suite.
|
|
||||||
*
|
|
||||||
* NOTE NOTE NOTE NOTE NOTE NOTE NOTE
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class TestHttpServer {
|
|
||||||
|
|
||||||
ServerSocketChannel schan;
|
|
||||||
int threads;
|
|
||||||
int cperthread;
|
|
||||||
HttpCallback cb;
|
|
||||||
Server[] servers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a <code>TestHttpServer<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 TestHttpServer (HttpCallback cb) throws IOException {
|
|
||||||
this (cb, 1, 10, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a <code>TestHttpServer<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
|
|
||||||
* @param address the address to bind the server to. <code>Null</code>
|
|
||||||
* means bind to the wildcard address.
|
|
||||||
* @param port the port number to bind the server to. <code>Zero</code>
|
|
||||||
* means choose any free port.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public TestHttpServer (HttpCallback cb, InetAddress address, int port) throws IOException {
|
|
||||||
this (cb, 1, 10, address, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a <code>TestHttpServer<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 TestHttpServer (HttpCallback cb, int threads, int cperthread)
|
|
||||||
throws IOException {
|
|
||||||
this (cb, threads, cperthread, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a <code>TestHttpServer<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 TestHttpServer (HttpCallback cb, int threads, int cperthread, int port)
|
|
||||||
throws IOException {
|
|
||||||
this(cb, threads, cperthread, null, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a <code>TestHttpServer<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 address to bind the server to. <code>Null</code>
|
|
||||||
* means bind to the wildcard address.
|
|
||||||
* @param port the port number to bind the server to. <code>Zero</code>
|
|
||||||
* means choose any free port.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public TestHttpServer (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;
|
|
||||||
servers = new Server [threads];
|
|
||||||
for (int i=0; i<threads; i++) {
|
|
||||||
servers[i] = new Server (cb, schan, cperthread);
|
|
||||||
servers[i].start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @throws InterruptedException
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void terminate () {
|
|
||||||
for (int i=0; i<threads; i++) {
|
|
||||||
servers[i].terminate ();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < threads; i++) {
|
|
||||||
try {
|
|
||||||
servers[i].join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
System.err.println("Unexpected InterruptedException during terminating server");
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
volatile 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);
|
|
||||||
throw new RuntimeException("Server could not start: " + e, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stop the thread as soon as possible */
|
|
||||||
public void terminate () {
|
|
||||||
shutdown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run () {
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
selector.select(1000);
|
|
||||||
Set<SelectionKey> selected = selector.selectedKeys();
|
|
||||||
Iterator<SelectionKey> iter = selected.iterator();
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
key = iter.next();
|
|
||||||
if (key.equals (listenerKey)) {
|
|
||||||
SocketChannel sock = schan.accept ();
|
|
||||||
if (sock == null) {
|
|
||||||
/* false notification */
|
|
||||||
iter.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sock.configureBlocking (false);
|
|
||||||
sock.register (selector, SelectionKey.OP_READ);
|
|
||||||
nconn ++;
|
|
||||||
System.out.println("SERVER: new connection. chan[" + sock + "]");
|
|
||||||
if (nconn == maxconn) {
|
|
||||||
/* deregister */
|
|
||||||
listenerKey.cancel ();
|
|
||||||
listenerKey = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (key.isReadable()) {
|
|
||||||
boolean closed;
|
|
||||||
SocketChannel chan = (SocketChannel) key.channel();
|
|
||||||
System.out.println("SERVER: connection readable. chan[" + chan + "]");
|
|
||||||
if (key.attachment() != null) {
|
|
||||||
System.out.println("Server: consume");
|
|
||||||
closed = consume (chan);
|
|
||||||
} else {
|
|
||||||
closed = read (chan, key);
|
|
||||||
}
|
|
||||||
if (closed) {
|
|
||||||
chan.close ();
|
|
||||||
key.cancel ();
|
|
||||||
if (nconn == maxconn) {
|
|
||||||
listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);
|
|
||||||
}
|
|
||||||
nconn --;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
clist.check();
|
|
||||||
if (shutdown) {
|
|
||||||
System.out.println("Force to Shutdown");
|
|
||||||
SelectionKey sKey = schan.keyFor(selector);
|
|
||||||
if (sKey != null) {
|
|
||||||
sKey.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
clist.terminate ();
|
|
||||||
selector.close();
|
|
||||||
schan.socket().close();
|
|
||||||
schan.close();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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));
|
|
||||||
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, key);
|
|
||||||
cb.request (msg);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
System.err.println ("Invalid URI: " + e);
|
|
||||||
msg = new HttpTransaction (this, cmd, null, null, null, null, key);
|
|
||||||
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 (SelectionKey key) throws IOException {
|
|
||||||
SocketChannel ch = (SocketChannel)key.channel ();
|
|
||||||
System.out.println("SERVER: orderlyCloseChannel chan[" + ch + "]");
|
|
||||||
ch.socket().shutdownOutput();
|
|
||||||
key.attach (this);
|
|
||||||
clist.add (key);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void abortiveCloseChannel (SelectionKey key) throws IOException {
|
|
||||||
SocketChannel ch = (SocketChannel)key.channel ();
|
|
||||||
System.out.println("SERVER: abortiveCloseChannel chan[" + ch + "]");
|
|
||||||
|
|
||||||
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 {
|
|
||||||
SocketChannel channel;
|
|
||||||
Selector selector;
|
|
||||||
ByteBuffer chanbuf;
|
|
||||||
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) throws IOException {
|
|
||||||
this.channel = chan;
|
|
||||||
selector = Selector.open();
|
|
||||||
chanbuf = ByteBuffer.allocate (1024);
|
|
||||||
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;
|
|
||||||
chanbuf.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;
|
|
||||||
|
|
||||||
chanbuf.clear ();
|
|
||||||
available = channel.read (chanbuf);
|
|
||||||
if (available > 0)
|
|
||||||
chanbuf.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 {
|
|
||||||
SocketChannel channel;
|
|
||||||
ByteBuffer buf;
|
|
||||||
SelectionKey key;
|
|
||||||
Selector selector;
|
|
||||||
boolean closed;
|
|
||||||
byte[] one;
|
|
||||||
|
|
||||||
public NioOutputStream (SocketChannel channel) throws IOException {
|
|
||||||
this.channel = channel;
|
|
||||||
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");
|
|
||||||
|
|
||||||
buf = ByteBuffer.allocate (len);
|
|
||||||
buf.put (b, off, len);
|
|
||||||
buf.flip ();
|
|
||||||
int n;
|
|
||||||
while ((n = channel.write (buf)) < len) {
|
|
||||||
len -= n;
|
|
||||||
if (len == 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,12 +32,22 @@
|
|||||||
* system properties in samevm/agentvm mode.
|
* system properties in samevm/agentvm mode.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
import java.net.*;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.URL;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import javax.net.*;
|
|
||||||
import javax.net.ssl.*;
|
import javax.net.ServerSocketFactory;
|
||||||
import java.security.cert.*;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ClientHelloRead.java -- includes a simple server that can serve
|
* ClientHelloRead.java -- includes a simple server that can serve
|
||||||
|
Loading…
x
Reference in New Issue
Block a user