8087124: HTTP/2 implementation
Reviewed-by: chegar
This commit is contained in:
parent
e955660cf1
commit
bab1d3912a
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implemented by classes that offer an asynchronous interface.
|
||||||
|
*
|
||||||
|
* PlainHttpConnection, AsyncSSLConnection AsyncSSLDelegate.
|
||||||
|
*
|
||||||
|
* setAsyncCallbacks() is called to set the callback for reading
|
||||||
|
* and error notification. Reads all happen on the selector thread, which
|
||||||
|
* must not block.
|
||||||
|
*
|
||||||
|
* Writing uses the same write() methods as used in blocking mode.
|
||||||
|
* Queues are employed on the writing side to buffer data while it is waiting
|
||||||
|
* to be sent. This strategy relies on HTTP/2 protocol flow control to stop
|
||||||
|
* outgoing queue from continually growing. Writes can be initiated by the
|
||||||
|
* calling thread, but if socket becomes full then the queue is emptied by
|
||||||
|
* the selector thread
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface AsyncConnection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables asynchronous sending and receiving mode. The given async
|
||||||
|
* receiver will receive all incoming data. asyncInput() will be called
|
||||||
|
* to trigger reads. asyncOutput() will be called to drive writes.
|
||||||
|
*
|
||||||
|
* The errorReceiver callback must be called when any fatal exception
|
||||||
|
* occurs. Connection is assumed to be closed afterwards.
|
||||||
|
*
|
||||||
|
* @param asyncReceiver
|
||||||
|
* @param errorReceiver
|
||||||
|
*/
|
||||||
|
void setAsyncCallbacks(
|
||||||
|
Consumer<ByteBuffer> asyncReceiver,
|
||||||
|
Consumer<Throwable> errorReceiver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does whatever is required to start reading. Usually registers
|
||||||
|
* an event with the selector thread.
|
||||||
|
*/
|
||||||
|
void startReading();
|
||||||
|
}
|
@ -25,24 +25,27 @@
|
|||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handling interface from HttpClientImpl's selector.
|
* Event handling interface from HttpClientImpl's selector.
|
||||||
*
|
*
|
||||||
* <p> If blockingChannel is true, then the channel will be put in blocking
|
* If BLOCKING is set, then the channel will be put in blocking
|
||||||
* mode prior to handle() being called. If false, then it remains non-blocking.
|
* mode prior to handle() being called. If false, then it remains non-blocking.
|
||||||
|
*
|
||||||
|
* If REPEATING is set then the event is not cancelled after being posted.
|
||||||
*/
|
*/
|
||||||
abstract class AsyncEvent {
|
abstract class AsyncEvent {
|
||||||
|
|
||||||
/**
|
public static final int BLOCKING = 0x1; // non blocking if not set
|
||||||
* Implement this if channel should be made blocking before calling handle()
|
public static final int REPEATING = 0x2; // one off event if not set
|
||||||
*/
|
|
||||||
public interface Blocking { }
|
|
||||||
|
|
||||||
/**
|
protected final int flags;
|
||||||
* Implement this if channel should remain non-blocking before calling handle()
|
|
||||||
*/
|
AsyncEvent(int flags) {
|
||||||
public interface NonBlocking { }
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the channel */
|
/** Returns the channel */
|
||||||
public abstract SelectableChannel channel();
|
public abstract SelectableChannel channel();
|
||||||
@ -55,4 +58,12 @@ abstract class AsyncEvent {
|
|||||||
|
|
||||||
/** Called when selector is shutting down. Abort all exchanges. */
|
/** Called when selector is shutting down. Abort all exchanges. */
|
||||||
public abstract void abort();
|
public abstract void abort();
|
||||||
|
|
||||||
|
public boolean blocking() {
|
||||||
|
return (flags & BLOCKING) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean repeating() {
|
||||||
|
return (flags & REPEATING) != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous version of SSLConnection.
|
||||||
|
*/
|
||||||
|
class AsyncSSLConnection extends HttpConnection implements AsyncConnection {
|
||||||
|
final AsyncSSLDelegate sslDelegate;
|
||||||
|
final PlainHttpConnection delegate;
|
||||||
|
|
||||||
|
AsyncSSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) {
|
||||||
|
super(addr, client);
|
||||||
|
delegate = new PlainHttpConnection(addr, client);
|
||||||
|
sslDelegate = new AsyncSSLDelegate(delegate, client, ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect() throws IOException, InterruptedException {
|
||||||
|
delegate.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> connectAsync() {
|
||||||
|
return delegate.connectAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean connected() {
|
||||||
|
return delegate.connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isSecure() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isProxied() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SocketChannel channel() {
|
||||||
|
return delegate.channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ConnectionPool.CacheKey cacheKey() {
|
||||||
|
return ConnectionPool.cacheKey(address, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
synchronized long write(ByteBuffer[] buffers, int start, int number) throws IOException {
|
||||||
|
ByteBuffer[] bufs = Utils.reduce(buffers, start, number);
|
||||||
|
long n = Utils.remaining(bufs);
|
||||||
|
sslDelegate.write(bufs);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
long write(ByteBuffer buffer) throws IOException {
|
||||||
|
long n = buffer.remaining();
|
||||||
|
sslDelegate.write(buffer);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
Utils.close(sslDelegate, delegate.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsyncCallbacks(Consumer<ByteBuffer> asyncReceiver, Consumer<Throwable> errorReceiver) {
|
||||||
|
sslDelegate.setAsyncCallbacks(asyncReceiver, errorReceiver);
|
||||||
|
delegate.setAsyncCallbacks(sslDelegate::lowerRead, errorReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocking read functions not used here
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ByteBuffer readImpl(int length) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int readImpl(ByteBuffer buffer) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompletableFuture<Void> whenReceivingResponse() {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startReading() {
|
||||||
|
delegate.startReading();
|
||||||
|
sslDelegate.startReading();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,598 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import static javax.net.ssl.SSLEngineResult.Status.*;
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous wrapper around SSLEngine. send and receive is fully non
|
||||||
|
* blocking. When handshaking is required, a thread is created to perform
|
||||||
|
* the handshake and application level sends do not take place during this time.
|
||||||
|
*
|
||||||
|
* Is implemented using queues and functions operating on the receiving end
|
||||||
|
* of each queue.
|
||||||
|
*
|
||||||
|
* Application writes to:
|
||||||
|
* ||
|
||||||
|
* \/
|
||||||
|
* appOutputQ
|
||||||
|
* ||
|
||||||
|
* \/
|
||||||
|
* appOutputQ read by "upperWrite" method which does SSLEngine.wrap
|
||||||
|
* and writes to
|
||||||
|
* ||
|
||||||
|
* \/
|
||||||
|
* channelOutputQ
|
||||||
|
* ||
|
||||||
|
* \/
|
||||||
|
* channelOutputQ is read by "lowerWrite" method which is invoked from
|
||||||
|
* OP_WRITE events on the socket (from selector thread)
|
||||||
|
*
|
||||||
|
* Reading side is as follows
|
||||||
|
* --------------------------
|
||||||
|
*
|
||||||
|
* "upperRead" method reads off channelInputQ and calls SSLEngine.unwrap and
|
||||||
|
* when decrypted data is returned, it is passed to the user's Consumer<ByteBuffer>
|
||||||
|
* /\
|
||||||
|
* ||
|
||||||
|
* channelInputQ
|
||||||
|
* /\
|
||||||
|
* ||
|
||||||
|
* "lowerRead" method puts buffers into channelInputQ. It is invoked from
|
||||||
|
* OP_READ events from the selector.
|
||||||
|
*
|
||||||
|
* Whenever handshaking is required, the doHandshaking() method is called
|
||||||
|
* which creates a thread to complete the handshake. It takes over the
|
||||||
|
* channelInputQ from upperRead, and puts outgoing packets on channelOutputQ.
|
||||||
|
* Selector events are delivered to lowerRead and lowerWrite as normal.
|
||||||
|
*
|
||||||
|
* Errors
|
||||||
|
*
|
||||||
|
* Any exception thrown by the engine or channel, causes all Queues to be closed
|
||||||
|
* the channel to be closed, and the error is reported to the user's
|
||||||
|
* Consumer<Throwable>
|
||||||
|
*/
|
||||||
|
public class AsyncSSLDelegate implements Closeable, AsyncConnection {
|
||||||
|
|
||||||
|
// outgoing buffers put in this queue first and may remain here
|
||||||
|
// while SSL handshaking happening.
|
||||||
|
final Queue<ByteBuffer> appOutputQ;
|
||||||
|
|
||||||
|
// queue of wrapped ByteBuffers waiting to be sent on socket channel
|
||||||
|
//final Queue<ByteBuffer> channelOutputQ;
|
||||||
|
|
||||||
|
// Bytes read into this queue before being unwrapped. Backup on this
|
||||||
|
// Q should only happen when the engine is stalled due to delegated tasks
|
||||||
|
final Queue<ByteBuffer> channelInputQ;
|
||||||
|
|
||||||
|
// input occurs through the read() method which is expected to be called
|
||||||
|
// when the selector signals some data is waiting to be read. All incoming
|
||||||
|
// handshake data is handled in this method, which means some calls to
|
||||||
|
// read() may return zero bytes of user data. This is not a sign of spinning,
|
||||||
|
// just that the handshake mechanics are being executed.
|
||||||
|
|
||||||
|
final SSLEngine engine;
|
||||||
|
final SSLParameters sslParameters;
|
||||||
|
//final SocketChannel chan;
|
||||||
|
final HttpConnection lowerOutput;
|
||||||
|
final HttpClientImpl client;
|
||||||
|
final ExecutorService executor;
|
||||||
|
final BufferHandler bufPool;
|
||||||
|
Consumer<ByteBuffer> receiver;
|
||||||
|
Consumer<Throwable> errorHandler;
|
||||||
|
// Locks.
|
||||||
|
final Object reader = new Object();
|
||||||
|
final Object writer = new Object();
|
||||||
|
// synchronizing handshake state
|
||||||
|
final Object handshaker = new Object();
|
||||||
|
// flag set when reader or writer is blocked waiting for handshake to finish
|
||||||
|
boolean writerBlocked;
|
||||||
|
boolean readerBlocked;
|
||||||
|
|
||||||
|
// some thread is currently doing the handshake
|
||||||
|
boolean handshaking;
|
||||||
|
|
||||||
|
// alpn[] may be null. upcall is callback which receives incoming decoded bytes off socket
|
||||||
|
|
||||||
|
AsyncSSLDelegate(HttpConnection lowerOutput, HttpClientImpl client, String[] alpn)
|
||||||
|
{
|
||||||
|
SSLContext context = client.sslContext();
|
||||||
|
executor = client.executorService();
|
||||||
|
bufPool = client;
|
||||||
|
appOutputQ = new Queue<>();
|
||||||
|
appOutputQ.registerPutCallback(this::upperWrite);
|
||||||
|
//channelOutputQ = new Queue<>();
|
||||||
|
//channelOutputQ.registerPutCallback(this::lowerWrite);
|
||||||
|
engine = context.createSSLEngine();
|
||||||
|
engine.setUseClientMode(true);
|
||||||
|
SSLParameters sslp = client.sslParameters().orElse(null);
|
||||||
|
if (sslp == null) {
|
||||||
|
sslp = context.getSupportedSSLParameters();
|
||||||
|
//sslp = context.getDefaultSSLParameters();
|
||||||
|
//printParams(sslp);
|
||||||
|
}
|
||||||
|
sslParameters = Utils.copySSLParameters(sslp);
|
||||||
|
if (alpn != null) {
|
||||||
|
sslParameters.setApplicationProtocols(alpn);
|
||||||
|
Log.logSSL("Setting application protocols: " + Arrays.toString(alpn));
|
||||||
|
} else {
|
||||||
|
Log.logSSL("No application protocols proposed");
|
||||||
|
}
|
||||||
|
engine.setSSLParameters(sslParameters);
|
||||||
|
engine.setEnabledCipherSuites(sslp.getCipherSuites());
|
||||||
|
engine.setEnabledProtocols(sslp.getProtocols());
|
||||||
|
this.lowerOutput = lowerOutput;
|
||||||
|
this.client = client;
|
||||||
|
this.channelInputQ = new Queue<>();
|
||||||
|
this.channelInputQ.registerPutCallback(this::upperRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put buffers to appOutputQ, and call upperWrite() if q was empty.
|
||||||
|
*
|
||||||
|
* @param src
|
||||||
|
*/
|
||||||
|
public void write(ByteBuffer[] src) throws IOException {
|
||||||
|
appOutputQ.putAll(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(ByteBuffer buf) throws IOException {
|
||||||
|
ByteBuffer[] a = new ByteBuffer[1];
|
||||||
|
a[0] = buf;
|
||||||
|
write(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
Utils.close(appOutputQ, channelInputQ, lowerOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to wrap buffers from appOutputQ and place them on the
|
||||||
|
* channelOutputQ for writing. If handshaking is happening, then the
|
||||||
|
* process stalls and last buffers taken off the appOutputQ are put back
|
||||||
|
* into it until handshaking completes.
|
||||||
|
*
|
||||||
|
* This same method is called to try and resume output after a blocking
|
||||||
|
* handshaking operation has completed.
|
||||||
|
*/
|
||||||
|
private void upperWrite() {
|
||||||
|
try {
|
||||||
|
EngineResult r = null;
|
||||||
|
ByteBuffer[] buffers = appOutputQ.pollAll(Utils.EMPTY_BB_ARRAY);
|
||||||
|
int bytes = Utils.remaining(buffers);
|
||||||
|
while (bytes > 0) {
|
||||||
|
synchronized (writer) {
|
||||||
|
r = wrapBuffers(buffers);
|
||||||
|
int bytesProduced = r.bytesProduced();
|
||||||
|
int bytesConsumed = r.bytesConsumed();
|
||||||
|
bytes -= bytesConsumed;
|
||||||
|
if (bytesProduced > 0) {
|
||||||
|
// pass destination buffer to channelOutputQ.
|
||||||
|
lowerOutput.write(r.destBuffer);
|
||||||
|
}
|
||||||
|
synchronized (handshaker) {
|
||||||
|
if (r.handshaking()) {
|
||||||
|
// handshaking is happening or is needed
|
||||||
|
// so we put the buffers back on Q to process again
|
||||||
|
// later. It's possible that some may have already
|
||||||
|
// been processed, which is ok.
|
||||||
|
appOutputQ.pushbackAll(buffers);
|
||||||
|
writerBlocked = true;
|
||||||
|
if (!handshaking()) {
|
||||||
|
// execute the handshake in another thread.
|
||||||
|
// This method will be called again to resume sending
|
||||||
|
// later
|
||||||
|
doHandshake(r);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnBuffers(buffers);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doHandshake(EngineResult r) {
|
||||||
|
handshaking = true;
|
||||||
|
channelInputQ.registerPutCallback(null);
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
doHandshakeImpl(r);
|
||||||
|
channelInputQ.registerPutCallback(this::upperRead);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnBuffers(ByteBuffer[] bufs) {
|
||||||
|
for (ByteBuffer buf : bufs)
|
||||||
|
client.returnBuffer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if some thread is currently doing the handshake
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean handshaking() {
|
||||||
|
synchronized(handshaker) {
|
||||||
|
return handshaking;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes entire handshake in calling thread.
|
||||||
|
* Returns after handshake is completed or error occurs
|
||||||
|
* @param r
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void doHandshakeImpl(EngineResult r) throws IOException {
|
||||||
|
while (true) {
|
||||||
|
SSLEngineResult.HandshakeStatus status = r.handshakeStatus();
|
||||||
|
if (status == NEED_TASK) {
|
||||||
|
LinkedList<Runnable> tasks = obtainTasks();
|
||||||
|
for (Runnable task : tasks)
|
||||||
|
task.run();
|
||||||
|
r = handshakeWrapAndSend();
|
||||||
|
} else if (status == NEED_WRAP) {
|
||||||
|
r = handshakeWrapAndSend();
|
||||||
|
} else if (status == NEED_UNWRAP) {
|
||||||
|
r = handshakeReceiveAndUnWrap();
|
||||||
|
}
|
||||||
|
if (!r.handshaking())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
boolean dowrite = false;
|
||||||
|
boolean doread = false;
|
||||||
|
// Handshake is finished. Now resume reading and/or writing
|
||||||
|
synchronized(handshaker) {
|
||||||
|
handshaking = false;
|
||||||
|
if (writerBlocked) {
|
||||||
|
writerBlocked = false;
|
||||||
|
dowrite = true;
|
||||||
|
}
|
||||||
|
if (readerBlocked) {
|
||||||
|
readerBlocked = false;
|
||||||
|
doread = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dowrite)
|
||||||
|
upperWrite();
|
||||||
|
if (doread)
|
||||||
|
upperRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
// acknowledge a received CLOSE request from peer
|
||||||
|
void doClosure() throws IOException {
|
||||||
|
//while (!wrapAndSend(emptyArray))
|
||||||
|
//;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedList<Runnable> obtainTasks() {
|
||||||
|
LinkedList<Runnable> l = new LinkedList<>();
|
||||||
|
Runnable r;
|
||||||
|
while ((r = engine.getDelegatedTask()) != null)
|
||||||
|
l.add(r);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setAsyncCallbacks(Consumer<ByteBuffer> asyncReceiver, Consumer<Throwable> errorReceiver) {
|
||||||
|
this.receiver = asyncReceiver;
|
||||||
|
this.errorHandler = errorReceiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startReading() {
|
||||||
|
// maybe this class does not need to implement AsyncConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
static class EngineResult {
|
||||||
|
ByteBuffer destBuffer;
|
||||||
|
ByteBuffer srcBuffer;
|
||||||
|
SSLEngineResult result;
|
||||||
|
Throwable t;
|
||||||
|
|
||||||
|
boolean handshaking() {
|
||||||
|
SSLEngineResult.HandshakeStatus s = result.getHandshakeStatus();
|
||||||
|
return s != FINISHED && s != NOT_HANDSHAKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesConsumed() {
|
||||||
|
return result.bytesConsumed();
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesProduced() {
|
||||||
|
return result.bytesProduced();
|
||||||
|
}
|
||||||
|
|
||||||
|
Throwable exception() {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLEngineResult.HandshakeStatus handshakeStatus() {
|
||||||
|
return result.getHandshakeStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLEngineResult.Status status() {
|
||||||
|
return result.getStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineResult handshakeWrapAndSend() throws IOException {
|
||||||
|
EngineResult r = wrapBuffer(Utils.EMPTY_BYTEBUFFER);
|
||||||
|
if (r.bytesProduced() > 0) {
|
||||||
|
lowerOutput.write(r.destBuffer);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// called during handshaking. It blocks until a complete packet
|
||||||
|
// is available, unwraps it and returns.
|
||||||
|
EngineResult handshakeReceiveAndUnWrap() throws IOException {
|
||||||
|
ByteBuffer buf = channelInputQ.take();
|
||||||
|
while (true) {
|
||||||
|
// block waiting for input
|
||||||
|
EngineResult r = unwrapBuffer(buf);
|
||||||
|
SSLEngineResult.Status status = r.status();
|
||||||
|
if (status == BUFFER_UNDERFLOW) {
|
||||||
|
// wait for another buffer to arrive
|
||||||
|
ByteBuffer buf1 = channelInputQ.take();
|
||||||
|
buf = combine (buf, buf1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// OK
|
||||||
|
// theoretically possible we could receive some user data
|
||||||
|
if (r.bytesProduced() > 0) {
|
||||||
|
receiver.accept(r.destBuffer);
|
||||||
|
}
|
||||||
|
if (!buf.hasRemaining())
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineResult wrapBuffer(ByteBuffer src) throws SSLException {
|
||||||
|
ByteBuffer[] bufs = new ByteBuffer[1];
|
||||||
|
bufs[0] = src;
|
||||||
|
return wrapBuffers(bufs);
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineResult wrapBuffers(ByteBuffer[] src) throws SSLException {
|
||||||
|
EngineResult r = new EngineResult();
|
||||||
|
ByteBuffer dst = bufPool.getBuffer();
|
||||||
|
while (true) {
|
||||||
|
r.result = engine.wrap(src, dst);
|
||||||
|
switch (r.result.getStatus()) {
|
||||||
|
case BUFFER_OVERFLOW:
|
||||||
|
dst = getPacketBuffer();
|
||||||
|
break;
|
||||||
|
case CLOSED:
|
||||||
|
case OK:
|
||||||
|
dst.flip();
|
||||||
|
r.destBuffer = dst;
|
||||||
|
return r;
|
||||||
|
case BUFFER_UNDERFLOW:
|
||||||
|
// underflow handled externally
|
||||||
|
bufPool.returnBuffer(dst);
|
||||||
|
return r;
|
||||||
|
default:
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EngineResult unwrapBuffer(ByteBuffer srcbuf) throws IOException {
|
||||||
|
EngineResult r = new EngineResult();
|
||||||
|
r.srcBuffer = srcbuf;
|
||||||
|
|
||||||
|
ByteBuffer dst = bufPool.getBuffer();
|
||||||
|
while (true) {
|
||||||
|
r.result = engine.unwrap(srcbuf, dst);
|
||||||
|
switch (r.result.getStatus()) {
|
||||||
|
case BUFFER_OVERFLOW:
|
||||||
|
// dest buffer not big enough. Reallocate
|
||||||
|
int oldcap = dst.capacity();
|
||||||
|
dst = getApplicationBuffer();
|
||||||
|
assert dst.capacity() > oldcap;
|
||||||
|
break;
|
||||||
|
case CLOSED:
|
||||||
|
doClosure();
|
||||||
|
throw new IOException("Engine closed");
|
||||||
|
case BUFFER_UNDERFLOW:
|
||||||
|
bufPool.returnBuffer(dst);
|
||||||
|
return r;
|
||||||
|
case OK:
|
||||||
|
dst.flip();
|
||||||
|
r.destBuffer = dst;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous read input. Call this when selector fires.
|
||||||
|
* Unwrap done in upperRead because it also happens in
|
||||||
|
* doHandshake() when handshake taking place
|
||||||
|
*/
|
||||||
|
public void lowerRead(ByteBuffer buffer) {
|
||||||
|
try {
|
||||||
|
channelInputQ.put(buffer);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
close();
|
||||||
|
errorHandler.accept(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void upperRead() {
|
||||||
|
EngineResult r;
|
||||||
|
ByteBuffer srcbuf;
|
||||||
|
synchronized (reader) {
|
||||||
|
try {
|
||||||
|
srcbuf = channelInputQ.poll();
|
||||||
|
if (srcbuf == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
r = unwrapBuffer(srcbuf);
|
||||||
|
switch (r.result.getStatus()) {
|
||||||
|
case BUFFER_UNDERFLOW:
|
||||||
|
// Buffer too small. Need to combine with next buf
|
||||||
|
ByteBuffer nextBuf = channelInputQ.poll();
|
||||||
|
if (nextBuf == null) {
|
||||||
|
// no data available. push buffer back until more data available
|
||||||
|
channelInputQ.pushback(srcbuf);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
srcbuf = combine(srcbuf, nextBuf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OK:
|
||||||
|
// check for any handshaking work
|
||||||
|
synchronized (handshaker) {
|
||||||
|
if (r.handshaking()) {
|
||||||
|
// handshaking is happening or is needed
|
||||||
|
// so we put the buffer back on Q to process again
|
||||||
|
// later.
|
||||||
|
channelInputQ.pushback(srcbuf);
|
||||||
|
readerBlocked = true;
|
||||||
|
if (!handshaking()) {
|
||||||
|
// execute the handshake in another thread.
|
||||||
|
// This method will be called again to resume sending
|
||||||
|
// later
|
||||||
|
doHandshake(r);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ByteBuffer dst = r.destBuffer;
|
||||||
|
if (dst.hasRemaining()) {
|
||||||
|
receiver.accept(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (srcbuf.hasRemaining()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
srcbuf = channelInputQ.poll();
|
||||||
|
if (srcbuf == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Utils.close(lowerOutput);
|
||||||
|
errorHandler.accept(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new buffer that is the right size for application buffers.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ByteBuffer getApplicationBuffer() {
|
||||||
|
SSLSession session = engine.getSession();
|
||||||
|
int appBufsize = session.getApplicationBufferSize();
|
||||||
|
bufPool.setMinBufferSize(appBufsize);
|
||||||
|
return bufPool.getBuffer(appBufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer getPacketBuffer() {
|
||||||
|
SSLSession session = engine.getSession();
|
||||||
|
int packetBufSize = session.getPacketBufferSize();
|
||||||
|
bufPool.setMinBufferSize(packetBufSize);
|
||||||
|
return bufPool.getBuffer(packetBufSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer combine(ByteBuffer buf1, ByteBuffer buf2) {
|
||||||
|
int avail1 = buf1.capacity() - buf1.remaining();
|
||||||
|
if (buf2.remaining() < avail1) {
|
||||||
|
buf1.compact();
|
||||||
|
buf1.put(buf2);
|
||||||
|
buf1.flip();
|
||||||
|
return buf1;
|
||||||
|
}
|
||||||
|
int newsize = buf1.remaining() + buf2.remaining();
|
||||||
|
ByteBuffer newbuf = bufPool.getBuffer(newsize);
|
||||||
|
newbuf.put(buf1);
|
||||||
|
newbuf.put(buf2);
|
||||||
|
newbuf.flip();
|
||||||
|
return newbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLParameters getSSLParameters() {
|
||||||
|
return sslParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printParams(SSLParameters p) {
|
||||||
|
System.out.println("SSLParameters:");
|
||||||
|
if (p == null) {
|
||||||
|
System.out.println("Null params");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String cipher : p.getCipherSuites()) {
|
||||||
|
System.out.printf("cipher: %s\n", cipher);
|
||||||
|
}
|
||||||
|
for (String approto : p.getApplicationProtocols()) {
|
||||||
|
System.out.printf("application protocol: %s\n", approto);
|
||||||
|
}
|
||||||
|
for (String protocol : p.getProtocols()) {
|
||||||
|
System.out.printf("protocol: %s\n", protocol);
|
||||||
|
}
|
||||||
|
if (p.getServerNames() != null)
|
||||||
|
for (SNIServerName sname : p.getServerNames()) {
|
||||||
|
System.out.printf("server name: %s\n", sname.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSessionInfo() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String application = engine.getApplicationProtocol();
|
||||||
|
SSLSession sess = engine.getSession();
|
||||||
|
String cipher = sess.getCipherSuite();
|
||||||
|
String protocol = sess.getProtocol();
|
||||||
|
sb.append("Handshake complete alpn: ")
|
||||||
|
.append(application)
|
||||||
|
.append(", Cipher: ")
|
||||||
|
.append(cipher)
|
||||||
|
.append(", Protocol: ")
|
||||||
|
.append(protocol);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -45,7 +45,7 @@ class AuthenticationFilter implements HeaderFilter {
|
|||||||
static final int DEFAULT_RETRY_LIMIT = 3;
|
static final int DEFAULT_RETRY_LIMIT = 3;
|
||||||
|
|
||||||
static final int retry_limit = Utils.getIntegerNetProperty(
|
static final int retry_limit = Utils.getIntegerNetProperty(
|
||||||
"sun.net.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT);
|
"java.net.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT);
|
||||||
|
|
||||||
static final int UNAUTHORIZED = 401;
|
static final int UNAUTHORIZED = 401;
|
||||||
static final int PROXY_UNAUTHORIZED = 407;
|
static final int PROXY_UNAUTHORIZED = 407;
|
||||||
|
@ -27,11 +27,23 @@ package java.net.http;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented by buffer pools.
|
* Implemented by buffer pools. A buffer pool has a current buffer size
|
||||||
|
* (number of bytes in each buffer) which may increase over time.
|
||||||
*/
|
*/
|
||||||
interface BufferHandler {
|
interface BufferHandler {
|
||||||
|
|
||||||
ByteBuffer getBuffer();
|
default ByteBuffer getBuffer() {
|
||||||
|
return getBuffer(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMinBufferSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* size == -1 means return any sized buffer. Any other value means
|
||||||
|
* @param size
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ByteBuffer getBuffer(int size);
|
||||||
|
|
||||||
void returnBuffer(ByteBuffer buffer);
|
void returnBuffer(ByteBuffer buffer);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a List<ByteBuffer> which is assumed to contain at least one HTTP/2
|
||||||
|
* frame and allows it to be processed supplying bytes, ints, shorts, byte[] etc.
|
||||||
|
* from the list. As each ByteBuffer is consumed it is removed from the List<>.
|
||||||
|
*
|
||||||
|
* NOTE. shorts and bytes returned are UNSIGNED ints
|
||||||
|
*
|
||||||
|
* When finished processing the frame, the List may be empty or may contain
|
||||||
|
* partially read or unread ByteBuffers. A new ByteBufferConsumer can be
|
||||||
|
* created with the List<>
|
||||||
|
*/
|
||||||
|
class ByteBufferConsumer {
|
||||||
|
|
||||||
|
ByteBuffer currentBuffer;
|
||||||
|
|
||||||
|
final List<ByteBuffer> buffers;
|
||||||
|
final ListIterator<ByteBuffer> iterator;
|
||||||
|
final Supplier<ByteBuffer> newBufferSupplier;
|
||||||
|
|
||||||
|
ByteBufferConsumer(List<ByteBuffer> buffers,
|
||||||
|
Supplier<ByteBuffer> newBufferSupplier) {
|
||||||
|
this.buffers = buffers;
|
||||||
|
this.newBufferSupplier = newBufferSupplier;
|
||||||
|
this.iterator = buffers.listIterator();
|
||||||
|
if (!iterator.hasNext()) {
|
||||||
|
throw new IllegalArgumentException("Empty buffer list");
|
||||||
|
}
|
||||||
|
currentBuffer = iterator.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dump() {
|
||||||
|
int l = 0;
|
||||||
|
System.err.printf("ByteBufferConsumer:\n");
|
||||||
|
for (ByteBuffer buf : buffers) {
|
||||||
|
System.err.printf("\t%s\n", buf.toString());
|
||||||
|
l+= buf.remaining();
|
||||||
|
}
|
||||||
|
System.err.printf("BBC contains %d bytes\n", l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized ByteBuffer getBuffer(boolean exception) throws IOException {
|
||||||
|
while (currentBuffer == null || !currentBuffer.hasRemaining()) {
|
||||||
|
if (currentBuffer != null) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
if (!iterator.hasNext()) {
|
||||||
|
currentBuffer = null;
|
||||||
|
if (exception) {
|
||||||
|
throw new IOException ("Connection closed unexpectedly");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
currentBuffer = iterator.next();
|
||||||
|
}
|
||||||
|
return currentBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// call this to check if the data has all been consumed
|
||||||
|
|
||||||
|
public boolean consumed() {
|
||||||
|
try {
|
||||||
|
return getBuffer(false) == null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
/* CAN'T HAPPEN */
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getByte() throws IOException {
|
||||||
|
// TODO: what to do if connection is closed. Throw NPE?
|
||||||
|
ByteBuffer buf = getBuffer(true);
|
||||||
|
return buf.get() & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes(int n) throws IOException {
|
||||||
|
return getBytes(n, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBytes(int n, byte[] buf) throws IOException {
|
||||||
|
if (buf == null) {
|
||||||
|
buf = new byte[n];
|
||||||
|
} else if (buf.length < n) {
|
||||||
|
throw new IllegalArgumentException("getBytes: buffer too small");
|
||||||
|
}
|
||||||
|
int offset = 0;
|
||||||
|
while (n > 0) {
|
||||||
|
ByteBuffer b = getBuffer(true);
|
||||||
|
int length = Math.min(n, b.remaining());
|
||||||
|
b.get(buf, offset, length);
|
||||||
|
offset += length;
|
||||||
|
n -= length;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getShort() throws IOException {
|
||||||
|
ByteBuffer buf = getBuffer(true);
|
||||||
|
int rem = buf.remaining();
|
||||||
|
if (rem >= 2) {
|
||||||
|
return buf.getShort() & 0xffff;
|
||||||
|
}
|
||||||
|
// Slow path. Not common
|
||||||
|
int val = 0;
|
||||||
|
val = (val << 8) + getByte();
|
||||||
|
val = (val << 8) + getByte();
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInt() throws IOException {
|
||||||
|
ByteBuffer buf = getBuffer(true);
|
||||||
|
int rem = buf.remaining();
|
||||||
|
if (rem >= 4) {
|
||||||
|
return buf.getInt();
|
||||||
|
}
|
||||||
|
// Slow path. Not common
|
||||||
|
int val = 0;
|
||||||
|
for (int nbytes = 0; nbytes < 4; nbytes++) {
|
||||||
|
val = (val << 8) + getByte();
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ByteBuffer[] EMPTY = new ByteBuffer[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts whatever number of ByteBuffers from list to get required number
|
||||||
|
* of bytes. Any remaining buffers are 'tidied up' so reading can continue.
|
||||||
|
*/
|
||||||
|
public ByteBuffer[] getBuffers(int bytecount) throws IOException {
|
||||||
|
LinkedList<ByteBuffer> l = new LinkedList<>();
|
||||||
|
while (bytecount > 0) {
|
||||||
|
ByteBuffer buffer = getBuffer(true);
|
||||||
|
int remaining = buffer.remaining();
|
||||||
|
if (remaining > bytecount) {
|
||||||
|
int difference = remaining - bytecount;
|
||||||
|
// split
|
||||||
|
ByteBuffer newb = newBufferSupplier.get();
|
||||||
|
newb.clear();
|
||||||
|
int limit = buffer.limit();
|
||||||
|
buffer.limit(limit - difference);
|
||||||
|
newb.put(buffer);
|
||||||
|
newb.flip();
|
||||||
|
buffer.limit(limit);
|
||||||
|
l.add(newb);
|
||||||
|
bytecount = 0;
|
||||||
|
} else {
|
||||||
|
l.add(buffer);
|
||||||
|
currentBuffer = null;
|
||||||
|
iterator.remove();
|
||||||
|
bytecount -= remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l.toArray(EMPTY);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a ByteBuffer[] for writing frames into for output. The last
|
||||||
|
* ByteBuffer in the list is always unflipped (able to receive more bytes for
|
||||||
|
* sending) until getBufferArray() is called, which calls finish().
|
||||||
|
*
|
||||||
|
* This allows multiple frames to be written to the same BBG.
|
||||||
|
*
|
||||||
|
* Buffers added with addByteBuffer() must be already flipped.
|
||||||
|
*/
|
||||||
|
class ByteBufferGenerator {
|
||||||
|
|
||||||
|
ByteBuffer currentBuffer;
|
||||||
|
// source is assumed to always return the same sized buffer
|
||||||
|
final BufferHandler pool;
|
||||||
|
final ArrayList<ByteBuffer> buflist;
|
||||||
|
final int bufsize;
|
||||||
|
boolean finished;
|
||||||
|
|
||||||
|
ByteBufferGenerator(BufferHandler pool) {
|
||||||
|
this.buflist = new ArrayList<>();
|
||||||
|
this.pool = pool;
|
||||||
|
this.currentBuffer = pool.getBuffer();
|
||||||
|
this.bufsize = currentBuffer.capacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ByteBuffer[] EMPTY = new ByteBuffer[0];
|
||||||
|
|
||||||
|
public ByteBuffer[] getBufferArray() {
|
||||||
|
finish();
|
||||||
|
return buflist.toArray(EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<ByteBuffer> getBufferList() {
|
||||||
|
finish();
|
||||||
|
return buflist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void finish() {
|
||||||
|
if (finished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finished = true;
|
||||||
|
currentBuffer.flip();
|
||||||
|
if (currentBuffer.hasRemaining()) {
|
||||||
|
buflist.add(currentBuffer);
|
||||||
|
} else {
|
||||||
|
pool.returnBuffer(currentBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only used for SettingsFrame: offset is number of bytes to
|
||||||
|
// ignore at start (we only want the payload of the settings frame)
|
||||||
|
public byte[] asByteArray(int offset) {
|
||||||
|
ByteBuffer[] bufs = getBufferArray();
|
||||||
|
int size = 0;
|
||||||
|
for (ByteBuffer buf : bufs) {
|
||||||
|
size += buf.remaining();
|
||||||
|
}
|
||||||
|
byte[] bytes = new byte[size-offset];
|
||||||
|
int pos = 0;
|
||||||
|
for (ByteBuffer buf : bufs) {
|
||||||
|
int rem = buf.remaining();
|
||||||
|
int ignore = Math.min(rem, offset);
|
||||||
|
buf.position(buf.position()+ignore);
|
||||||
|
rem -= ignore;
|
||||||
|
offset -= ignore;
|
||||||
|
buf.get(bytes, pos, rem);
|
||||||
|
pos += rem;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer getBuffer(long n) {
|
||||||
|
if (currentBuffer.remaining() < n) {
|
||||||
|
getNewBuffer();
|
||||||
|
if (n > currentBuffer.capacity()) {
|
||||||
|
throw new IllegalArgumentException("requested buffer too large");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getNewBuffer() {
|
||||||
|
currentBuffer.flip();
|
||||||
|
if (currentBuffer.hasRemaining()) {
|
||||||
|
buflist.add(currentBuffer);
|
||||||
|
} else {
|
||||||
|
pool.returnBuffer(currentBuffer);
|
||||||
|
}
|
||||||
|
currentBuffer = pool.getBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addByteBuffer(ByteBuffer buf) {
|
||||||
|
getNewBuffer();
|
||||||
|
buflist.add(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPadding(int length) {
|
||||||
|
while (length > 0) {
|
||||||
|
int n = Math.min(length, bufsize);
|
||||||
|
ByteBuffer b = getBuffer(n);
|
||||||
|
// TODO: currently zeroed?
|
||||||
|
b.position(b.position() + n);
|
||||||
|
length -= n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.CharacterCodingException;
|
||||||
|
import java.nio.charset.CharsetDecoder;
|
||||||
|
import java.nio.charset.CharsetEncoder;
|
||||||
|
import java.nio.charset.CoderResult;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
// The purpose of this class is to separate charset-related tasks from the main
|
||||||
|
// WebSocket logic, simplifying where possible.
|
||||||
|
//
|
||||||
|
// * Coders hide the differences between coding and flushing stages on the
|
||||||
|
// API level
|
||||||
|
// * Verifier abstracts the way the verification is performed
|
||||||
|
// (spoiler: it's a decoding into a throw-away buffer)
|
||||||
|
//
|
||||||
|
// Coding methods throw exceptions instead of returning coding result denoting
|
||||||
|
// errors, since any kind of handling and recovery is not expected.
|
||||||
|
final class CharsetToolkit {
|
||||||
|
|
||||||
|
private CharsetToolkit() { }
|
||||||
|
|
||||||
|
static final class Verifier {
|
||||||
|
|
||||||
|
private final CharsetDecoder decoder = UTF_8.newDecoder();
|
||||||
|
// A buffer used to check validity of UTF-8 byte stream by decoding it.
|
||||||
|
// The contents of this buffer are never used.
|
||||||
|
// The size is arbitrary, though it should probably be chosen from the
|
||||||
|
// performance perspective since it affects the total number of calls to
|
||||||
|
// decoder.decode() and amount of work in each of these calls
|
||||||
|
private final CharBuffer blackHole = CharBuffer.allocate(1024);
|
||||||
|
|
||||||
|
void verify(ByteBuffer in, boolean endOfInput)
|
||||||
|
throws CharacterCodingException {
|
||||||
|
while (true) {
|
||||||
|
// Since decoder.flush() cannot produce an error, it's not
|
||||||
|
// helpful for verification. Therefore this step is skipped.
|
||||||
|
CoderResult r = decoder.decode(in, blackHole, endOfInput);
|
||||||
|
if (r.isOverflow()) {
|
||||||
|
blackHole.clear();
|
||||||
|
} else if (r.isUnderflow()) {
|
||||||
|
break;
|
||||||
|
} else if (r.isError()) {
|
||||||
|
r.throwException();
|
||||||
|
} else {
|
||||||
|
// Should not happen
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Verifier reset() {
|
||||||
|
decoder.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Encoder {
|
||||||
|
|
||||||
|
private final CharsetEncoder encoder = UTF_8.newEncoder();
|
||||||
|
private boolean coding = true;
|
||||||
|
|
||||||
|
CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput)
|
||||||
|
throws CharacterCodingException {
|
||||||
|
|
||||||
|
if (coding) {
|
||||||
|
CoderResult r = encoder.encode(in, out, endOfInput);
|
||||||
|
if (r.isOverflow()) {
|
||||||
|
return r;
|
||||||
|
} else if (r.isUnderflow()) {
|
||||||
|
if (endOfInput) {
|
||||||
|
coding = false;
|
||||||
|
} else {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else if (r.isError()) {
|
||||||
|
r.throwException();
|
||||||
|
} else {
|
||||||
|
// Should not happen
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert !coding;
|
||||||
|
return encoder.flush(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Encoder reset() {
|
||||||
|
coding = true;
|
||||||
|
encoder.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CharBuffer decode(ByteBuffer in) throws CharacterCodingException {
|
||||||
|
return UTF_8.newDecoder().decode(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Decoder {
|
||||||
|
|
||||||
|
private final CharsetDecoder decoder = UTF_8.newDecoder();
|
||||||
|
private boolean coding = true; // Either coding or flushing
|
||||||
|
|
||||||
|
CoderResult decode(ByteBuffer in, CharBuffer out, boolean endOfInput)
|
||||||
|
throws CharacterCodingException {
|
||||||
|
|
||||||
|
if (coding) {
|
||||||
|
CoderResult r = decoder.decode(in, out, endOfInput);
|
||||||
|
if (r.isOverflow()) {
|
||||||
|
return r;
|
||||||
|
} else if (r.isUnderflow()) {
|
||||||
|
if (endOfInput) {
|
||||||
|
coding = false;
|
||||||
|
} else {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else if (r.isError()) {
|
||||||
|
r.throwException();
|
||||||
|
} else {
|
||||||
|
// Should not happen
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert !coding;
|
||||||
|
return decoder.flush(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Decoder reset() {
|
||||||
|
coding = true;
|
||||||
|
decoder.reset();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ import java.util.Objects;
|
|||||||
class ConnectionPool {
|
class ConnectionPool {
|
||||||
|
|
||||||
static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
|
static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
|
||||||
"sun.net.httpclient.keepalive.timeout", 1200); // seconds
|
"java.net.httpclient.keepalive.timeout", 1200); // seconds
|
||||||
|
|
||||||
// Pools of idle connections
|
// Pools of idle connections
|
||||||
|
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
class ContinuationFrame extends HeaderFrame {
|
||||||
|
|
||||||
|
public static final int TYPE = 0x9;
|
||||||
|
|
||||||
|
ContinuationFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
headerBlocks = bc.getBuffers(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
for (int i=0; i<headerBlocks.length; i++) {
|
||||||
|
bg.addByteBuffer(headerBlocks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean endHeaders() {
|
||||||
|
return getFlag(END_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = headerLength;
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ class CookieFilter implements HeaderFilter {
|
|||||||
@Override
|
@Override
|
||||||
public void request(HttpRequestImpl r) throws IOException {
|
public void request(HttpRequestImpl r) throws IOException {
|
||||||
Map<String,List<String>> userheaders, cookies;
|
Map<String,List<String>> userheaders, cookies;
|
||||||
userheaders = r.getUserHeaders().directMap();
|
userheaders = r.getUserHeaders().map();
|
||||||
cookies = cookieMan.get(r.uri(), userheaders);
|
cookies = cookieMan.get(r.uri(), userheaders);
|
||||||
// add the returned cookies
|
// add the returned cookies
|
||||||
HttpHeadersImpl systemHeaders = r.getSystemHeaders();
|
HttpHeadersImpl systemHeaders = r.getSystemHeaders();
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class DataFrame extends Http2Frame {
|
||||||
|
|
||||||
|
public final static int TYPE = 0x0;
|
||||||
|
|
||||||
|
DataFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
public static final int END_STREAM = 0x1;
|
||||||
|
public static final int PADDED = 0x8;
|
||||||
|
|
||||||
|
int padLength;
|
||||||
|
int dataLength;
|
||||||
|
ByteBuffer[] data;
|
||||||
|
|
||||||
|
public void setData(ByteBuffer[] data) {
|
||||||
|
this.data = data;
|
||||||
|
setDataLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case END_STREAM:
|
||||||
|
return "END_STREAM";
|
||||||
|
case PADDED:
|
||||||
|
return "PADDED";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setData(ByteBuffer data) {
|
||||||
|
ByteBuffer[] bb;
|
||||||
|
if (data == null) {
|
||||||
|
bb = new ByteBuffer[0];
|
||||||
|
} else {
|
||||||
|
bb = new ByteBuffer[1];
|
||||||
|
bb[0] = data;
|
||||||
|
}
|
||||||
|
setData(bb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ByteBuffer[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDataLength() {
|
||||||
|
int len = 0;
|
||||||
|
for (ByteBuffer buf : data) {
|
||||||
|
len += buf.remaining();
|
||||||
|
}
|
||||||
|
dataLength = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
padLength = bc.getByte();
|
||||||
|
dataLength = length - (padLength + 1);
|
||||||
|
} else {
|
||||||
|
dataLength = length;
|
||||||
|
}
|
||||||
|
data = bc.getBuffers(dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getPadLength() {
|
||||||
|
return padLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getDataLength() {
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
ByteBuffer buf = bg.getBuffer(1);
|
||||||
|
buf.put((byte)getPadLength());
|
||||||
|
}
|
||||||
|
for (int i=0; i<data.length; i++) {
|
||||||
|
bg.addByteBuffer(data[i]);
|
||||||
|
}
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
bg.addPadding(padLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = dataLength;
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
length += (1 + padLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
abstract class ErrorFrame extends Http2Frame {
|
||||||
|
|
||||||
|
// error codes
|
||||||
|
public static final int NO_ERROR = 0x0;
|
||||||
|
public static final int PROTOCOL_ERROR = 0x1;
|
||||||
|
public static final int INTERNAL_ERROR = 0x2;
|
||||||
|
public static final int FLOW_CONTROL_ERROR = 0x3;
|
||||||
|
public static final int SETTINGS_TIMEOUT = 0x4;
|
||||||
|
public static final int STREAM_CLOSED = 0x5;
|
||||||
|
public static final int FRAME_SIZE_ERROR = 0x6;
|
||||||
|
public static final int REFUSED_STREAM = 0x7;
|
||||||
|
public static final int CANCEL = 0x8;
|
||||||
|
public static final int COMPRESSION_ERROR = 0x9;
|
||||||
|
public static final int CONNECT_ERROR = 0xa;
|
||||||
|
public static final int ENHANCE_YOUR_CALM = 0xb;
|
||||||
|
public static final int INADEQUATE_SECURITY = 0xc;
|
||||||
|
public static final int HTTP_1_1_REQUIRED = 0xd;
|
||||||
|
static final int LAST_ERROR = 0xd;
|
||||||
|
|
||||||
|
static final String[] errorStrings = {
|
||||||
|
"Not an error",
|
||||||
|
"Protocol error",
|
||||||
|
"Internal error",
|
||||||
|
"Flow control error",
|
||||||
|
"Settings timeout",
|
||||||
|
"Stream is closed",
|
||||||
|
"Frame size error",
|
||||||
|
"Stream not processed",
|
||||||
|
"Stream cancelled",
|
||||||
|
"Compression state not updated",
|
||||||
|
"TCP Connection error on CONNECT",
|
||||||
|
"Processing capacity exceeded",
|
||||||
|
"Negotiated TLS parameters not acceptable",
|
||||||
|
"Use HTTP/1.1 for request"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String stringForCode(int code) {
|
||||||
|
if (code < 0)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
|
if (code > LAST_ERROR) {
|
||||||
|
return "Error: " + Integer.toString(code);
|
||||||
|
} else {
|
||||||
|
return errorStrings[code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int errorCode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " Error: " + stringForCode(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorCode() {
|
||||||
|
return this.errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorCode(int errorCode) {
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
}
|
||||||
|
}
|
@ -128,7 +128,7 @@ class Exchange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponseImpl responseImpl0(HttpConnection connection)
|
private HttpResponseImpl responseImpl0(HttpConnection connection)
|
||||||
throws IOException, InterruptedException
|
throws IOException, InterruptedException
|
||||||
{
|
{
|
||||||
exchImpl = ExchangeImpl.get(this, connection);
|
exchImpl = ExchangeImpl.get(this, connection);
|
||||||
@ -136,7 +136,7 @@ class Exchange {
|
|||||||
request.addSystemHeader("Expect", "100-Continue");
|
request.addSystemHeader("Expect", "100-Continue");
|
||||||
exchImpl.sendHeadersOnly();
|
exchImpl.sendHeadersOnly();
|
||||||
HttpResponseImpl resp = exchImpl.getResponse();
|
HttpResponseImpl resp = exchImpl.getResponse();
|
||||||
logResponse(resp);
|
Utils.logResponse(resp);
|
||||||
if (resp.statusCode() != 100) {
|
if (resp.statusCode() != 100) {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ class Exchange {
|
|||||||
} else {
|
} else {
|
||||||
exchImpl.sendRequest();
|
exchImpl.sendRequest();
|
||||||
HttpResponseImpl resp = exchImpl.getResponse();
|
HttpResponseImpl resp = exchImpl.getResponse();
|
||||||
logResponse(resp);
|
Utils.logResponse(resp);
|
||||||
return checkForUpgrade(resp, exchImpl);
|
return checkForUpgrade(resp, exchImpl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,9 +163,7 @@ class Exchange {
|
|||||||
}
|
}
|
||||||
SecurityException e = securityCheck(acc);
|
SecurityException e = securityCheck(acc);
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
CompletableFuture<HttpResponseImpl> cf = new CompletableFuture<>();
|
return CompletableFuture.failedFuture(e);
|
||||||
cf.completeExceptionally(e);
|
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
if (permissions.size() > 0) {
|
if (permissions.size() > 0) {
|
||||||
return AccessController.doPrivileged(
|
return AccessController.doPrivileged(
|
||||||
@ -182,9 +180,7 @@ class Exchange {
|
|||||||
try {
|
try {
|
||||||
exchImpl = ExchangeImpl.get(this, connection);
|
exchImpl = ExchangeImpl.get(this, connection);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
CompletableFuture<HttpResponseImpl> cf = new CompletableFuture<>();
|
return CompletableFuture.failedFuture(e);
|
||||||
cf.completeExceptionally(e);
|
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
if (request.expectContinue()) {
|
if (request.expectContinue()) {
|
||||||
request.addSystemHeader("Expect", "100-Continue");
|
request.addSystemHeader("Expect", "100-Continue");
|
||||||
@ -200,23 +196,19 @@ class Exchange {
|
|||||||
return exchImpl.sendBodyAsync()
|
return exchImpl.sendBodyAsync()
|
||||||
.thenCompose(exchImpl::getResponseAsync)
|
.thenCompose(exchImpl::getResponseAsync)
|
||||||
.thenApply((r) -> {
|
.thenApply((r) -> {
|
||||||
logResponse(r);
|
Utils.logResponse(r);
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Exchange.this.response = r1;
|
Exchange.this.response = r1;
|
||||||
logResponse(r1);
|
Utils.logResponse(r1);
|
||||||
return CompletableFuture.completedFuture(r1);
|
return CompletableFuture.completedFuture(r1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return exchImpl
|
return exchImpl
|
||||||
.sendHeadersAsync()
|
.sendRequestAsync()
|
||||||
.thenCompose((Void v) -> {
|
.thenCompose(exchImpl::getResponseAsync)
|
||||||
// send body and get response at same time
|
|
||||||
return exchImpl.sendBodyAsync()
|
|
||||||
.thenCompose(exchImpl::getResponseAsync);
|
|
||||||
})
|
|
||||||
.thenCompose((HttpResponseImpl r1) -> {
|
.thenCompose((HttpResponseImpl r1) -> {
|
||||||
int rcode = r1.statusCode();
|
int rcode = r1.statusCode();
|
||||||
CompletableFuture<HttpResponseImpl> cf =
|
CompletableFuture<HttpResponseImpl> cf =
|
||||||
@ -225,13 +217,13 @@ class Exchange {
|
|||||||
return cf;
|
return cf;
|
||||||
} else {
|
} else {
|
||||||
Exchange.this.response = r1;
|
Exchange.this.response = r1;
|
||||||
logResponse(r1);
|
Utils.logResponse(r1);
|
||||||
return CompletableFuture.completedFuture(r1);
|
return CompletableFuture.completedFuture(r1);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.thenApply((HttpResponseImpl response) -> {
|
.thenApply((HttpResponseImpl response) -> {
|
||||||
this.response = response;
|
this.response = response;
|
||||||
logResponse(response);
|
Utils.logResponse(response);
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -254,9 +246,9 @@ class Exchange {
|
|||||||
client.client2(),
|
client.client2(),
|
||||||
this)
|
this)
|
||||||
.thenCompose((Http2Connection c) -> {
|
.thenCompose((Http2Connection c) -> {
|
||||||
|
c.putConnection();
|
||||||
Stream s = c.getStream(1);
|
Stream s = c.getStream(1);
|
||||||
exchImpl = s;
|
exchImpl = s;
|
||||||
c.putConnection();
|
|
||||||
return s.getResponseAsync(null);
|
return s.getResponseAsync(null);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -294,21 +286,6 @@ class Exchange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void logResponse(HttpResponseImpl r) {
|
|
||||||
if (!Log.requests())
|
|
||||||
return;
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
String method = r.request().method();
|
|
||||||
URI uri = r.uri();
|
|
||||||
String uristring = uri == null ? "" : uri.toString();
|
|
||||||
sb.append('(')
|
|
||||||
.append(method)
|
|
||||||
.append(" ")
|
|
||||||
.append(uristring)
|
|
||||||
.append(") ")
|
|
||||||
.append(Integer.toString(r.statusCode()));
|
|
||||||
Log.logResponse(sb.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
<T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
|
<T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
|
||||||
return exchImpl.responseBodyAsync(processor);
|
return exchImpl.responseBodyAsync(processor);
|
||||||
@ -352,9 +329,9 @@ class Exchange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String method = request.method();
|
String method = request.method();
|
||||||
HttpHeadersImpl userHeaders = request.getUserHeaders();
|
HttpHeaders userHeaders = request.getUserHeaders();
|
||||||
URI u = getURIForSecurityCheck();
|
URI u = getURIForSecurityCheck();
|
||||||
URLPermission p = Utils.getPermission(u, method, userHeaders.directMap());
|
URLPermission p = Utils.getPermission(u, method, userHeaders.map());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assert acc != null;
|
assert acc != null;
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents one frame. May be initialized with a leftover buffer from previous
|
||||||
|
* frame. Call {@code haveFrame()} to determine if buffers contains at least one
|
||||||
|
* frame. If false, the obtain another buffer and call {@code}input(ByteBuffer)}.
|
||||||
|
* There may be additional bytes at end of the frame list.
|
||||||
|
*/
|
||||||
|
class FrameReader {
|
||||||
|
|
||||||
|
final List<ByteBuffer> buffers;
|
||||||
|
|
||||||
|
FrameReader() {
|
||||||
|
buffers = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameReader(FrameReader that) {
|
||||||
|
this.buffers = that.buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameReader(ByteBuffer remainder) {
|
||||||
|
buffers = new LinkedList<>();
|
||||||
|
if (remainder != null) {
|
||||||
|
buffers.add(remainder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void input(ByteBuffer buffer) {
|
||||||
|
buffers.add(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean haveFrame() {
|
||||||
|
//buffers = Utils.superCompact(buffers, () -> ByteBuffer.allocate(Utils.BUFSIZE));
|
||||||
|
int size = 0;
|
||||||
|
for (ByteBuffer buffer : buffers) {
|
||||||
|
size += buffer.remaining();
|
||||||
|
}
|
||||||
|
if (size < 3) {
|
||||||
|
return false; // don't have length yet
|
||||||
|
}
|
||||||
|
// we at least have length field
|
||||||
|
int length = 0;
|
||||||
|
int j = 0;
|
||||||
|
ByteBuffer b = buffers.get(j);
|
||||||
|
b.mark();
|
||||||
|
for (int i=0; i<3; i++) {
|
||||||
|
while (!b.hasRemaining()) {
|
||||||
|
b.reset();
|
||||||
|
b = buffers.get(++j);
|
||||||
|
b.mark();
|
||||||
|
}
|
||||||
|
length = (length << 8) + (b.get() & 0xff);
|
||||||
|
}
|
||||||
|
b.reset();
|
||||||
|
return (size >= length + 9); // frame length
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized List<ByteBuffer> frame() {
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class GoAwayFrame extends ErrorFrame {
|
||||||
|
|
||||||
|
GoAwayFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastStream;
|
||||||
|
byte[] debugData = new byte[0];
|
||||||
|
|
||||||
|
public static final int TYPE = 0x7;
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
public static final int ACK = 0x1;
|
||||||
|
|
||||||
|
public void setDebugData(byte[] debugData) {
|
||||||
|
this.debugData = debugData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " Debugdata: " + new String(debugData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case ACK:
|
||||||
|
return "ACK";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastStream(int lastStream) {
|
||||||
|
this.lastStream = lastStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastStream() {
|
||||||
|
return this.lastStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getDebugData() {
|
||||||
|
return debugData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if (length < 8) {
|
||||||
|
throw new IOException("Invalid GoAway frame");
|
||||||
|
}
|
||||||
|
lastStream = bc.getInt() & 0x7fffffff;
|
||||||
|
errorCode = bc.getInt();
|
||||||
|
//debugData = bc.getBytes(8);
|
||||||
|
int datalen = length - 8;
|
||||||
|
if (datalen > 0) {
|
||||||
|
debugData = bc.getBytes(datalen);
|
||||||
|
Log.logError("GoAway debugData " + new String(debugData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(length);
|
||||||
|
buf.putInt(lastStream);
|
||||||
|
buf.putInt(errorCode);
|
||||||
|
if (length > 8) {
|
||||||
|
buf.put(debugData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = 8 + debugData.length;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either a HeadersFrame or a ContinuationFrame
|
||||||
|
*/
|
||||||
|
abstract class HeaderFrame extends Http2Frame {
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
int number;
|
||||||
|
int headerLength;
|
||||||
|
ByteBuffer[] headerBlocks;
|
||||||
|
|
||||||
|
public static final int END_HEADERS = 0x4;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case END_HEADERS:
|
||||||
|
return "END_HEADERS";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the array of hpack encoded ByteBuffers
|
||||||
|
*/
|
||||||
|
public void setHeaderBlock(ByteBuffer bufs[], int offset, int number) {
|
||||||
|
this.headerBlocks = bufs;
|
||||||
|
this.offset = offset;
|
||||||
|
this.number = number;
|
||||||
|
int length = 0;
|
||||||
|
for (int i=offset; i<offset+number; i++) {
|
||||||
|
length += headerBlocks[i].remaining();
|
||||||
|
}
|
||||||
|
this.headerLength = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeaderBlock(ByteBuffer bufs[]) {
|
||||||
|
setHeaderBlock(bufs, 0, bufs.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer[] getHeaderBlock() {
|
||||||
|
return headerBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this block is the final block of headers
|
||||||
|
*/
|
||||||
|
public abstract boolean endHeaders();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class HeadersFrame extends HeaderFrame {
|
||||||
|
|
||||||
|
public final static int TYPE = 0x1;
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
public static final int END_STREAM = 0x1;
|
||||||
|
public static final int PADDED = 0x8;
|
||||||
|
public static final int PRIORITY = 0x20;
|
||||||
|
|
||||||
|
|
||||||
|
int padLength;
|
||||||
|
int streamDependency;
|
||||||
|
int weight;
|
||||||
|
boolean exclusive;
|
||||||
|
|
||||||
|
HeadersFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case END_STREAM:
|
||||||
|
return "END_STREAM";
|
||||||
|
case PADDED:
|
||||||
|
return "PADDED";
|
||||||
|
case PRIORITY:
|
||||||
|
return "PRIORITY";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPadLength(int padLength) {
|
||||||
|
this.padLength = padLength;
|
||||||
|
flags |= PADDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int streamDependency, boolean exclusive, int weight) {
|
||||||
|
this.streamDependency = streamDependency;
|
||||||
|
this.exclusive = exclusive;
|
||||||
|
this.weight = weight;
|
||||||
|
this.flags |= PRIORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStreamDependency() {
|
||||||
|
return streamDependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean endHeaders() {
|
||||||
|
return getFlag(END_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getExclusive() {
|
||||||
|
return exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
padLength = bc.getByte();
|
||||||
|
}
|
||||||
|
if ((flags & PRIORITY) != 0) {
|
||||||
|
int x = bc.getInt();
|
||||||
|
exclusive = (x & 0x80000000) != 0;
|
||||||
|
streamDependency = x & 0x7fffffff;
|
||||||
|
weight = bc.getByte();
|
||||||
|
}
|
||||||
|
headerLength = length - padLength;
|
||||||
|
headerBlocks = bc.getBuffers(headerLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
int len = 0;
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
len += (1 + padLength);
|
||||||
|
}
|
||||||
|
if ((flags & PRIORITY) != 0) {
|
||||||
|
len += 5;
|
||||||
|
}
|
||||||
|
len += headerLength;
|
||||||
|
this.length = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(6);
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
buf.put((byte)padLength);
|
||||||
|
}
|
||||||
|
if ((flags & PRIORITY) != 0) {
|
||||||
|
int x = exclusive ? 1 << 31 + streamDependency : streamDependency;
|
||||||
|
buf.putInt(x);
|
||||||
|
buf.put((byte)weight);
|
||||||
|
}
|
||||||
|
for (int i=0; i<headerBlocks.length; i++) {
|
||||||
|
bg.addByteBuffer(headerBlocks[i]);
|
||||||
|
}
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
bg.addPadding(padLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -64,32 +64,12 @@ class Http1Exchange extends ExchangeImpl {
|
|||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
} else {
|
} else {
|
||||||
InetSocketAddress addr = getAddress(request);
|
InetSocketAddress addr = Utils.getAddress(request);
|
||||||
this.connection = HttpConnection.getConnection(addr, request);
|
this.connection = HttpConnection.getConnection(addr, request);
|
||||||
}
|
}
|
||||||
this.requestAction = new Http1Request(request, this.connection);
|
this.requestAction = new Http1Request(request, this.connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InetSocketAddress getAddress(HttpRequestImpl req) {
|
|
||||||
URI uri = req.uri();
|
|
||||||
if (uri == null) {
|
|
||||||
return req.authority();
|
|
||||||
}
|
|
||||||
int port = uri.getPort();
|
|
||||||
if (port == -1) {
|
|
||||||
if (uri.getScheme().equalsIgnoreCase("https")) {
|
|
||||||
port = 443;
|
|
||||||
} else {
|
|
||||||
port = 80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String host = uri.getHost();
|
|
||||||
if (req.proxy() == null) {
|
|
||||||
return new InetSocketAddress(host, port);
|
|
||||||
} else {
|
|
||||||
return InetSocketAddress.createUnresolved(host, port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpConnection connection() {
|
HttpConnection connection() {
|
||||||
return connection;
|
return connection;
|
||||||
@ -211,7 +191,7 @@ class Http1Exchange extends ExchangeImpl {
|
|||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() -> request.getAccessControlContext());
|
request::getAccessControlContext);
|
||||||
operations.add(cf);
|
operations.add(cf);
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
@ -269,7 +249,7 @@ class Http1Exchange extends ExchangeImpl {
|
|||||||
cf.completeExceptionally(e);
|
cf.completeExceptionally(e);
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
}, () -> request.getAccessControlContext());
|
}, request::getAccessControlContext);
|
||||||
operations.add(cf);
|
operations.add(cf);
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
@ -302,7 +282,7 @@ class Http1Exchange extends ExchangeImpl {
|
|||||||
cf.completeExceptionally(e);
|
cf.completeExceptionally(e);
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
}, () -> request.getAccessControlContext());
|
}, request::getAccessControlContext);
|
||||||
operations.add(cf);
|
operations.add(cf);
|
||||||
return cf;
|
return cf;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.http.HttpConnection.Mode;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.function.LongConsumer;
|
import java.util.function.LongConsumer;
|
||||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||||
@ -48,7 +49,8 @@ class Http1Request {
|
|||||||
// See line 206 and below for description
|
// See line 206 and below for description
|
||||||
final ByteBuffer[] buffers;
|
final ByteBuffer[] buffers;
|
||||||
final HttpRequest.BodyProcessor requestProc;
|
final HttpRequest.BodyProcessor requestProc;
|
||||||
final HttpHeadersImpl userHeaders, systemHeaders;
|
final HttpHeaders userHeaders;
|
||||||
|
final HttpHeadersImpl systemHeaders;
|
||||||
final LongConsumer flowController;
|
final LongConsumer flowController;
|
||||||
boolean streaming;
|
boolean streaming;
|
||||||
long contentLength;
|
long contentLength;
|
||||||
@ -91,10 +93,10 @@ class Http1Request {
|
|||||||
|
|
||||||
private void collectHeaders1(StringBuilder sb,
|
private void collectHeaders1(StringBuilder sb,
|
||||||
HttpRequestImpl request,
|
HttpRequestImpl request,
|
||||||
HttpHeadersImpl headers)
|
HttpHeaders headers)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
Map<String,List<String>> h = headers.directMap();
|
Map<String,List<String>> h = headers.map();
|
||||||
Set<Map.Entry<String,List<String>>> entries = h.entrySet();
|
Set<Map.Entry<String,List<String>>> entries = h.entrySet();
|
||||||
|
|
||||||
for (Map.Entry<String,List<String>> entry : entries) {
|
for (Map.Entry<String,List<String>> entry : entries) {
|
||||||
@ -112,8 +114,6 @@ class Http1Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int BUFSIZE = 64 * 1024; // TODO: configurable?
|
|
||||||
|
|
||||||
private String getPathAndQuery(URI uri) {
|
private String getPathAndQuery(URI uri) {
|
||||||
String path = uri.getPath();
|
String path = uri.getPath();
|
||||||
String query = uri.getQuery();
|
String query = uri.getQuery();
|
||||||
@ -134,6 +134,25 @@ class Http1Request {
|
|||||||
return addr.getHostString() + ":" + addr.getPort();
|
return addr.getHostString() + ":" + addr.getPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String hostString() {
|
||||||
|
URI uri = request.uri();
|
||||||
|
int port = uri.getPort();
|
||||||
|
String host = uri.getHost();
|
||||||
|
|
||||||
|
boolean defaultPort;
|
||||||
|
if (port == -1)
|
||||||
|
defaultPort = true;
|
||||||
|
else if (request.secure())
|
||||||
|
defaultPort = port == 443;
|
||||||
|
else
|
||||||
|
defaultPort = port == 80;
|
||||||
|
|
||||||
|
if (defaultPort)
|
||||||
|
return host;
|
||||||
|
else
|
||||||
|
return host + ":" + Integer.toString(port);
|
||||||
|
}
|
||||||
|
|
||||||
private String requestURI() {
|
private String requestURI() {
|
||||||
URI uri = request.uri();
|
URI uri = request.uri();
|
||||||
String method = request.method();
|
String method = request.method();
|
||||||
@ -161,6 +180,7 @@ class Http1Request {
|
|||||||
|
|
||||||
void sendRequest() throws IOException {
|
void sendRequest() throws IOException {
|
||||||
collectHeaders();
|
collectHeaders();
|
||||||
|
chan.configureMode(Mode.BLOCKING);
|
||||||
if (contentLength == 0) {
|
if (contentLength == 0) {
|
||||||
chan.write(buffers, 0, 2);
|
chan.write(buffers, 0, 2);
|
||||||
} else if (contentLength > 0) {
|
} else if (contentLength > 0) {
|
||||||
@ -196,7 +216,7 @@ class Http1Request {
|
|||||||
buffers[0] = ByteBuffer.wrap(cmd.getBytes(StandardCharsets.US_ASCII));
|
buffers[0] = ByteBuffer.wrap(cmd.getBytes(StandardCharsets.US_ASCII));
|
||||||
URI uri = request.uri();
|
URI uri = request.uri();
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
systemHeaders.setHeader("Host", uri.getHost());
|
systemHeaders.setHeader("Host", hostString());
|
||||||
}
|
}
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
// this is not a user request. No content
|
// this is not a user request. No content
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2016, 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,11 +23,133 @@
|
|||||||
*/
|
*/
|
||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URI;
|
||||||
|
import static java.net.http.SettingsFrame.INITIAL_WINDOW_SIZE;
|
||||||
|
import static java.net.http.SettingsFrame.ENABLE_PUSH;
|
||||||
|
import static java.net.http.SettingsFrame.HEADER_TABLE_SIZE;
|
||||||
|
import static java.net.http.SettingsFrame.MAX_CONCURRENT_STREAMS;
|
||||||
|
import static java.net.http.SettingsFrame.MAX_FRAME_SIZE;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http2 specific aspects of HttpClientImpl
|
||||||
|
*/
|
||||||
class Http2ClientImpl {
|
class Http2ClientImpl {
|
||||||
Http2ClientImpl(HttpClientImpl t) {}
|
|
||||||
String getSettingsString() {return "";}
|
final private HttpClientImpl client;
|
||||||
void debugPrint() {}
|
|
||||||
Http2Connection getConnectionFor(HttpRequestImpl r) {
|
Http2ClientImpl(HttpClientImpl client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map key is "scheme:host:port" */
|
||||||
|
final private Map<String,Http2Connection> connections =
|
||||||
|
Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
final private Set<String> opening = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
|
synchronized boolean haveConnectionFor(URI uri, InetSocketAddress proxy) {
|
||||||
|
return connections.containsKey(Http2Connection.keyFor(uri,proxy));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a https request then blocks and waits until a connection is opened.
|
||||||
|
* Returns null if the request is 'http' as a different (upgrade)
|
||||||
|
* mechanism is used.
|
||||||
|
*
|
||||||
|
* Only one connection per destination is created. Blocks when opening
|
||||||
|
* connection, or when waiting for connection to be opened.
|
||||||
|
* First thread opens the connection and notifies the others when done.
|
||||||
|
*
|
||||||
|
* If the request is secure (https) then we open the connection here.
|
||||||
|
* If not, then the more complicated upgrade from 1.1 to 2 happens (not here)
|
||||||
|
* In latter case, when the Http2Connection is connected, putConnection() must
|
||||||
|
* be called to store it.
|
||||||
|
*/
|
||||||
|
Http2Connection getConnectionFor(HttpRequestImpl req)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
URI uri = req.uri();
|
||||||
|
InetSocketAddress proxy = req.proxy();
|
||||||
|
String key = Http2Connection.keyFor(uri, proxy);
|
||||||
|
Http2Connection connection;
|
||||||
|
synchronized (opening) {
|
||||||
|
while ((connection = connections.get(key)) == null) {
|
||||||
|
if (!req.secure()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!opening.contains(key)) {
|
||||||
|
opening.add(key);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
opening.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (connection != null) {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
// we are opening the connection here blocking until it is done.
|
||||||
|
connection = new Http2Connection(req);
|
||||||
|
synchronized (opening) {
|
||||||
|
connections.put(key, connection);
|
||||||
|
opening.remove(key);
|
||||||
|
opening.notifyAll();
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: If there isn't a connection to the same destination, then
|
||||||
|
* store it. If there is already a connection, then close it
|
||||||
|
*/
|
||||||
|
synchronized void putConnection(Http2Connection c) {
|
||||||
|
String key = c.key();
|
||||||
|
connections.put(key, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void deleteConnection(Http2Connection c) {
|
||||||
|
String key = c.key();
|
||||||
|
connections.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClientImpl client() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the client settings as a base64 (url) encoded string */
|
||||||
|
String getSettingsString() {
|
||||||
|
SettingsFrame sf = getClientSettings();
|
||||||
|
ByteBufferGenerator bg = new ByteBufferGenerator(client);
|
||||||
|
sf.writeOutgoing(bg);
|
||||||
|
byte[] settings = bg.asByteArray(9); // without the header
|
||||||
|
Base64.Encoder encoder = Base64.getUrlEncoder()
|
||||||
|
.withoutPadding();
|
||||||
|
return encoder.encodeToString(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int K = 1024;
|
||||||
|
|
||||||
|
SettingsFrame getClientSettings() {
|
||||||
|
SettingsFrame frame = new SettingsFrame();
|
||||||
|
frame.setParameter(HEADER_TABLE_SIZE, Utils.getIntegerNetProperty(
|
||||||
|
"java.net.httpclient.hpack.maxheadertablesize", 16 * K));
|
||||||
|
frame.setParameter(ENABLE_PUSH, Utils.getIntegerNetProperty(
|
||||||
|
"java.net.httpclient.enablepush", 1));
|
||||||
|
frame.setParameter(MAX_CONCURRENT_STREAMS, Utils.getIntegerNetProperty(
|
||||||
|
"java.net.httpclient.maxstreams", 16));
|
||||||
|
frame.setParameter(INITIAL_WINDOW_SIZE, Utils.getIntegerNetProperty(
|
||||||
|
"java.net.httpclient.windowsize", 32 * K));
|
||||||
|
frame.setParameter(MAX_FRAME_SIZE, Utils.getIntegerNetProperty(
|
||||||
|
"java.net.httpclient.maxframesize", 16 * K));
|
||||||
|
frame.computeLength();
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2016, 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,767 @@
|
|||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.CookieManager;
|
|
||||||
import java.net.ProxySelector;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import static java.net.http.Utils.BUFSIZE;
|
import java.net.http.HttpConnection.Mode;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.util.Collection;
|
||||||
import static java.nio.channels.SelectionKey.OP_CONNECT;
|
|
||||||
import static java.nio.channels.SelectionKey.OP_READ;
|
|
||||||
import static java.nio.channels.SelectionKey.OP_WRITE;
|
|
||||||
import java.nio.channels.Selector;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Map;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import sun.net.httpclient.hpack.Encoder;
|
||||||
import java.util.ListIterator;
|
import sun.net.httpclient.hpack.Decoder;
|
||||||
import java.util.Optional;
|
import static java.net.http.SettingsFrame.*;
|
||||||
import java.util.concurrent.Executors;
|
import static java.net.http.Utils.BUFSIZE;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.ArrayList;
|
||||||
import javax.net.ssl.SSLContext;
|
import java.util.Collections;
|
||||||
import javax.net.ssl.SSLParameters;
|
import java.util.Formatter;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import sun.net.httpclient.hpack.DecodingCallback;
|
||||||
|
|
||||||
class Http2Connection {
|
/**
|
||||||
static CompletableFuture<Http2Connection> createAsync(
|
* An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used
|
||||||
HttpConnection connection, Http2ClientImpl client2, Exchange exchange) {
|
* over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.
|
||||||
return null;
|
*
|
||||||
}
|
* Http2Connections belong to a Http2ClientImpl, (one of) which belongs
|
||||||
|
* to a HttpClientImpl.
|
||||||
|
*
|
||||||
|
* Creation cases:
|
||||||
|
* 1) upgraded HTTP/1.1 plain tcp connection
|
||||||
|
* 2) prior knowledge directly created plain tcp connection
|
||||||
|
* 3) directly created HTTP/2 SSL connection which uses ALPN.
|
||||||
|
*
|
||||||
|
* Sending is done by writing directly to underlying HttpConnection object which
|
||||||
|
* is operating in async mode. No flow control applies on output at this level
|
||||||
|
* and all writes are just executed as puts to an output Q belonging to HttpConnection
|
||||||
|
* Flow control is implemented by HTTP/2 protocol itself.
|
||||||
|
*
|
||||||
|
* Hpack header compression
|
||||||
|
* and outgoing stream creation is also done here, because these operations
|
||||||
|
* must be synchronized at the socket level. Stream objects send frames simply
|
||||||
|
* by placing them on the connection's output Queue. sendFrame() is called
|
||||||
|
* from a higher level (Stream) thread.
|
||||||
|
*
|
||||||
|
* asyncReceive(ByteBuffer) is always called from the selector thread. It assembles
|
||||||
|
* incoming Http2Frames, and directs them to the appropriate Stream.incoming()
|
||||||
|
* or handles them directly itself. This thread performs hpack decompression
|
||||||
|
* and incoming stream creation (Server push). Incoming frames destined for a
|
||||||
|
* stream are provided by calling Stream.incoming().
|
||||||
|
*/
|
||||||
|
class Http2Connection implements BufferHandler {
|
||||||
|
|
||||||
|
final Queue<Http2Frame> outputQ;
|
||||||
|
volatile boolean closed;
|
||||||
|
|
||||||
|
//-------------------------------------
|
||||||
|
final HttpConnection connection;
|
||||||
|
HttpClientImpl client;
|
||||||
|
final Http2ClientImpl client2;
|
||||||
|
Map<Integer,Stream> streams;
|
||||||
|
int nextstreamid = 3; // stream 1 is registered separately
|
||||||
|
int nextPushStream = 2;
|
||||||
|
Encoder hpackOut;
|
||||||
|
Decoder hpackIn;
|
||||||
|
SettingsFrame clientSettings, serverSettings;
|
||||||
|
ByteBufferConsumer bbc;
|
||||||
|
final LinkedList<ByteBuffer> freeList;
|
||||||
|
final String key; // for HttpClientImpl.connections map
|
||||||
|
FrameReader reader;
|
||||||
|
|
||||||
|
// Connection level flow control windows
|
||||||
|
int sendWindow = INITIAL_WINDOW_SIZE;
|
||||||
|
|
||||||
|
final static int DEFAULT_FRAME_SIZE = 16 * 1024;
|
||||||
|
private static ByteBuffer[] empty = Utils.EMPTY_BB_ARRAY;
|
||||||
|
|
||||||
|
final ExecutorWrapper executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is established by the protocol spec and the peer will update it with
|
||||||
|
* WINDOW_UPDATEs, which affects the sendWindow.
|
||||||
|
*/
|
||||||
|
final static int INITIAL_WINDOW_SIZE = 64 * 1024 - 1;
|
||||||
|
|
||||||
|
// TODO: need list of control frames from other threads
|
||||||
|
// that need to be sent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case 1) Create from upgraded HTTP/1.1 connection.
|
||||||
|
* Is ready to use. Will not be SSL. exchange is the Exchange
|
||||||
|
* that initiated the connection, whose response will be delivered
|
||||||
|
* on a Stream.
|
||||||
|
*/
|
||||||
Http2Connection(HttpConnection connection, Http2ClientImpl client2,
|
Http2Connection(HttpConnection connection, Http2ClientImpl client2,
|
||||||
Exchange exchange) throws IOException, InterruptedException {
|
Exchange exchange) throws IOException, InterruptedException {
|
||||||
|
this.outputQ = new Queue<>();
|
||||||
|
String msg = "Connection send window size " + Integer.toString(sendWindow);
|
||||||
|
Log.logTrace(msg);
|
||||||
|
|
||||||
|
//this.initialExchange = exchange;
|
||||||
|
assert !(connection instanceof SSLConnection);
|
||||||
|
this.connection = connection;
|
||||||
|
this.client = client2.client();
|
||||||
|
this.client2 = client2;
|
||||||
|
this.executor = client.executorWrapper();
|
||||||
|
this.freeList = new LinkedList<>();
|
||||||
|
this.key = keyFor(connection);
|
||||||
|
streams = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
initCommon();
|
||||||
|
//sendConnectionPreface();
|
||||||
|
Stream initialStream = createStream(exchange);
|
||||||
|
initialStream.registerStream(1);
|
||||||
|
initialStream.requestSent();
|
||||||
|
sendConnectionPreface();
|
||||||
|
connection.configureMode(Mode.ASYNC);
|
||||||
|
// start reading and writing
|
||||||
|
// start reading
|
||||||
|
AsyncConnection asyncConn = (AsyncConnection)connection;
|
||||||
|
asyncConn.setAsyncCallbacks(this::asyncReceive, this::shutdown);
|
||||||
|
asyncReceive(connection.getRemaining());
|
||||||
|
asyncConn.startReading();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream getStream(int i) {return null;}
|
// async style but completes immediately
|
||||||
Stream createStream(Exchange ex) {return null;}
|
static CompletableFuture<Http2Connection> createAsync(HttpConnection connection,
|
||||||
void putConnection() {}
|
Http2ClientImpl client2, Exchange exchange) {
|
||||||
|
CompletableFuture<Http2Connection> cf = new CompletableFuture<>();
|
||||||
|
try {
|
||||||
|
Http2Connection c = new Http2Connection(connection, client2, exchange);
|
||||||
|
cf.complete(c);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
cf.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cases 2) 3)
|
||||||
|
*
|
||||||
|
* request is request to be sent.
|
||||||
|
*/
|
||||||
|
Http2Connection(HttpRequestImpl request) throws IOException, InterruptedException {
|
||||||
|
InetSocketAddress proxy = request.proxy();
|
||||||
|
URI uri = request.uri();
|
||||||
|
InetSocketAddress addr = Utils.getAddress(request);
|
||||||
|
String msg = "Connection send window size " + Integer.toString(sendWindow);
|
||||||
|
Log.logTrace(msg);
|
||||||
|
this.key = keyFor(uri, proxy);
|
||||||
|
this.connection = HttpConnection.getConnection(addr, request, this);
|
||||||
|
streams = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
this.client = request.client();
|
||||||
|
this.client2 = client.client2();
|
||||||
|
this.executor = client.executorWrapper();
|
||||||
|
this.freeList = new LinkedList<>();
|
||||||
|
this.outputQ = new Queue<>();
|
||||||
|
nextstreamid = 1;
|
||||||
|
initCommon();
|
||||||
|
connection.connect();
|
||||||
|
connection.configureMode(Mode.ASYNC);
|
||||||
|
// start reading
|
||||||
|
AsyncConnection asyncConn = (AsyncConnection)connection;
|
||||||
|
asyncConn.setAsyncCallbacks(this::asyncReceive, this::shutdown);
|
||||||
|
sendConnectionPreface();
|
||||||
|
asyncConn.startReading();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW
|
||||||
|
synchronized void obtainSendWindow(int amount) throws InterruptedException {
|
||||||
|
while (amount > 0) {
|
||||||
|
int n = Math.min(amount, sendWindow);
|
||||||
|
sendWindow -= n;
|
||||||
|
amount -= n;
|
||||||
|
if (amount > 0)
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void updateSendWindow(int amount) {
|
||||||
|
if (sendWindow == 0) {
|
||||||
|
sendWindow += amount;
|
||||||
|
notifyAll();
|
||||||
|
} else
|
||||||
|
sendWindow += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized int sendWindow() {
|
||||||
|
return sendWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String keyFor(HttpConnection connection) {
|
||||||
|
boolean isProxy = connection.isProxied();
|
||||||
|
boolean isSecure = connection.isSecure();
|
||||||
|
InetSocketAddress addr = connection.address();
|
||||||
|
|
||||||
|
return keyString(isSecure, isProxy, addr.getHostString(), addr.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
static String keyFor(URI uri, InetSocketAddress proxy) {
|
||||||
|
boolean isSecure = uri.getScheme().equalsIgnoreCase("https");
|
||||||
|
boolean isProxy = proxy != null;
|
||||||
|
|
||||||
|
String host;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
if (isProxy) {
|
||||||
|
host = proxy.getHostString();
|
||||||
|
port = proxy.getPort();
|
||||||
|
} else {
|
||||||
|
host = uri.getHost();
|
||||||
|
port = uri.getPort();
|
||||||
|
}
|
||||||
|
return keyString(isSecure, isProxy, host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// {C,S}:{H:P}:host:port
|
||||||
|
// C indicates clear text connection "http"
|
||||||
|
// S indicates secure "https"
|
||||||
|
// H indicates host (direct) connection
|
||||||
|
// P indicates proxy
|
||||||
|
// Eg: "S:H:foo.com:80"
|
||||||
|
static String keyString(boolean secure, boolean proxy, String host, int port) {
|
||||||
|
char c1 = secure ? 'S' : 'C';
|
||||||
|
char c2 = proxy ? 'P' : 'H';
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(128);
|
||||||
|
sb.append(c1).append(':').append(c2).append(':')
|
||||||
|
.append(host).append(':').append(port);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String key() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void putConnection() {
|
||||||
|
client2.putConnection(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toHexdump1(ByteBuffer bb) {
|
||||||
|
bb.mark();
|
||||||
|
StringBuilder sb = new StringBuilder(512);
|
||||||
|
Formatter f = new Formatter(sb);
|
||||||
|
|
||||||
|
while (bb.hasRemaining()) {
|
||||||
|
int i = Byte.toUnsignedInt(bb.get());
|
||||||
|
f.format("%02x:", i);
|
||||||
|
}
|
||||||
|
sb.deleteCharAt(sb.length()-1);
|
||||||
|
bb.reset();
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toHexdump(ByteBuffer bb) {
|
||||||
|
List<String> words = new ArrayList<>();
|
||||||
|
int i = 0;
|
||||||
|
bb.mark();
|
||||||
|
while (bb.hasRemaining()) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
words.add("");
|
||||||
|
}
|
||||||
|
byte b = bb.get();
|
||||||
|
String hex = Integer.toHexString(256 + Byte.toUnsignedInt(b)).substring(1);
|
||||||
|
words.set(i / 2, words.get(i / 2) + hex);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
bb.reset();
|
||||||
|
return words.stream().collect(Collectors.joining(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decodeHeaders(HeaderFrame frame, DecodingCallback decoder) {
|
||||||
|
boolean endOfHeaders = frame.getFlag(HeaderFrame.END_HEADERS);
|
||||||
|
|
||||||
|
ByteBuffer[] buffers = frame.getHeaderBlock();
|
||||||
|
for (int i = 0; i < buffers.length; i++) {
|
||||||
|
hpackIn.decode(buffers[i], endOfHeaders, decoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getInitialSendWindowSize() {
|
||||||
|
return serverSettings.getParameter(SettingsFrame.INITIAL_WINDOW_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
GoAwayFrame f = new GoAwayFrame();
|
||||||
|
f.setDebugData("Requested by user".getBytes());
|
||||||
|
// TODO: set last stream. For now zero ok.
|
||||||
|
sendFrame(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferHandler methods
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getBuffer(int n) {
|
||||||
|
return client.getBuffer(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void returnBuffer(ByteBuffer buf) {
|
||||||
|
client.returnBuffer(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMinBufferSize(int n) {
|
||||||
|
client.setMinBufferSize(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object readlock = new Object();
|
||||||
|
|
||||||
|
void asyncReceive(ByteBuffer buffer) {
|
||||||
|
synchronized (readlock) {
|
||||||
|
try {
|
||||||
|
if (reader == null) {
|
||||||
|
reader = new FrameReader(buffer);
|
||||||
|
} else {
|
||||||
|
reader.input(buffer);
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
if (reader.haveFrame()) {
|
||||||
|
List<ByteBuffer> buffers = reader.frame();
|
||||||
|
|
||||||
|
ByteBufferConsumer bbc = new ByteBufferConsumer(buffers, this::getBuffer);
|
||||||
|
processFrame(bbc);
|
||||||
|
if (bbc.consumed()) {
|
||||||
|
reader = new FrameReader();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
reader = new FrameReader(reader);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
String msg = Utils.stackTrace(e);
|
||||||
|
Log.logTrace(msg);
|
||||||
|
shutdown(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown(Throwable t) {
|
||||||
|
System.err.println("Shutdown: " + t);
|
||||||
|
t.printStackTrace();
|
||||||
|
closed = true;
|
||||||
|
client2.deleteConnection(this);
|
||||||
|
Collection<Stream> c = streams.values();
|
||||||
|
for (Stream s : c) {
|
||||||
|
s.cancelImpl(t);
|
||||||
|
}
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles stream 0 (common) frames that apply to whole connection and passes
|
||||||
|
* other stream specific frames to that Stream object.
|
||||||
|
*
|
||||||
|
* Invokes Stream.incoming() which is expected to process frame without
|
||||||
|
* blocking.
|
||||||
|
*/
|
||||||
|
void processFrame(ByteBufferConsumer bbc) throws IOException, InterruptedException {
|
||||||
|
Http2Frame frame = Http2Frame.readIncoming(bbc);
|
||||||
|
Log.logFrames(frame, "IN");
|
||||||
|
int streamid = frame.streamid();
|
||||||
|
if (streamid == 0) {
|
||||||
|
handleCommonFrame(frame);
|
||||||
|
} else {
|
||||||
|
Stream stream = getStream(streamid);
|
||||||
|
if (stream == null) {
|
||||||
|
// should never receive a frame with unknown stream id
|
||||||
|
resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
|
||||||
|
}
|
||||||
|
if (frame instanceof PushPromiseFrame) {
|
||||||
|
PushPromiseFrame pp = (PushPromiseFrame)frame;
|
||||||
|
handlePushPromise(stream, pp);
|
||||||
|
} else if (frame instanceof HeaderFrame) {
|
||||||
|
// decode headers (or continuation)
|
||||||
|
decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());
|
||||||
|
stream.incoming(frame);
|
||||||
|
} else
|
||||||
|
stream.incoming(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePushPromise(Stream parent, PushPromiseFrame pp)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
HttpRequestImpl parentReq = parent.request;
|
||||||
|
int promisedStreamid = pp.getPromisedStream();
|
||||||
|
if (promisedStreamid != nextPushStream) {
|
||||||
|
resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
nextPushStream += 2;
|
||||||
|
}
|
||||||
|
HeaderDecoder decoder = new HeaderDecoder();
|
||||||
|
decodeHeaders(pp, decoder);
|
||||||
|
HttpHeadersImpl headers = decoder.headers();
|
||||||
|
HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers);
|
||||||
|
|
||||||
|
Stream.PushedStream pushStream = createPushStream(parent, pushReq);
|
||||||
|
pushStream.registerStream(promisedStreamid);
|
||||||
|
parent.incoming_pushPromise(pushReq, pushStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCommonFrame(Http2Frame frame)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
switch (frame.type()) {
|
||||||
|
case SettingsFrame.TYPE:
|
||||||
|
{ SettingsFrame f = (SettingsFrame)frame;
|
||||||
|
handleSettings(f);}
|
||||||
|
break;
|
||||||
|
case PingFrame.TYPE:
|
||||||
|
{ PingFrame f = (PingFrame)frame;
|
||||||
|
handlePing(f);}
|
||||||
|
break;
|
||||||
|
case GoAwayFrame.TYPE:
|
||||||
|
{ GoAwayFrame f = (GoAwayFrame)frame;
|
||||||
|
handleGoAway(f);}
|
||||||
|
break;
|
||||||
|
case WindowUpdateFrame.TYPE:
|
||||||
|
{ WindowUpdateFrame f = (WindowUpdateFrame)frame;
|
||||||
|
handleWindowUpdate(f);}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
protocolError(ErrorFrame.PROTOCOL_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetStream(int streamid, int code) throws IOException, InterruptedException {
|
||||||
|
Log.logError(
|
||||||
|
"Resetting stream {0,number,integer} with error code {1,number,integer}",
|
||||||
|
streamid, code);
|
||||||
|
ResetFrame frame = new ResetFrame();
|
||||||
|
frame.streamid(streamid);
|
||||||
|
frame.setErrorCode(code);
|
||||||
|
sendFrame(frame);
|
||||||
|
streams.remove(streamid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWindowUpdate(WindowUpdateFrame f)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
updateSendWindow(f.getUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void protocolError(int errorCode)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
GoAwayFrame frame = new GoAwayFrame();
|
||||||
|
frame.setErrorCode(errorCode);
|
||||||
|
sendFrame(frame);
|
||||||
|
String msg = "Error code: " + errorCode;
|
||||||
|
shutdown(new IOException("protocol error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSettings(SettingsFrame frame)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
if (frame.getFlag(SettingsFrame.ACK)) {
|
||||||
|
// ignore ack frames for now.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serverSettings = frame;
|
||||||
|
SettingsFrame ack = getAckFrame(frame.streamid());
|
||||||
|
sendFrame(ack);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePing(PingFrame frame)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
frame.setFlag(PingFrame.ACK);
|
||||||
|
sendFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGoAway(GoAwayFrame frame)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
//System.err.printf("GoAWAY: %s\n", ErrorFrame.stringForCode(frame.getErrorCode()));
|
||||||
|
shutdown(new IOException("GOAWAY received"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCommon() {
|
||||||
|
clientSettings = client2.getClientSettings();
|
||||||
|
|
||||||
|
// serverSettings will be updated by server
|
||||||
|
serverSettings = SettingsFrame.getDefaultSettings();
|
||||||
|
hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));
|
||||||
|
hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max frame size we are allowed to send
|
||||||
|
*/
|
||||||
|
public int getMaxSendFrameSize() {
|
||||||
|
int param = serverSettings.getParameter(MAX_FRAME_SIZE);
|
||||||
|
if (param == -1) {
|
||||||
|
param = DEFAULT_FRAME_SIZE;
|
||||||
|
}
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max frame size we will receive
|
||||||
|
*/
|
||||||
|
public int getMaxReceiveFrameSize() {
|
||||||
|
return clientSettings.getParameter(MAX_FRAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not sure how useful this is.
|
||||||
|
public int getMaxHeadersSize() {
|
||||||
|
return serverSettings.getParameter(MAX_HEADER_LIST_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String CLIENT_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
|
||||||
|
|
||||||
|
private static final byte[] PREFACE_BYTES =
|
||||||
|
CLIENT_PREFACE.getBytes(StandardCharsets.ISO_8859_1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends Connection preface and Settings frame with current preferred
|
||||||
|
* values
|
||||||
|
*/
|
||||||
|
private void sendConnectionPreface() throws IOException {
|
||||||
|
ByteBufferGenerator bg = new ByteBufferGenerator(this);
|
||||||
|
bg.getBuffer(PREFACE_BYTES.length).put(PREFACE_BYTES);
|
||||||
|
ByteBuffer[] ba = bg.getBufferArray();
|
||||||
|
connection.write(ba, 0, ba.length);
|
||||||
|
|
||||||
|
bg = new ByteBufferGenerator(this);
|
||||||
|
SettingsFrame sf = client2.getClientSettings();
|
||||||
|
Log.logFrames(sf, "OUT");
|
||||||
|
sf.writeOutgoing(bg);
|
||||||
|
WindowUpdateFrame wup = new WindowUpdateFrame();
|
||||||
|
wup.streamid(0);
|
||||||
|
// send a Window update for the receive buffer we are using
|
||||||
|
// minus the initial 64 K specified in protocol
|
||||||
|
wup.setUpdate(client2.client().getReceiveBufferSize() - (64 * 1024 - 1));
|
||||||
|
wup.computeLength();
|
||||||
|
wup.writeOutgoing(bg);
|
||||||
|
Log.logFrames(wup, "OUT");
|
||||||
|
ba = bg.getBufferArray();
|
||||||
|
connection.write(ba, 0, ba.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an existing Stream with given id, or null if doesn't exist
|
||||||
|
*/
|
||||||
|
Stream getStream(int streamid) {
|
||||||
|
return streams.get(streamid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Stream with given id.
|
||||||
|
*/
|
||||||
|
Stream createStream(Exchange exchange) {
|
||||||
|
Stream stream = new Stream(client, this, exchange);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream.PushedStream createPushStream(Stream parent, HttpRequestImpl pushReq) {
|
||||||
|
Stream.PushGroup<?> pg = parent.request.pushGroup();
|
||||||
|
return new Stream.PushedStream(pg, client, this, parent, pushReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putStream(Stream stream, int streamid) {
|
||||||
|
streams.put(streamid, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteStream(Stream stream) {
|
||||||
|
streams.remove(stream.streamid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int MAX_STREAM = Integer.MAX_VALUE - 2;
|
||||||
|
|
||||||
|
// Number of header bytes in a Headers Frame
|
||||||
|
final static int HEADERS_HEADER_SIZE = 15;
|
||||||
|
|
||||||
|
// Number of header bytes in a Continuation frame
|
||||||
|
final static int CONTIN_HEADER_SIZE = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the headers into a List<ByteBuffer> and then create HEADERS
|
||||||
|
* and CONTINUATION frames from the list and return the List<Http2Frame>.
|
||||||
|
*
|
||||||
|
* @param frame
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private LinkedList<Http2Frame> encodeHeaders(OutgoingHeaders frame) {
|
||||||
|
LinkedList<ByteBuffer> buffers = new LinkedList<>();
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
buffers.add(buf);
|
||||||
|
encodeHeadersImpl(frame.stream.getRequestPseudoHeaders(), buffers);
|
||||||
|
encodeHeadersImpl(frame.getUserHeaders(), buffers);
|
||||||
|
encodeHeadersImpl(frame.getSystemHeaders(), buffers);
|
||||||
|
|
||||||
|
for (ByteBuffer b : buffers) {
|
||||||
|
b.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedList<Http2Frame> frames = new LinkedList<>();
|
||||||
|
int maxframesize = getMaxSendFrameSize();
|
||||||
|
|
||||||
|
HeadersFrame oframe = new HeadersFrame();
|
||||||
|
oframe.setFlags(frame.getFlags());
|
||||||
|
oframe.streamid(frame.streamid());
|
||||||
|
|
||||||
|
oframe.setHeaderBlock(getBufferArray(buffers, maxframesize));
|
||||||
|
frames.add(oframe);
|
||||||
|
// Any buffers left?
|
||||||
|
boolean done = buffers.isEmpty();
|
||||||
|
if (done) {
|
||||||
|
oframe.setFlag(HeaderFrame.END_HEADERS);
|
||||||
|
} else {
|
||||||
|
ContinuationFrame cf = null;
|
||||||
|
while (!done) {
|
||||||
|
cf = new ContinuationFrame();
|
||||||
|
cf.streamid(frame.streamid());
|
||||||
|
cf.setHeaderBlock(getBufferArray(buffers, maxframesize));
|
||||||
|
frames.add(cf);
|
||||||
|
done = buffers.isEmpty();
|
||||||
|
}
|
||||||
|
cf.setFlag(HeaderFrame.END_HEADERS);
|
||||||
|
}
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
// should always return at least one buffer
|
||||||
|
private static ByteBuffer[] getBufferArray(LinkedList<ByteBuffer> list, int maxsize) {
|
||||||
|
assert maxsize >= BUFSIZE;
|
||||||
|
LinkedList<ByteBuffer> newlist = new LinkedList<>();
|
||||||
|
int size = list.size();
|
||||||
|
int nbytes = 0;
|
||||||
|
for (int i=0; i<size; i++) {
|
||||||
|
ByteBuffer buf = list.getFirst();
|
||||||
|
if (nbytes + buf.remaining() <= maxsize) {
|
||||||
|
nbytes += buf.remaining();
|
||||||
|
newlist.add(buf);
|
||||||
|
list.remove();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newlist.toArray(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode all the headers from the given HttpHeadersImpl into the given List.
|
||||||
|
*/
|
||||||
|
private void encodeHeadersImpl(HttpHeaders hdrs, LinkedList<ByteBuffer> buffers) {
|
||||||
|
ByteBuffer buffer;
|
||||||
|
if (!(buffer = buffers.getLast()).hasRemaining()) {
|
||||||
|
buffer = getBuffer();
|
||||||
|
buffers.add(buffer);
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, List<String>> e : hdrs.map().entrySet()) {
|
||||||
|
String key = e.getKey();
|
||||||
|
String lkey = key.toLowerCase();
|
||||||
|
List<String> values = e.getValue();
|
||||||
|
for (String value : values) {
|
||||||
|
hpackOut.header(lkey, value);
|
||||||
|
boolean encoded = false;
|
||||||
|
do {
|
||||||
|
encoded = hpackOut.encode(buffer);
|
||||||
|
if (!encoded) {
|
||||||
|
buffer = getBuffer();
|
||||||
|
buffers.add(buffer);
|
||||||
|
}
|
||||||
|
} while (!encoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendFrames(List<Http2Frame> frames) throws IOException, InterruptedException {
|
||||||
|
for (Http2Frame frame : frames) {
|
||||||
|
sendFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Throwable getExceptionFrom(CompletableFuture<?> cf) {
|
||||||
|
try {
|
||||||
|
cf.get();
|
||||||
|
return null;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (e.getCause() != null)
|
||||||
|
return e.getCause();
|
||||||
|
else
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void execute(Runnable r) {
|
||||||
|
executor.execute(r, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object sendlock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void sendFrame(Http2Frame frame) {
|
||||||
|
synchronized (sendlock) {
|
||||||
|
try {
|
||||||
|
if (frame instanceof OutgoingHeaders) {
|
||||||
|
OutgoingHeaders oh = (OutgoingHeaders) frame;
|
||||||
|
Stream stream = oh.getStream();
|
||||||
|
stream.registerStream(nextstreamid);
|
||||||
|
oh.streamid(nextstreamid);
|
||||||
|
nextstreamid += 2;
|
||||||
|
// set outgoing window here. This allows thread sending
|
||||||
|
// body to proceed.
|
||||||
|
stream.updateOutgoingWindow(getInitialSendWindowSize());
|
||||||
|
LinkedList<Http2Frame> frames = encodeHeaders(oh);
|
||||||
|
for (Http2Frame f : frames) {
|
||||||
|
sendOneFrame(f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendOneFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (!closed) {
|
||||||
|
Log.logError(e);
|
||||||
|
shutdown(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a frame.
|
||||||
|
*
|
||||||
|
* @param frame
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void sendOneFrame(Http2Frame frame) throws IOException {
|
||||||
|
ByteBufferGenerator bbg = new ByteBufferGenerator(this);
|
||||||
|
frame.computeLength();
|
||||||
|
Log.logFrames(frame, "OUT");
|
||||||
|
frame.writeOutgoing(bbg);
|
||||||
|
ByteBuffer[] currentBufs = bbg.getBufferArray();
|
||||||
|
connection.write(currentBufs, 0, currentBufs.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SettingsFrame getAckFrame(int streamid) {
|
||||||
|
SettingsFrame frame = new SettingsFrame();
|
||||||
|
frame.setFlag(SettingsFrame.ACK);
|
||||||
|
frame.streamid(streamid);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HeaderDecoder implements DecodingCallback {
|
||||||
|
HttpHeadersImpl headers;
|
||||||
|
|
||||||
|
HeaderDecoder() {
|
||||||
|
this.headers = new HttpHeadersImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDecoded(CharSequence name, CharSequence value) {
|
||||||
|
headers.addHeader(name.toString(), value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeadersImpl headers() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When sending a frame, the length field must be set in sub-class
|
||||||
|
* by calling computeLength()
|
||||||
|
*/
|
||||||
|
abstract class Http2Frame {
|
||||||
|
|
||||||
|
int length = -1;
|
||||||
|
int type;
|
||||||
|
int streamid;
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
// called when reading in only
|
||||||
|
void initCommon(int length, int type, int streamid, int flags) {
|
||||||
|
this.length = length;
|
||||||
|
this.type = type;
|
||||||
|
this.streamid = streamid;
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int streamid() {
|
||||||
|
return streamid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlag(int flag) {
|
||||||
|
flags |= flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlags(int flags) {
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getFlag(int flag) {
|
||||||
|
return (flags & flag) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearFlag(int flag) {
|
||||||
|
flags &= 0xffffffff ^ flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void streamid(int streamid) {
|
||||||
|
this.streamid = streamid;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void readIncomingImpl(ByteBufferConsumer bc) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* assume given array contains at least one complete frame.
|
||||||
|
*/
|
||||||
|
static Http2Frame readIncoming(ByteBufferConsumer bc) throws IOException {
|
||||||
|
int x = bc.getInt();
|
||||||
|
int length = x >> 8;
|
||||||
|
int type = x & 0xff;
|
||||||
|
int flags = bc.getByte();
|
||||||
|
int streamid = bc.getInt();
|
||||||
|
Http2Frame f = null;
|
||||||
|
switch (type) {
|
||||||
|
case DataFrame.TYPE:
|
||||||
|
f = new DataFrame();
|
||||||
|
break;
|
||||||
|
case HeadersFrame.TYPE:
|
||||||
|
f = new HeadersFrame();
|
||||||
|
break;
|
||||||
|
case ContinuationFrame.TYPE:
|
||||||
|
f = new ContinuationFrame();
|
||||||
|
break;
|
||||||
|
case ResetFrame.TYPE:
|
||||||
|
f = new ResetFrame();
|
||||||
|
break;
|
||||||
|
case PriorityFrame.TYPE:
|
||||||
|
f = new PriorityFrame();
|
||||||
|
break;
|
||||||
|
case SettingsFrame.TYPE:
|
||||||
|
f = new SettingsFrame();
|
||||||
|
break;
|
||||||
|
case GoAwayFrame.TYPE:
|
||||||
|
f = new GoAwayFrame();
|
||||||
|
break;
|
||||||
|
case PingFrame.TYPE:
|
||||||
|
f = new PingFrame();
|
||||||
|
break;
|
||||||
|
case PushPromiseFrame.TYPE:
|
||||||
|
f = new PushPromiseFrame();
|
||||||
|
break;
|
||||||
|
case WindowUpdateFrame.TYPE:
|
||||||
|
f = new WindowUpdateFrame();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
String msg = Integer.toString(type);
|
||||||
|
throw new IOException("unknown frame type " + msg);
|
||||||
|
}
|
||||||
|
f.initCommon(length, type, streamid, flags);
|
||||||
|
f.readIncomingImpl(bc);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String typeAsString() {
|
||||||
|
return asString(this.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String asString(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case DataFrame.TYPE:
|
||||||
|
return "DATA";
|
||||||
|
case HeadersFrame.TYPE:
|
||||||
|
return "HEADERS";
|
||||||
|
case ContinuationFrame.TYPE:
|
||||||
|
return "CONTINUATION";
|
||||||
|
case ResetFrame.TYPE:
|
||||||
|
return "RESET";
|
||||||
|
case PriorityFrame.TYPE:
|
||||||
|
return "PRIORITY";
|
||||||
|
case SettingsFrame.TYPE:
|
||||||
|
return "SETTINGS";
|
||||||
|
case GoAwayFrame.TYPE:
|
||||||
|
return "GOAWAY";
|
||||||
|
case PingFrame.TYPE:
|
||||||
|
return "PING";
|
||||||
|
case PushPromiseFrame.TYPE:
|
||||||
|
return "PUSH_PROMISE";
|
||||||
|
case WindowUpdateFrame.TYPE:
|
||||||
|
return "WINDOW_UPDATE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(typeAsString())
|
||||||
|
.append(": length=")
|
||||||
|
.append(Integer.toString(length))
|
||||||
|
.append(", streamid=")
|
||||||
|
.append(streamid)
|
||||||
|
.append(", flags=");
|
||||||
|
|
||||||
|
int f = flags;
|
||||||
|
int i = 0;
|
||||||
|
if (f == 0) {
|
||||||
|
sb.append("0 ");
|
||||||
|
} else {
|
||||||
|
while (f != 0) {
|
||||||
|
if ((f & 1) == 1) {
|
||||||
|
sb.append(flagAsString(1 << i))
|
||||||
|
.append(' ');
|
||||||
|
}
|
||||||
|
f = f >> 1;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override
|
||||||
|
String flagAsString(int f) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void computeLength();
|
||||||
|
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
if (length == -1) {
|
||||||
|
throw new InternalError("Length not set on outgoing frame");
|
||||||
|
}
|
||||||
|
ByteBuffer buf = bg.getBuffer(9);
|
||||||
|
int x = (length << 8) + type;
|
||||||
|
buf.putInt(x);
|
||||||
|
buf.put((byte)flags);
|
||||||
|
buf.putInt(streamid);
|
||||||
|
}
|
||||||
|
}
|
@ -23,28 +23,32 @@
|
|||||||
*/
|
*/
|
||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import static java.net.http.Utils.BUFSIZE;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import static java.nio.channels.SelectionKey.OP_CONNECT;
|
|
||||||
import static java.nio.channels.SelectionKey.OP_READ;
|
|
||||||
import static java.nio.channels.SelectionKey.OP_WRITE;
|
|
||||||
import java.nio.channels.Selector;
|
import java.nio.channels.Selector;
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import java.util.stream.Stream;
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
|
import static java.net.http.Utils.BUFSIZE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client implementation. Contains all configuration information and also
|
* Client implementation. Contains all configuration information and also
|
||||||
@ -53,6 +57,9 @@ import javax.net.ssl.SSLParameters;
|
|||||||
*/
|
*/
|
||||||
class HttpClientImpl extends HttpClient implements BufferHandler {
|
class HttpClientImpl extends HttpClient implements BufferHandler {
|
||||||
|
|
||||||
|
private static final ThreadFactory defaultFactory =
|
||||||
|
(r -> new Thread(null, r, "HttpClient_worker", 0, true));
|
||||||
|
|
||||||
private final CookieManager cookieManager;
|
private final CookieManager cookieManager;
|
||||||
private final Redirect followRedirects;
|
private final Redirect followRedirects;
|
||||||
private final ProxySelector proxySelector;
|
private final ProxySelector proxySelector;
|
||||||
@ -67,7 +74,6 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
private final SelectorManager selmgr;
|
private final SelectorManager selmgr;
|
||||||
private final FilterFactory filters;
|
private final FilterFactory filters;
|
||||||
private final Http2ClientImpl client2;
|
private final Http2ClientImpl client2;
|
||||||
private static final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
|
|
||||||
private final LinkedList<TimeoutEvent> timeouts;
|
private final LinkedList<TimeoutEvent> timeouts;
|
||||||
|
|
||||||
public static HttpClientImpl create(HttpClientBuilderImpl builder) {
|
public static HttpClientImpl create(HttpClientBuilderImpl builder) {
|
||||||
@ -115,7 +121,6 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
throw new InternalError(e);
|
throw new InternalError(e);
|
||||||
}
|
}
|
||||||
selmgr.setDaemon(true);
|
selmgr.setDaemon(true);
|
||||||
selmgr.setName("HttpSelector");
|
|
||||||
filters = new FilterFactory();
|
filters = new FilterFactory();
|
||||||
initFilters();
|
initFilters();
|
||||||
}
|
}
|
||||||
@ -145,35 +150,56 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
return client2;
|
return client2;
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkedList<ByteBuffer> freelist = new LinkedList<>();
|
/**
|
||||||
|
* We keep one size of buffer on free list. That size may increase
|
||||||
|
* depending on demand. If that happens we dispose of free buffers
|
||||||
|
* that are smaller than new size.
|
||||||
|
*/
|
||||||
|
private final LinkedList<ByteBuffer> freelist = new LinkedList<>();
|
||||||
|
int currentSize = BUFSIZE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized ByteBuffer getBuffer() {
|
public synchronized ByteBuffer getBuffer(int size) {
|
||||||
if (freelist.isEmpty()) {
|
|
||||||
return ByteBuffer.allocate(BUFSIZE);
|
ByteBuffer buf;
|
||||||
|
if (size == -1)
|
||||||
|
size = currentSize;
|
||||||
|
|
||||||
|
if (size > currentSize)
|
||||||
|
currentSize = size;
|
||||||
|
|
||||||
|
while (!freelist.isEmpty()) {
|
||||||
|
buf = freelist.removeFirst();
|
||||||
|
if (buf.capacity() < currentSize)
|
||||||
|
continue;
|
||||||
|
buf.clear();
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
return freelist.removeFirst();
|
return ByteBuffer.allocate(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void returnBuffer(ByteBuffer buffer) {
|
public synchronized void returnBuffer(ByteBuffer buffer) {
|
||||||
buffer.clear();
|
|
||||||
freelist.add(buffer);
|
freelist.add(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setMinBufferSize(int n) {
|
||||||
|
currentSize = Math.max(n, currentSize);
|
||||||
|
}
|
||||||
|
|
||||||
// Main loop for this client's selector
|
// Main loop for this client's selector
|
||||||
|
private final class SelectorManager extends Thread {
|
||||||
|
|
||||||
class SelectorManager extends Thread {
|
private final Selector selector;
|
||||||
final Selector selector;
|
private volatile boolean closed;
|
||||||
boolean closed;
|
private final List<AsyncEvent> readyList;
|
||||||
|
private final List<AsyncEvent> registrations;
|
||||||
final List<AsyncEvent> readyList;
|
|
||||||
final List<AsyncEvent> registrations;
|
|
||||||
|
|
||||||
SelectorManager() throws IOException {
|
SelectorManager() throws IOException {
|
||||||
readyList = new LinkedList<>();
|
super(null, null, "SelectorManager", 0, false);
|
||||||
registrations = new LinkedList<>();
|
readyList = new ArrayList<>();
|
||||||
|
registrations = new ArrayList<>();
|
||||||
selector = Selector.open();
|
selector = Selector.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,32 +219,13 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
closed = true;
|
closed = true;
|
||||||
try {
|
try {
|
||||||
selector.close();
|
selector.close();
|
||||||
} catch (IOException e) {}
|
} catch (IOException ignored) { }
|
||||||
}
|
|
||||||
|
|
||||||
private List<AsyncEvent> copy(List<AsyncEvent> list) {
|
|
||||||
LinkedList<AsyncEvent> c = new LinkedList<>();
|
|
||||||
for (AsyncEvent e : list) {
|
|
||||||
c.add(e);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
String opvals(int i) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if ((i & OP_READ) != 0)
|
|
||||||
sb.append("OP_READ ");
|
|
||||||
if ((i & OP_CONNECT) != 0)
|
|
||||||
sb.append("OP_CONNECT ");
|
|
||||||
if ((i & OP_WRITE) != 0)
|
|
||||||
sb.append("OP_WRITE ");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
for (AsyncEvent exchange : registrations) {
|
for (AsyncEvent exchange : registrations) {
|
||||||
SelectableChannel c = exchange.channel();
|
SelectableChannel c = exchange.channel();
|
||||||
@ -229,7 +236,7 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
if (key == null) {
|
if (key == null) {
|
||||||
sa = new SelectorAttachment(c, selector);
|
sa = new SelectorAttachment(c, selector);
|
||||||
} else {
|
} else {
|
||||||
sa = (SelectorAttachment)key.attachment();
|
sa = (SelectorAttachment) key.attachment();
|
||||||
}
|
}
|
||||||
sa.register(exchange);
|
sa.register(exchange);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -243,6 +250,7 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
}
|
}
|
||||||
long timeval = getTimeoutValue();
|
long timeval = getTimeoutValue();
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
|
//debugPrint(selector);
|
||||||
int n = selector.select(timeval);
|
int n = selector.select(timeval);
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
signalTimeouts(now);
|
signalTimeouts(now);
|
||||||
@ -251,7 +259,7 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
Set<SelectionKey> keys = selector.selectedKeys();
|
Set<SelectionKey> keys = selector.selectedKeys();
|
||||||
|
|
||||||
for (SelectionKey key : keys) {
|
for (SelectionKey key : keys) {
|
||||||
SelectorAttachment sa = (SelectorAttachment)key.attachment();
|
SelectorAttachment sa = (SelectorAttachment) key.attachment();
|
||||||
int eventsOccurred = key.readyOps();
|
int eventsOccurred = key.readyOps();
|
||||||
sa.events(eventsOccurred).forEach(readyList::add);
|
sa.events(eventsOccurred).forEach(readyList::add);
|
||||||
sa.resetInterestOps(eventsOccurred);
|
sa.resetInterestOps(eventsOccurred);
|
||||||
@ -260,10 +268,8 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
selector.selectedKeys().clear();
|
selector.selectedKeys().clear();
|
||||||
|
|
||||||
for (AsyncEvent exchange : readyList) {
|
for (AsyncEvent exchange : readyList) {
|
||||||
if (exchange instanceof AsyncEvent.Blocking) {
|
if (exchange.blocking()) {
|
||||||
exchange.channel().configureBlocking(true);
|
exchange.channel().configureBlocking(true);
|
||||||
} else {
|
|
||||||
assert exchange instanceof AsyncEvent.NonBlocking;
|
|
||||||
}
|
}
|
||||||
executor.synchronize();
|
executor.synchronize();
|
||||||
handleEvent(exchange); // will be delegated to executor
|
handleEvent(exchange); // will be delegated to executor
|
||||||
@ -272,14 +278,26 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
System.err.println("HttpClientImpl terminating on error");
|
|
||||||
// This terminates thread. So, better just print stack trace
|
// This terminates thread. So, better just print stack trace
|
||||||
String err = Utils.stackTrace(e);
|
String err = Utils.stackTrace(e);
|
||||||
Log.logError("HttpClientImpl: fatal error: " + err);
|
Log.logError("HttpClientImpl: fatal error: " + err);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void debugPrint(Selector selector) {
|
||||||
|
System.err.println("Selector: debugprint start");
|
||||||
|
Set<SelectionKey> keys = selector.keys();
|
||||||
|
for (SelectionKey key : keys) {
|
||||||
|
SelectableChannel c = key.channel();
|
||||||
|
int ops = key.interestOps();
|
||||||
|
System.err.printf("selector chan:%s ops:%d\n", c, ops);
|
||||||
|
}
|
||||||
|
System.err.println("Selector: debugprint end");
|
||||||
|
}
|
||||||
|
|
||||||
void handleEvent(AsyncEvent e) {
|
void handleEvent(AsyncEvent e) {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
e.abort();
|
e.abort();
|
||||||
@ -303,7 +321,7 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
private final SelectableChannel chan;
|
private final SelectableChannel chan;
|
||||||
private final Selector selector;
|
private final Selector selector;
|
||||||
private final ArrayList<AsyncEvent> pending;
|
private final ArrayList<AsyncEvent> pending;
|
||||||
private int interestops;
|
private int interestOps;
|
||||||
|
|
||||||
SelectorAttachment(SelectableChannel chan, Selector selector) {
|
SelectorAttachment(SelectableChannel chan, Selector selector) {
|
||||||
this.pending = new ArrayList<>();
|
this.pending = new ArrayList<>();
|
||||||
@ -312,53 +330,53 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void register(AsyncEvent e) throws ClosedChannelException {
|
void register(AsyncEvent e) throws ClosedChannelException {
|
||||||
int newops = e.interestOps();
|
int newOps = e.interestOps();
|
||||||
boolean reRegister = (interestops & newops) != newops;
|
boolean reRegister = (interestOps & newOps) != newOps;
|
||||||
interestops |= newops;
|
interestOps |= newOps;
|
||||||
pending.add(e);
|
pending.add(e);
|
||||||
if (reRegister) {
|
if (reRegister) {
|
||||||
// first time registration happens here also
|
// first time registration happens here also
|
||||||
chan.register(selector, interestops, this);
|
chan.register(selector, interestOps, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int interestOps() {
|
|
||||||
return interestops;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Stream<AsyncEvents> containing only events that are
|
* Returns a Stream<AsyncEvents> containing only events that are
|
||||||
* registered with the given {@code interestop}.
|
* registered with the given {@code interestOps}.
|
||||||
*/
|
*/
|
||||||
Stream<AsyncEvent> events(int interestop) {
|
Stream<AsyncEvent> events(int interestOps) {
|
||||||
return pending.stream()
|
return pending.stream()
|
||||||
.filter(ev -> (ev.interestOps() & interestop) != 0);
|
.filter(ev -> (ev.interestOps() & interestOps) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any events with the given {@code interestop}, and if no
|
* Removes any events with the given {@code interestOps}, and if no
|
||||||
* events remaining, cancels the associated SelectionKey.
|
* events remaining, cancels the associated SelectionKey.
|
||||||
*/
|
*/
|
||||||
void resetInterestOps(int interestop) {
|
void resetInterestOps(int interestOps) {
|
||||||
int newops = 0;
|
int newOps = 0;
|
||||||
|
|
||||||
Iterator<AsyncEvent> itr = pending.iterator();
|
Iterator<AsyncEvent> itr = pending.iterator();
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
AsyncEvent event = itr.next();
|
AsyncEvent event = itr.next();
|
||||||
int evops = event.interestOps();
|
int evops = event.interestOps();
|
||||||
if ((evops & interestop) != 0) {
|
if (event.repeating()) {
|
||||||
|
newOps |= evops;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((evops & interestOps) != 0) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
} else {
|
} else {
|
||||||
newops |= evops;
|
newOps |= evops;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interestops = newops;
|
this.interestOps = newOps;
|
||||||
SelectionKey key = chan.keyFor(selector);
|
SelectionKey key = chan.keyFor(selector);
|
||||||
if (newops == 0) {
|
if (newOps == 0) {
|
||||||
key.cancel();
|
key.cancel();
|
||||||
} else {
|
} else {
|
||||||
key.interestOps(newops);
|
key.interestOps(newOps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -366,7 +384,8 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
/**
|
/**
|
||||||
* Creates a HttpRequest associated with this group.
|
* Creates a HttpRequest associated with this group.
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException if the group has been stopped
|
* @throws IllegalStateException
|
||||||
|
* if the group has been stopped
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public HttpRequestBuilderImpl request() {
|
public HttpRequestBuilderImpl request() {
|
||||||
@ -376,7 +395,8 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
/**
|
/**
|
||||||
* Creates a HttpRequest associated with this group.
|
* Creates a HttpRequest associated with this group.
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException if the group has been stopped
|
* @throws IllegalStateException
|
||||||
|
* if the group has been stopped
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public HttpRequestBuilderImpl request(URI uri) {
|
public HttpRequestBuilderImpl request(URI uri) {
|
||||||
@ -444,16 +464,12 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
return version.equals(Version.HTTP_2);
|
return version.equals(Version.HTTP_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
//void setHttp2NotSupported(String host) {
|
private void initFilters() {
|
||||||
//http2NotSupported.put(host, false);
|
|
||||||
//}
|
|
||||||
|
|
||||||
final void initFilters() {
|
|
||||||
addFilter(AuthenticationFilter.class);
|
addFilter(AuthenticationFilter.class);
|
||||||
addFilter(RedirectFilter.class);
|
addFilter(RedirectFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
final void addFilter(Class<? extends HeaderFilter> f) {
|
private void addFilter(Class<? extends HeaderFilter> f) {
|
||||||
filters.addFilter(f);
|
filters.addFilter(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,14 +495,14 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
iter.previous();
|
iter.previous();
|
||||||
break;
|
break;
|
||||||
} else if (!iter.hasNext()) {
|
} else if (!iter.hasNext()) {
|
||||||
event.delta = event.timeval - listval ;
|
event.delta = event.timeval - listval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
iter.add(event);
|
iter.add(event);
|
||||||
selmgr.wakeupSelector();
|
selmgr.wakeupSelector();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void signalTimeouts(long then) {
|
private synchronized void signalTimeouts(long then) {
|
||||||
if (timeouts.isEmpty()) {
|
if (timeouts.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -532,12 +548,12 @@ class HttpClientImpl extends HttpClient implements BufferHandler {
|
|||||||
// used for the connection window
|
// used for the connection window
|
||||||
int getReceiveBufferSize() {
|
int getReceiveBufferSize() {
|
||||||
return Utils.getIntegerNetProperty(
|
return Utils.getIntegerNetProperty(
|
||||||
"sun.net.httpclient.connectionWindowSize", 256 * 1024
|
"java.net.httpclient.connectionWindowSize", 256 * 1024
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns 0 meaning block forever, or a number of millis to block for
|
// returns 0 meaning block forever, or a number of millis to block for
|
||||||
synchronized long getTimeoutValue() {
|
private synchronized long getTimeoutValue() {
|
||||||
if (timeouts.isEmpty()) {
|
if (timeouts.isEmpty()) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,9 +23,8 @@
|
|||||||
*/
|
*/
|
||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
@ -42,7 +41,17 @@ import javax.net.ssl.SSLParameters;
|
|||||||
* SSLConnection: TLS channel direct to server
|
* SSLConnection: TLS channel direct to server
|
||||||
* SSLTunnelConnection: TLS channel via (CONNECT) proxy tunnel
|
* SSLTunnelConnection: TLS channel via (CONNECT) proxy tunnel
|
||||||
*/
|
*/
|
||||||
abstract class HttpConnection implements BufferHandler {
|
abstract class HttpConnection implements BufferHandler, Closeable {
|
||||||
|
|
||||||
|
protected final static ByteBuffer emptyBuf = Utils.EMPTY_BYTEBUFFER;
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
BLOCKING,
|
||||||
|
NON_BLOCKING,
|
||||||
|
ASYNC
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Mode mode;
|
||||||
|
|
||||||
// address we are connected to. Could be a server or a proxy
|
// address we are connected to. Could be a server or a proxy
|
||||||
final InetSocketAddress address;
|
final InetSocketAddress address;
|
||||||
@ -52,6 +61,7 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
HttpConnection(InetSocketAddress address, HttpClientImpl client) {
|
HttpConnection(InetSocketAddress address, HttpClientImpl client) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.buffer = emptyBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,7 +78,21 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
*/
|
*/
|
||||||
public static HttpConnection getConnection(InetSocketAddress addr,
|
public static HttpConnection getConnection(InetSocketAddress addr,
|
||||||
HttpRequestImpl request) {
|
HttpRequestImpl request) {
|
||||||
return getConnectionImpl(addr, request);
|
return getConnectionImpl(addr, request, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called specifically to get an async connection for HTTP/2 over SSL.
|
||||||
|
*
|
||||||
|
* @param addr
|
||||||
|
* @param request
|
||||||
|
* @param http2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static HttpConnection getConnection(InetSocketAddress addr,
|
||||||
|
HttpRequestImpl request, Http2Connection http2) {
|
||||||
|
|
||||||
|
return getConnectionImpl(addr, request, http2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void connect() throws IOException, InterruptedException;
|
public abstract void connect() throws IOException, InterruptedException;
|
||||||
@ -93,7 +117,7 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
// at beginning of response.
|
// at beginning of response.
|
||||||
ByteBuffer getRemaining() {
|
ByteBuffer getRemaining() {
|
||||||
ByteBuffer b = buffer;
|
ByteBuffer b = buffer;
|
||||||
buffer = null;
|
buffer = emptyBuf;
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,17 +147,18 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static HttpConnection getSSLConnection(InetSocketAddress addr,
|
private static HttpConnection getSSLConnection(InetSocketAddress addr,
|
||||||
InetSocketAddress proxy,
|
InetSocketAddress proxy, HttpRequestImpl request,
|
||||||
HttpRequestImpl request,
|
String[] alpn, Http2Connection http2) {
|
||||||
String[] alpn) {
|
|
||||||
HttpClientImpl client = request.client();
|
HttpClientImpl client = request.client();
|
||||||
if (proxy != null) {
|
if (proxy != null) {
|
||||||
return new SSLTunnelConnection(addr,
|
return new SSLTunnelConnection(addr,
|
||||||
client,
|
client,
|
||||||
proxy,
|
proxy,
|
||||||
request.getAccessControlContext());
|
request.getAccessControlContext());
|
||||||
} else {
|
} else if (http2 == null) {
|
||||||
return new SSLConnection(addr, client, alpn);
|
return new SSLConnection(addr, client, alpn);
|
||||||
|
} else {
|
||||||
|
return new AsyncSSLConnection(addr, client, alpn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +167,8 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
* none available.
|
* none available.
|
||||||
*/
|
*/
|
||||||
private static HttpConnection getConnectionImpl(InetSocketAddress addr,
|
private static HttpConnection getConnectionImpl(InetSocketAddress addr,
|
||||||
HttpRequestImpl request) {
|
HttpRequestImpl request, Http2Connection http2) {
|
||||||
|
|
||||||
HttpConnection c;
|
HttpConnection c;
|
||||||
HttpClientImpl client = request.client();
|
HttpClientImpl client = request.client();
|
||||||
InetSocketAddress proxy = request.proxy();
|
InetSocketAddress proxy = request.proxy();
|
||||||
@ -167,7 +193,7 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
if (c != null) {
|
if (c != null) {
|
||||||
return c;
|
return c;
|
||||||
} else {
|
} else {
|
||||||
return getSSLConnection(addr, proxy, request, alpn);
|
return getSSLConnection(addr, proxy, request, alpn, http2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,64 +249,16 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
void configureBlocking(boolean mode) throws IOException {
|
synchronized void configureMode(Mode mode) throws IOException {
|
||||||
channel().configureBlocking(mode);
|
this.mode = mode;
|
||||||
|
if (mode == Mode.BLOCKING)
|
||||||
|
channel().configureBlocking(true);
|
||||||
|
else
|
||||||
|
channel().configureBlocking(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract ConnectionPool.CacheKey cacheKey();
|
abstract ConnectionPool.CacheKey cacheKey();
|
||||||
|
|
||||||
/*
|
|
||||||
static PrintStream ps;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
String propval = Utils.getNetProperty("java.net.httpclient.showData");
|
|
||||||
if (propval != null && propval.equalsIgnoreCase("true")) {
|
|
||||||
ps = new PrintStream(new FileOutputStream("/tmp/httplog.txt"), false);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized final void debugPrint(String s, ByteBuffer b) {
|
|
||||||
ByteBuffer[] bufs = new ByteBuffer[1];
|
|
||||||
bufs[0] = b;
|
|
||||||
debugPrint(s, bufs, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized final void debugPrint(String s,
|
|
||||||
ByteBuffer[] bufs,
|
|
||||||
int start,
|
|
||||||
int number) {
|
|
||||||
if (ps == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.printf("\n%s:\n", s);
|
|
||||||
|
|
||||||
for (int i=start; i<start+number; i++) {
|
|
||||||
ByteBuffer b = bufs[i].duplicate();
|
|
||||||
while (b.hasRemaining()) {
|
|
||||||
int c = b.get();
|
|
||||||
if (c == 10) {
|
|
||||||
ps.printf("LF \n");
|
|
||||||
} else if (c == 13) {
|
|
||||||
ps.printf(" CR ");
|
|
||||||
} else if (c == 0x20) {
|
|
||||||
ps.printf("_");
|
|
||||||
} else if (c > 0x20 && c <= 0x7F) {
|
|
||||||
ps.printf("%c", (char)c);
|
|
||||||
} else {
|
|
||||||
ps.printf("0x%02x ", c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ps.printf("\n---------------------\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// overridden in SSL only
|
// overridden in SSL only
|
||||||
SSLParameters sslParameters() {
|
SSLParameters sslParameters() {
|
||||||
return null;
|
return null;
|
||||||
@ -296,7 +274,8 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
/**
|
/**
|
||||||
* Closes this connection, by returning the socket to its connection pool.
|
* Closes this connection, by returning the socket to its connection pool.
|
||||||
*/
|
*/
|
||||||
abstract void close();
|
@Override
|
||||||
|
public abstract void close();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a ByteBuffer with data, or null if EOF.
|
* Returns a ByteBuffer with data, or null if EOF.
|
||||||
@ -356,12 +335,17 @@ abstract class HttpConnection implements BufferHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final ByteBuffer getBuffer() {
|
public final ByteBuffer getBuffer(int n) {
|
||||||
return client.getBuffer();
|
return client.getBuffer(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void returnBuffer(ByteBuffer buffer) {
|
public final void returnBuffer(ByteBuffer buffer) {
|
||||||
client.returnBuffer(buffer);
|
client.returnBuffer(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setMinBufferSize(int n) {
|
||||||
|
client.setMinBufferSize(n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,44 +24,22 @@
|
|||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of HttpHeaders.
|
* Implementation of HttpHeaders.
|
||||||
*/
|
*/
|
||||||
class HttpHeadersImpl implements HttpHeaders1 {
|
class HttpHeadersImpl implements HttpHeaders {
|
||||||
|
|
||||||
private final HashMap<String,List<String>> headers;
|
private final TreeMap<String,List<String>> headers;
|
||||||
private boolean isUnmodifiable = false;
|
|
||||||
|
|
||||||
public HttpHeadersImpl() {
|
public HttpHeadersImpl() {
|
||||||
headers = new HashMap<>();
|
headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace all List<String> in headers with unmodifiable Lists. Call
|
|
||||||
* this only after all headers are added. The headers HashMap
|
|
||||||
* is wrapped with an unmodifiable HashMap in map()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void makeUnmodifiable() {
|
|
||||||
if (isUnmodifiable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Set<String> keys = new HashSet<>(headers.keySet());
|
|
||||||
for (String key : keys) {
|
|
||||||
List<String> values = headers.remove(key);
|
|
||||||
if (values != null) {
|
|
||||||
headers.put(key, Collections.unmodifiableList(values));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isUnmodifiable = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -88,7 +66,7 @@ class HttpHeadersImpl implements HttpHeaders1 {
|
|||||||
|
|
||||||
public HttpHeadersImpl deepCopy() {
|
public HttpHeadersImpl deepCopy() {
|
||||||
HttpHeadersImpl h1 = new HttpHeadersImpl();
|
HttpHeadersImpl h1 = new HttpHeadersImpl();
|
||||||
HashMap<String,List<String>> headers1 = h1.headers;
|
TreeMap<String,List<String>> headers1 = h1.headers;
|
||||||
Set<String> keys = headers.keySet();
|
Set<String> keys = headers.keySet();
|
||||||
for (String key : keys) {
|
for (String key : keys) {
|
||||||
List<String> vals = headers.get(key);
|
List<String> vals = headers.get(key);
|
||||||
@ -98,22 +76,13 @@ class HttpHeadersImpl implements HttpHeaders1 {
|
|||||||
return h1;
|
return h1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getOrCreate(String name) {
|
|
||||||
List<String> l = headers.get(name);
|
|
||||||
if (l == null) {
|
|
||||||
l = new LinkedList<>();
|
|
||||||
headers.put(name, l);
|
|
||||||
}
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addHeader(String name, String value) {
|
void addHeader(String name, String value) {
|
||||||
List<String> l = getOrCreate(name);
|
headers.computeIfAbsent(name, k -> new LinkedList<>())
|
||||||
l.add(value);
|
.add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHeader(String name, String value) {
|
void setHeader(String name, String value) {
|
||||||
List<String> l = getOrCreate(name);
|
List<String> l = headers.computeIfAbsent(name, k -> new LinkedList<>());
|
||||||
l.clear();
|
l.clear();
|
||||||
l.add(value);
|
l.add(value);
|
||||||
}
|
}
|
||||||
@ -122,7 +91,7 @@ class HttpHeadersImpl implements HttpHeaders1 {
|
|||||||
public Optional<Long> firstValueAsLong(String name) {
|
public Optional<Long> firstValueAsLong(String name) {
|
||||||
List<String> l = headers.get(name);
|
List<String> l = headers.get(name);
|
||||||
if (l == null) {
|
if (l == null) {
|
||||||
return Optional.ofNullable(null);
|
return Optional.empty();
|
||||||
} else {
|
} else {
|
||||||
String v = l.get(0);
|
String v = l.get(0);
|
||||||
Long lv = Long.parseLong(v);
|
Long lv = Long.parseLong(v);
|
||||||
|
@ -39,10 +39,11 @@ class HttpRequestBuilderImpl extends HttpRequest.Builder {
|
|||||||
private HttpClient.Version version;
|
private HttpClient.Version version;
|
||||||
private final HttpClientImpl client;
|
private final HttpClientImpl client;
|
||||||
private ProxySelector proxy;
|
private ProxySelector proxy;
|
||||||
private long timeval = 0;
|
private long timeval;
|
||||||
|
|
||||||
public HttpRequestBuilderImpl(HttpClientImpl client, URI uri) {
|
public HttpRequestBuilderImpl(HttpClientImpl client, URI uri) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
checkURI(uri);
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.version = client.version();
|
this.version = client.version();
|
||||||
this.userHeaders = new HttpHeadersImpl();
|
this.userHeaders = new HttpHeadersImpl();
|
||||||
@ -58,10 +59,17 @@ class HttpRequestBuilderImpl extends HttpRequest.Builder {
|
|||||||
@Override
|
@Override
|
||||||
public HttpRequestBuilderImpl uri(URI uri) {
|
public HttpRequestBuilderImpl uri(URI uri) {
|
||||||
Objects.requireNonNull(uri);
|
Objects.requireNonNull(uri);
|
||||||
|
checkURI(uri);
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkURI(URI uri) {
|
||||||
|
String scheme = uri.getScheme().toLowerCase();
|
||||||
|
if (!scheme.equals("https") && !scheme.equals("http"))
|
||||||
|
throw new IllegalArgumentException("invalid URI scheme");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequestBuilderImpl followRedirects(HttpClient.Redirect follow) {
|
public HttpRequestBuilderImpl followRedirects(HttpClient.Redirect follow) {
|
||||||
Objects.requireNonNull(follow);
|
Objects.requireNonNull(follow);
|
||||||
|
@ -30,16 +30,14 @@ import java.net.URI;
|
|||||||
import java.net.http.HttpClient.Version;
|
import java.net.http.HttpClient.Version;
|
||||||
import java.net.http.HttpResponse.MultiProcessor;
|
import java.net.http.HttpResponse.MultiProcessor;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.net.SocketPermission;
|
|
||||||
import java.security.AccessControlContext;
|
import java.security.AccessControlContext;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.util.Set;
|
|
||||||
import static java.net.http.HttpRedirectImpl.getRedirects;
|
import static java.net.http.HttpRedirectImpl.getRedirects;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
class HttpRequestImpl extends HttpRequest {
|
class HttpRequestImpl extends HttpRequest {
|
||||||
|
|
||||||
private final HttpHeadersImpl userHeaders;
|
private final ImmutableHeaders userHeaders;
|
||||||
private final HttpHeadersImpl systemHeaders;
|
private final HttpHeadersImpl systemHeaders;
|
||||||
private final URI uri;
|
private final URI uri;
|
||||||
private InetSocketAddress authority; // only used when URI not specified
|
private InetSocketAddress authority; // only used when URI not specified
|
||||||
@ -56,6 +54,7 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
private boolean receiving;
|
private boolean receiving;
|
||||||
private AccessControlContext acc;
|
private AccessControlContext acc;
|
||||||
private final long timeval;
|
private final long timeval;
|
||||||
|
private Stream.PushGroup<?> pushGroup;
|
||||||
|
|
||||||
public HttpRequestImpl(HttpClientImpl client,
|
public HttpRequestImpl(HttpClientImpl client,
|
||||||
String method,
|
String method,
|
||||||
@ -63,8 +62,8 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
this.client = client;
|
this.client = client;
|
||||||
this.method = method == null? "GET" : method;
|
this.method = method == null? "GET" : method;
|
||||||
this.userHeaders = builder.headers() == null ?
|
this.userHeaders = builder.headers() == null ?
|
||||||
new HttpHeadersImpl() : builder.headers();
|
new ImmutableHeaders() :
|
||||||
dropDisallowedHeaders();
|
new ImmutableHeaders(builder.headers(), Utils.ALLOWED_HEADERS);
|
||||||
this.followRedirects = getRedirects(builder.followRedirects() == null ?
|
this.followRedirects = getRedirects(builder.followRedirects() == null ?
|
||||||
client.followRedirects() : builder.followRedirects());
|
client.followRedirects() : builder.followRedirects());
|
||||||
this.systemHeaders = new HttpHeadersImpl();
|
this.systemHeaders = new HttpHeadersImpl();
|
||||||
@ -90,15 +89,13 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
HttpRequestImpl other) {
|
HttpRequestImpl other) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.method = method == null? "GET" : method;
|
this.method = method == null? "GET" : method;
|
||||||
this.userHeaders = other.userHeaders == null ?
|
this.userHeaders = other.userHeaders;
|
||||||
new HttpHeadersImpl() : other.userHeaders;
|
|
||||||
dropDisallowedHeaders();
|
|
||||||
this.followRedirects = getRedirects(other.followRedirects() == null ?
|
this.followRedirects = getRedirects(other.followRedirects() == null ?
|
||||||
client.followRedirects() : other.followRedirects());
|
client.followRedirects() : other.followRedirects());
|
||||||
this.systemHeaders = other.systemHeaders;
|
this.systemHeaders = other.systemHeaders;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.expectContinue = other.expectContinue;
|
this.expectContinue = other.expectContinue;
|
||||||
this.secure = other.secure;
|
this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
|
||||||
this.requestProcessor = other.requestProcessor;
|
this.requestProcessor = other.requestProcessor;
|
||||||
this.proxy = other.proxy;
|
this.proxy = other.proxy;
|
||||||
this.version = other.version;
|
this.version = other.version;
|
||||||
@ -115,7 +112,7 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
this.method = method;
|
this.method = method;
|
||||||
this.followRedirects = getRedirects(client.followRedirects());
|
this.followRedirects = getRedirects(client.followRedirects());
|
||||||
this.systemHeaders = new HttpHeadersImpl();
|
this.systemHeaders = new HttpHeadersImpl();
|
||||||
this.userHeaders = new HttpHeadersImpl();
|
this.userHeaders = new ImmutableHeaders();
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
this.proxy = null;
|
this.proxy = null;
|
||||||
this.requestProcessor = HttpRequest.noBody();
|
this.requestProcessor = HttpRequest.noBody();
|
||||||
@ -132,16 +129,52 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a HttpRequestImpl from the given set of Headers and the associated
|
||||||
|
* "parent" request. Fields not taken from the headers are taken from the
|
||||||
|
* parent.
|
||||||
|
*/
|
||||||
|
static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
|
||||||
|
HttpHeadersImpl headers) throws IOException {
|
||||||
|
|
||||||
|
return new HttpRequestImpl(parent, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// only used for push requests
|
||||||
|
private HttpRequestImpl(HttpRequestImpl parent, HttpHeadersImpl headers) throws IOException {
|
||||||
|
this.method = headers.firstValue(":method")
|
||||||
|
.orElseThrow(() -> new IOException("No method in Push Promise"));
|
||||||
|
String path = headers.firstValue(":path")
|
||||||
|
.orElseThrow(() -> new IOException("No path in Push Promise"));
|
||||||
|
String scheme = headers.firstValue(":scheme")
|
||||||
|
.orElseThrow(() -> new IOException("No scheme in Push Promise"));
|
||||||
|
String authority = headers.firstValue(":authority")
|
||||||
|
.orElseThrow(() -> new IOException("No authority in Push Promise"));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(scheme).append("://").append(authority).append(path);
|
||||||
|
this.uri = URI.create(sb.toString());
|
||||||
|
|
||||||
|
this.client = parent.client;
|
||||||
|
this.userHeaders = new ImmutableHeaders(headers, Utils.ALLOWED_HEADERS);
|
||||||
|
this.followRedirects = parent.followRedirects;
|
||||||
|
this.systemHeaders = parent.systemHeaders;
|
||||||
|
this.expectContinue = parent.expectContinue;
|
||||||
|
this.secure = parent.secure;
|
||||||
|
this.requestProcessor = parent.requestProcessor;
|
||||||
|
this.proxy = parent.proxy;
|
||||||
|
this.version = parent.version;
|
||||||
|
this.acc = parent.acc;
|
||||||
|
this.exchange = parent.exchange;
|
||||||
|
this.timeval = parent.timeval;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return (uri == null ? "" : uri.toString()) + "/" + method + "("
|
return (uri == null ? "" : uri.toString()) + " " + method;
|
||||||
+ hashCode() + ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders headers() {
|
public HttpHeaders headers() {
|
||||||
userHeaders.makeUnmodifiable();
|
|
||||||
return userHeaders;
|
return userHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,21 +187,6 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
|
systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Set<String> DISALLOWED_HEADERS_SET = Set.of(
|
|
||||||
"authorization", "connection", "cookie", "content-length",
|
|
||||||
"date", "expect", "from", "host", "origin", "proxy-authorization",
|
|
||||||
"referer", "user-agent", "upgrade", "via", "warning");
|
|
||||||
|
|
||||||
|
|
||||||
// we silently drop headers that are disallowed
|
|
||||||
private void dropDisallowedHeaders() {
|
|
||||||
Set<String> hdrnames = userHeaders.directMap().keySet();
|
|
||||||
|
|
||||||
hdrnames.removeIf((s) ->
|
|
||||||
DISALLOWED_HEADERS_SET.contains(s.toLowerCase())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void receiving() {
|
private synchronized void receiving() {
|
||||||
if (receiving) {
|
if (receiving) {
|
||||||
throw new IllegalStateException("already receiving response");
|
throw new IllegalStateException("already receiving response");
|
||||||
@ -176,6 +194,10 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
receiving = true;
|
receiving = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized Stream.PushGroup<?> pushGroup() {
|
||||||
|
return pushGroup;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Response filters may result in a new HttpRequestImpl being created
|
* Response filters may result in a new HttpRequestImpl being created
|
||||||
* (but still associated with the same API HttpRequest) and the process
|
* (but still associated with the same API HttpRequest) and the process
|
||||||
@ -200,10 +222,25 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
.thenApply((r) -> (HttpResponse)r);
|
.thenApply((r) -> (HttpResponse)r);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <U> CompletableFuture<U>
|
|
||||||
sendAsyncMulti(HttpResponse.MultiProcessor<U> rspproc) {
|
@SuppressWarnings("unchecked")
|
||||||
// To change body of generated methods, choose Tools | Templates.
|
@Override
|
||||||
throw new UnsupportedOperationException("Not supported yet.");
|
public synchronized <U> CompletableFuture<U>
|
||||||
|
multiResponseAsync(MultiProcessor<U> rspproc) {
|
||||||
|
if (System.getSecurityManager() != null) {
|
||||||
|
acc = AccessController.getContext();
|
||||||
|
}
|
||||||
|
this.pushGroup = new Stream.PushGroup<>(rspproc, this);
|
||||||
|
CompletableFuture<HttpResponse> cf = pushGroup.mainResponse();
|
||||||
|
responseAsync()
|
||||||
|
.whenComplete((HttpResponse r, Throwable t) -> {
|
||||||
|
if (r != null)
|
||||||
|
cf.complete(r);
|
||||||
|
else
|
||||||
|
cf.completeExceptionally(t);
|
||||||
|
pushGroup.pushError(t);
|
||||||
|
});
|
||||||
|
return (CompletableFuture<U>)pushGroup.groupResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -255,7 +292,7 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
@Override
|
@Override
|
||||||
public URI uri() { return uri; }
|
public URI uri() { return uri; }
|
||||||
|
|
||||||
HttpHeadersImpl getUserHeaders() { return userHeaders; }
|
HttpHeaders getUserHeaders() { return userHeaders; }
|
||||||
|
|
||||||
HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
|
HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
|
||||||
|
|
||||||
@ -275,11 +312,4 @@ class HttpRequestImpl extends HttpRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long timeval() { return timeval; }
|
long timeval() { return timeval; }
|
||||||
|
|
||||||
@Override
|
|
||||||
public <U> CompletableFuture<U>
|
|
||||||
multiResponseAsync(MultiProcessor<U> rspproc) {
|
|
||||||
//To change body of generated methods, choose Tools | Templates.
|
|
||||||
throw new UnsupportedOperationException("Not supported yet.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -200,8 +200,12 @@ public abstract class HttpResponse {
|
|||||||
if (n == -1) {
|
if (n == -1) {
|
||||||
throw new IOException("Bad Content-Disposition type");
|
throw new IOException("Bad Content-Disposition type");
|
||||||
}
|
}
|
||||||
String disposition = dispoHeader.substring(n + 9,
|
int lastsemi = dispoHeader.lastIndexOf(';');
|
||||||
dispoHeader.lastIndexOf(';'));
|
String disposition;
|
||||||
|
if (lastsemi < n)
|
||||||
|
disposition = dispoHeader.substring(n + 9);
|
||||||
|
else
|
||||||
|
disposition = dispoHeader.substring(n + 9, lastsemi);
|
||||||
file = Paths.get(directory.toString(), disposition);
|
file = Paths.get(directory.toString(), disposition);
|
||||||
fc = FileChannel.open(file, openOptions);
|
fc = FileChannel.open(file, openOptions);
|
||||||
return null;
|
return null;
|
||||||
@ -727,11 +731,14 @@ public abstract class HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Path> getBody(HttpRequest req,
|
private CompletableFuture<Path> getBody(HttpRequest req,
|
||||||
CompletableFuture<HttpResponse> cf) {
|
CompletableFuture<? extends HttpResponse> cf) {
|
||||||
URI u = req.uri();
|
URI u = req.uri();
|
||||||
String path = u.getPath();
|
String path = u.getPath();
|
||||||
|
if (path.startsWith("/"))
|
||||||
|
path = path.substring(1);
|
||||||
|
final String fpath = path;
|
||||||
return cf.thenCompose((HttpResponse resp) -> {
|
return cf.thenCompose((HttpResponse resp) -> {
|
||||||
return resp.bodyAsync(HttpResponse.asFile(destination.resolve(path)));
|
return resp.bodyAsync(HttpResponse.asFile(destination.resolve(fpath)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.AccessControlContext;
|
import java.security.AccessControlContext;
|
||||||
@ -42,17 +43,18 @@ class HttpResponseImpl extends HttpResponse {
|
|||||||
int responseCode;
|
int responseCode;
|
||||||
Exchange exchange;
|
Exchange exchange;
|
||||||
HttpRequestImpl request;
|
HttpRequestImpl request;
|
||||||
HttpHeaders1 headers;
|
HttpHeaders headers;
|
||||||
HttpHeaders1 trailers;
|
HttpHeaders trailers;
|
||||||
SSLParameters sslParameters;
|
SSLParameters sslParameters;
|
||||||
URI uri;
|
URI uri;
|
||||||
HttpClient.Version version;
|
HttpClient.Version version;
|
||||||
AccessControlContext acc;
|
AccessControlContext acc;
|
||||||
RawChannel rawchan;
|
RawChannel rawchan;
|
||||||
HttpConnection connection;
|
HttpConnection connection;
|
||||||
|
final Stream stream;
|
||||||
|
|
||||||
public HttpResponseImpl(int responseCode, Exchange exch, HttpHeaders1 headers,
|
public HttpResponseImpl(int responseCode, Exchange exch, HttpHeaders headers,
|
||||||
HttpHeaders1 trailers, SSLParameters sslParameters,
|
HttpHeaders trailers, SSLParameters sslParameters,
|
||||||
HttpClient.Version version, HttpConnection connection) {
|
HttpClient.Version version, HttpConnection connection) {
|
||||||
this.responseCode = responseCode;
|
this.responseCode = responseCode;
|
||||||
this.exchange = exch;
|
this.exchange = exch;
|
||||||
@ -63,6 +65,23 @@ class HttpResponseImpl extends HttpResponse {
|
|||||||
this.uri = request.uri();
|
this.uri = request.uri();
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
this.stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A response to a PUSH_PROMISE
|
||||||
|
public HttpResponseImpl(int responseCode, HttpRequestImpl pushRequest,
|
||||||
|
ImmutableHeaders headers,
|
||||||
|
Stream stream, SSLParameters sslParameters) {
|
||||||
|
this.responseCode = responseCode;
|
||||||
|
this.exchange = null;
|
||||||
|
this.request = pushRequest;
|
||||||
|
this.headers = headers;
|
||||||
|
this.trailers = null;
|
||||||
|
this.sslParameters = sslParameters;
|
||||||
|
this.uri = request.uri(); // TODO: take from headers
|
||||||
|
this.version = HttpClient.Version.HTTP_2;
|
||||||
|
this.connection = null;
|
||||||
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,26 +96,35 @@ class HttpResponseImpl extends HttpResponse {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders headers() {
|
public HttpHeaders headers() {
|
||||||
headers.makeUnmodifiable();
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders trailers() {
|
public HttpHeaders trailers() {
|
||||||
trailers.makeUnmodifiable();
|
|
||||||
return trailers;
|
return trailers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T body(java.net.http.HttpResponse.BodyProcessor<T> processor) {
|
public <T> T body(java.net.http.HttpResponse.BodyProcessor<T> processor) {
|
||||||
|
try {
|
||||||
|
if (exchange != null) {
|
||||||
return exchange.responseBody(processor);
|
return exchange.responseBody(processor);
|
||||||
|
} else {
|
||||||
|
return stream.responseBody(processor);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> CompletableFuture<T> bodyAsync(java.net.http.HttpResponse.BodyProcessor<T> processor) {
|
public <T> CompletableFuture<T> bodyAsync(java.net.http.HttpResponse.BodyProcessor<T> processor) {
|
||||||
acc = AccessController.getContext();
|
acc = AccessController.getContext();
|
||||||
|
if (exchange != null)
|
||||||
return exchange.responseBodyAsync(processor);
|
return exchange.responseBodyAsync(processor);
|
||||||
|
else
|
||||||
|
return stream.responseBodyAsync(processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable HttpHeaders constructed from mutable HttpHeadersImpl.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ImmutableHeaders implements HttpHeaders {
|
||||||
|
|
||||||
|
private final Map<String,List<String>> map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ImmutableHeaders() {
|
||||||
|
map = (Map<String,List<String>>)Collections.EMPTY_MAP;
|
||||||
|
}
|
||||||
|
// TODO: fix lower case issue. Must be lc for http/2 compares ignoreCase for http/1
|
||||||
|
ImmutableHeaders(HttpHeadersImpl h, Predicate<String> keyAllowed) {
|
||||||
|
Map<String,List<String>> src = h.directMap();
|
||||||
|
Map<String,List<String>> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
src.forEach((key, value) -> {
|
||||||
|
if (keyAllowed.test(key))
|
||||||
|
m.put(key, Collections.unmodifiableList(value));
|
||||||
|
});
|
||||||
|
map = Collections.unmodifiableMap(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> firstValue(String name) {
|
||||||
|
List<String> l = map.get(name);
|
||||||
|
String v = l == null ? null : l.get(0);
|
||||||
|
return Optional.ofNullable(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Long> firstValueAsLong(String name) {
|
||||||
|
return firstValue(name).map((v -> Long.parseLong(v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> allValues(String name) {
|
||||||
|
return map.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> map() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,9 @@ package java.net.http;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* -Djava.net.HttpClient.log=errors,requests,headers,frames[:type:type2:..],content
|
* -Djava.net.HttpClient.log=
|
||||||
|
* errors,requests,headers,
|
||||||
|
* frames[:type:type2:..],content,ssl,trace
|
||||||
*
|
*
|
||||||
* Any of errors, requests, headers or content are optional.
|
* Any of errors, requests, headers or content are optional.
|
||||||
*
|
*
|
||||||
@ -47,6 +49,7 @@ abstract class Log implements System.Logger {
|
|||||||
public static final int CONTENT = 0x8;
|
public static final int CONTENT = 0x8;
|
||||||
public static final int FRAMES = 0x10;
|
public static final int FRAMES = 0x10;
|
||||||
public static final int SSL = 0x20;
|
public static final int SSL = 0x20;
|
||||||
|
public static final int TRACE = 0x40;
|
||||||
static int logging;
|
static int logging;
|
||||||
|
|
||||||
// Frame types: "control", "data", "window", "all"
|
// Frame types: "control", "data", "window", "all"
|
||||||
@ -81,8 +84,11 @@ abstract class Log implements System.Logger {
|
|||||||
case "ssl":
|
case "ssl":
|
||||||
logging |= SSL;
|
logging |= SSL;
|
||||||
break;
|
break;
|
||||||
|
case "trace":
|
||||||
|
logging |= TRACE;
|
||||||
|
break;
|
||||||
case "all":
|
case "all":
|
||||||
logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS;
|
logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS|TRACE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (val.startsWith("frames")) {
|
if (val.startsWith("frames")) {
|
||||||
@ -130,6 +136,10 @@ abstract class Log implements System.Logger {
|
|||||||
return (logging & HEADERS) != 0;
|
return (logging & HEADERS) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean trace() {
|
||||||
|
return (logging & TRACE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
static boolean ssl() {
|
static boolean ssl() {
|
||||||
return (logging & SSL) != 0;
|
return (logging & SSL) != 0;
|
||||||
}
|
}
|
||||||
@ -138,9 +148,9 @@ abstract class Log implements System.Logger {
|
|||||||
return (logging & FRAMES) != 0;
|
return (logging & FRAMES) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logError(String s) {
|
static void logError(String s, Object... s1) {
|
||||||
if (errors())
|
if (errors())
|
||||||
logger.log(Level.INFO, "ERROR: " + s);
|
logger.log(Level.INFO, "ERROR: " + s, s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logError(Throwable t) {
|
static void logError(Throwable t) {
|
||||||
@ -150,24 +160,50 @@ abstract class Log implements System.Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logSSL(String s) {
|
static void logSSL(String s, Object... s1) {
|
||||||
if (ssl())
|
if (ssl())
|
||||||
logger.log(Level.INFO, "SSL: " + s);
|
logger.log(Level.INFO, "SSL: " + s, s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logRequest(String s) {
|
static void logTrace(String s, Object... s1) {
|
||||||
|
if (trace()) {
|
||||||
|
String format = "TRACE: " + s;
|
||||||
|
logger.log(Level.INFO, format, s1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logRequest(String s, Object... s1) {
|
||||||
if (requests())
|
if (requests())
|
||||||
logger.log(Level.INFO, "REQUEST: " + s);
|
logger.log(Level.INFO, "REQUEST: " + s, s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logResponse(String s) {
|
static void logResponse(String s, Object... s1) {
|
||||||
if (requests())
|
if (requests())
|
||||||
logger.log(Level.INFO, "RESPONSE: " + s);
|
logger.log(Level.INFO, "RESPONSE: " + s, s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logHeaders(String s) {
|
static void logHeaders(String s, Object... s1) {
|
||||||
if (headers())
|
if (headers())
|
||||||
logger.log(Level.INFO, "HEADERS: " + s);
|
logger.log(Level.INFO, "HEADERS: " + s, s1);
|
||||||
|
}
|
||||||
|
// START HTTP2
|
||||||
|
static boolean loggingFrame(Class<? extends Http2Frame> clazz) {
|
||||||
|
if (frametypes == ALL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (clazz == DataFrame.class) {
|
||||||
|
return (frametypes & DATA) != 0;
|
||||||
|
} else if (clazz == WindowUpdateFrame.class) {
|
||||||
|
return (frametypes & WINDOW_UPDATES) != 0;
|
||||||
|
} else {
|
||||||
|
return (frametypes & CONTROL) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logFrames(Http2Frame f, String direction) {
|
||||||
|
if (frames() && loggingFrame(f.getClass())) {
|
||||||
|
logger.log(Level.INFO, "FRAME: " + direction + ": " + f.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// not instantiable
|
// not instantiable
|
||||||
|
@ -54,7 +54,7 @@ class MultiExchange {
|
|||||||
|
|
||||||
final static int DEFAULT_MAX_ATTEMPTS = 5;
|
final static int DEFAULT_MAX_ATTEMPTS = 5;
|
||||||
final static int max_attempts = Utils.getIntegerNetProperty(
|
final static int max_attempts = Utils.getIntegerNetProperty(
|
||||||
"sun.net.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS
|
"java.net.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS
|
||||||
);
|
);
|
||||||
|
|
||||||
private final List<HeaderFilter> filters;
|
private final List<HeaderFilter> filters;
|
||||||
@ -187,8 +187,7 @@ class MultiExchange {
|
|||||||
public CompletableFuture<HttpResponseImpl> responseAsync(Void v) {
|
public CompletableFuture<HttpResponseImpl> responseAsync(Void v) {
|
||||||
CompletableFuture<HttpResponseImpl> cf;
|
CompletableFuture<HttpResponseImpl> cf;
|
||||||
if (++attempts > max_attempts) {
|
if (++attempts > max_attempts) {
|
||||||
cf = new CompletableFuture<>();
|
cf = CompletableFuture.failedFuture(new IOException("Too many retries"));
|
||||||
cf.completeExceptionally(new IOException("Too many retries"));
|
|
||||||
} else {
|
} else {
|
||||||
if (currentreq.timeval() != 0) {
|
if (currentreq.timeval() != 0) {
|
||||||
// set timer
|
// set timer
|
||||||
@ -241,7 +240,6 @@ class MultiExchange {
|
|||||||
* completed exceptionally.
|
* completed exceptionally.
|
||||||
*/
|
*/
|
||||||
private CompletableFuture<HttpResponseImpl> getExceptionalCF(Throwable t) {
|
private CompletableFuture<HttpResponseImpl> getExceptionalCF(Throwable t) {
|
||||||
CompletableFuture<HttpResponseImpl> error = new CompletableFuture<>();
|
|
||||||
if ((t instanceof CompletionException) || (t instanceof ExecutionException)) {
|
if ((t instanceof CompletionException) || (t instanceof ExecutionException)) {
|
||||||
if (t.getCause() != null) {
|
if (t.getCause() != null) {
|
||||||
t = t.getCause();
|
t = t.getCause();
|
||||||
@ -250,8 +248,7 @@ class MultiExchange {
|
|||||||
if (cancelled && t instanceof IOException) {
|
if (cancelled && t instanceof IOException) {
|
||||||
t = new HttpTimeoutException("request timed out");
|
t = new HttpTimeoutException("request timed out");
|
||||||
}
|
}
|
||||||
error.completeExceptionally(t);
|
return CompletableFuture.failedFuture(t);
|
||||||
return error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<T> T responseBody(HttpResponse.BodyProcessor<T> processor) {
|
<T> T responseBody(HttpResponse.BodyProcessor<T> processor) {
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all parameters for outgoing headers. Is converted to
|
||||||
|
* HeadersFrame and ContinuationFrames by Http2Connection.
|
||||||
|
*/
|
||||||
|
class OutgoingHeaders extends Http2Frame {
|
||||||
|
|
||||||
|
int streamDependency;
|
||||||
|
int weight;
|
||||||
|
boolean exclusive;
|
||||||
|
Stream stream;
|
||||||
|
|
||||||
|
public static final int PRIORITY = 0x20;
|
||||||
|
|
||||||
|
HttpHeaders user, system;
|
||||||
|
|
||||||
|
OutgoingHeaders(HttpHeaders hdrs1, HttpHeaders hdrs2, Stream stream) {
|
||||||
|
this.user = hdrs2;
|
||||||
|
this.system = hdrs1;
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int streamDependency, boolean exclusive, int weight) {
|
||||||
|
this.streamDependency = streamDependency;
|
||||||
|
this.exclusive = exclusive;
|
||||||
|
this.weight = weight;
|
||||||
|
this.flags |= PRIORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStreamDependency() {
|
||||||
|
return streamDependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getExclusive() {
|
||||||
|
return exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream getStream() {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpHeaders getUserHeaders() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpHeaders getSystemHeaders() {
|
||||||
|
return system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
//To change body of generated methods, choose Tools | Templates.
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
}
|
@ -43,4 +43,9 @@ final class Pair<T, U> {
|
|||||||
static <T, U> Pair<T, U> pair(T first, U second) {
|
static <T, U> Pair<T, U> pair(T first, U second) {
|
||||||
return new Pair<>(first, second);
|
return new Pair<>(first, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + first + ", " + second + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class PingFrame extends Http2Frame {
|
||||||
|
|
||||||
|
PingFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
|
||||||
|
public final static int TYPE = 0x6;
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
public static final int ACK = 0x1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case ACK:
|
||||||
|
return "ACK";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(byte[] data) {
|
||||||
|
if (data.length != 8) {
|
||||||
|
throw new IllegalArgumentException("Ping data not 8 bytes");
|
||||||
|
}
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if (length != 8) {
|
||||||
|
throw new IOException("Invalid Ping frame");
|
||||||
|
}
|
||||||
|
data = bc.getBytes(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
if (data == null) {
|
||||||
|
data = new byte[] {0, 0, 0, 0, 0 ,0, 0, 0};
|
||||||
|
}
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(8);
|
||||||
|
buf.put(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = 8;
|
||||||
|
}
|
||||||
|
}
|
@ -31,20 +31,43 @@ import java.nio.channels.SelectableChannel;
|
|||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plain raw TCP connection direct to destination
|
* Plain raw TCP connection direct to destination. 2 modes
|
||||||
|
* 1) Blocking used by http/1. In this case the connect is actually non
|
||||||
|
* blocking but the request is sent blocking. The first byte of a response
|
||||||
|
* is received non-blocking and the remainder of the response is received
|
||||||
|
* blocking
|
||||||
|
* 2) Non-blocking. In this case (for http/2) the connection is actually opened
|
||||||
|
* blocking but all reads and writes are done non-blocking under the
|
||||||
|
* control of a Http2Connection object.
|
||||||
*/
|
*/
|
||||||
class PlainHttpConnection extends HttpConnection {
|
class PlainHttpConnection extends HttpConnection implements AsyncConnection {
|
||||||
|
|
||||||
protected SocketChannel chan;
|
protected SocketChannel chan;
|
||||||
private volatile boolean connected;
|
private volatile boolean connected;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
|
Consumer<ByteBuffer> asyncReceiver;
|
||||||
|
Consumer<Throwable> errorReceiver;
|
||||||
|
Queue<ByteBuffer> asyncOutputQ;
|
||||||
|
final Object reading = new Object();
|
||||||
|
final Object writing = new Object();
|
||||||
|
|
||||||
class ConnectEvent extends AsyncEvent implements AsyncEvent.Blocking {
|
@Override
|
||||||
|
public void startReading() {
|
||||||
|
try {
|
||||||
|
client.registerEvent(new ReadEvent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectEvent extends AsyncEvent {
|
||||||
CompletableFuture<Void> cf;
|
CompletableFuture<Void> cf;
|
||||||
|
|
||||||
ConnectEvent(CompletableFuture<Void> cf) {
|
ConnectEvent(CompletableFuture<Void> cf) {
|
||||||
|
super(AsyncEvent.BLOCKING);
|
||||||
this.cf = cf;
|
this.cf = cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,14 +135,62 @@ class PlainHttpConnection extends HttpConnection {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
long write(ByteBuffer[] buffers, int start, int number) throws IOException {
|
long write(ByteBuffer[] buffers, int start, int number) throws IOException {
|
||||||
//debugPrint("Send", buffers, start, number);
|
if (mode != Mode.ASYNC)
|
||||||
return chan.write(buffers, start, number);
|
return chan.write(buffers, start, number);
|
||||||
|
// async
|
||||||
|
synchronized(writing) {
|
||||||
|
int qlen = asyncOutputQ.size();
|
||||||
|
ByteBuffer[] bufs = Utils.reduce(buffers, start, number);
|
||||||
|
long n = Utils.remaining(bufs);
|
||||||
|
asyncOutputQ.putAll(bufs);
|
||||||
|
if (qlen == 0)
|
||||||
|
asyncOutput();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer asyncBuffer = null;
|
||||||
|
|
||||||
|
void asyncOutput() {
|
||||||
|
synchronized (writing) {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
if (asyncBuffer == null) {
|
||||||
|
asyncBuffer = asyncOutputQ.poll();
|
||||||
|
if (asyncBuffer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!asyncBuffer.hasRemaining()) {
|
||||||
|
asyncBuffer = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int n = chan.write(asyncBuffer);
|
||||||
|
//System.err.printf("Written %d bytes to chan\n", n);
|
||||||
|
if (n == 0) {
|
||||||
|
client.registerEvent(new WriteEvent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
long write(ByteBuffer buffer) throws IOException {
|
long write(ByteBuffer buffer) throws IOException {
|
||||||
//debugPrint("Send", buffer);
|
if (mode != Mode.ASYNC)
|
||||||
return chan.write(buffer);
|
return chan.write(buffer);
|
||||||
|
// async
|
||||||
|
synchronized(writing) {
|
||||||
|
int qlen = asyncOutputQ.size();
|
||||||
|
long n = buffer.remaining();
|
||||||
|
asyncOutputQ.put(buffer);
|
||||||
|
if (qlen == 0)
|
||||||
|
asyncOutput();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,7 +202,7 @@ class PlainHttpConnection extends HttpConnection {
|
|||||||
* Close this connection
|
* Close this connection
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
synchronized void close() {
|
public synchronized void close() {
|
||||||
if (closed)
|
if (closed)
|
||||||
return;
|
return;
|
||||||
closed = true;
|
closed = true;
|
||||||
@ -155,14 +226,49 @@ class PlainHttpConnection extends HttpConnection {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
close();
|
||||||
|
errorReceiver.accept(new IOException("Connection aborted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void asyncRead() {
|
||||||
|
synchronized (reading) {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
int n = chan.read(buf);
|
||||||
|
//System.err.printf("Read %d bytes from chan\n", n);
|
||||||
|
if (n == -1) {
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
returnBuffer(buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
asyncReceiver.accept(buf);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int readImpl(ByteBuffer buf) throws IOException {
|
protected int readImpl(ByteBuffer buf) throws IOException {
|
||||||
int mark = buf.position();
|
int mark = buf.position();
|
||||||
int n = chan.read(buf);
|
int n;
|
||||||
|
// FIXME: this hack works in conjunction with the corresponding change
|
||||||
|
// in java.net.http.RawChannel.registerEvent
|
||||||
|
if ((n = buffer.remaining()) != 0) {
|
||||||
|
buf.put(buffer);
|
||||||
|
} else {
|
||||||
|
n = chan.read(buf);
|
||||||
|
}
|
||||||
if (n == -1) {
|
if (n == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
Utils.flipToMark(buffer, mark);
|
Utils.flipToMark(buf, mark);
|
||||||
String s = "Receive (" + n + " bytes) ";
|
String s = "Receive (" + n + " bytes) ";
|
||||||
//debugPrint(s, buf);
|
//debugPrint(s, buf);
|
||||||
return n;
|
return n;
|
||||||
@ -178,10 +284,67 @@ class PlainHttpConnection extends HttpConnection {
|
|||||||
return connected;
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReceiveResponseEvent extends AsyncEvent implements AsyncEvent.Blocking {
|
// used for all output in HTTP/2
|
||||||
|
class WriteEvent extends AsyncEvent {
|
||||||
|
WriteEvent() {
|
||||||
|
super(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SelectableChannel channel() {
|
||||||
|
return chan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int interestOps() {
|
||||||
|
return SelectionKey.OP_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() {
|
||||||
|
asyncOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// used for all input in HTTP/2
|
||||||
|
class ReadEvent extends AsyncEvent {
|
||||||
|
ReadEvent() {
|
||||||
|
super(AsyncEvent.REPEATING); // && !BLOCKING
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SelectableChannel channel() {
|
||||||
|
return chan;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int interestOps() {
|
||||||
|
return SelectionKey.OP_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() {
|
||||||
|
asyncRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// used in blocking channels only
|
||||||
|
class ReceiveResponseEvent extends AsyncEvent {
|
||||||
CompletableFuture<Void> cf;
|
CompletableFuture<Void> cf;
|
||||||
|
|
||||||
ReceiveResponseEvent(CompletableFuture<Void> cf) {
|
ReceiveResponseEvent(CompletableFuture<Void> cf) {
|
||||||
|
super(AsyncEvent.BLOCKING);
|
||||||
this.cf = cf;
|
this.cf = cf;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@ -215,6 +378,15 @@ class PlainHttpConnection extends HttpConnection {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setAsyncCallbacks(Consumer<ByteBuffer> asyncReceiver,
|
||||||
|
Consumer<Throwable> errorReceiver) {
|
||||||
|
this.asyncReceiver = asyncReceiver;
|
||||||
|
this.errorReceiver = errorReceiver;
|
||||||
|
asyncOutputQ = new Queue<>();
|
||||||
|
asyncOutputQ.registerPutCallback(this::asyncOutput);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
CompletableFuture<Void> whenReceivingResponse() {
|
CompletableFuture<Void> whenReceivingResponse() {
|
||||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||||
|
@ -111,7 +111,7 @@ class PlainTunnelingConnection extends HttpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void close() {
|
public void close() {
|
||||||
delegate.close();
|
delegate.close();
|
||||||
connected = false;
|
connected = false;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class PriorityFrame extends Http2Frame {
|
||||||
|
|
||||||
|
int streamDependency;
|
||||||
|
int weight;
|
||||||
|
boolean exclusive;
|
||||||
|
|
||||||
|
public final static int TYPE = 0x2;
|
||||||
|
|
||||||
|
PriorityFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PriorityFrame(int streamDependency, boolean exclusive, int weight) {
|
||||||
|
this.streamDependency = streamDependency;
|
||||||
|
this.exclusive = exclusive;
|
||||||
|
this.weight = weight;
|
||||||
|
this.type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int streamDependency() {
|
||||||
|
return streamDependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
int weight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean exclusive() {
|
||||||
|
return exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
int x = bc.getInt();
|
||||||
|
exclusive = (x & 0x80000000) != 0;
|
||||||
|
streamDependency = x & 0x7fffffff;
|
||||||
|
weight = bc.getByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(5);
|
||||||
|
int x = exclusive ? (1 << 31) + streamDependency : streamDependency;
|
||||||
|
buf.putInt(x);
|
||||||
|
buf.put((byte)weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = 5;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class PushPromiseFrame extends HeaderFrame {
|
||||||
|
|
||||||
|
int padLength;
|
||||||
|
int promisedStream;
|
||||||
|
|
||||||
|
PushPromiseFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int TYPE = 0x5;
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
public static final int END_HEADERS = 0x4;
|
||||||
|
public static final int PADDED = 0x8;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " promisedStreamid: " + promisedStream
|
||||||
|
+ " headerLength: " + headerLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case PADDED:
|
||||||
|
return "PADDED";
|
||||||
|
case END_HEADERS:
|
||||||
|
return "END_HEADERS";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPadLength(int padLength) {
|
||||||
|
this.padLength = padLength;
|
||||||
|
flags |= PADDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPromisedStream(int stream) {
|
||||||
|
this.promisedStream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPromisedStream() {
|
||||||
|
return promisedStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
padLength = bc.getByte();
|
||||||
|
headerLength = length - (padLength + 5);
|
||||||
|
} else
|
||||||
|
headerLength = length - 4;
|
||||||
|
|
||||||
|
promisedStream = bc.getInt() & 0x7fffffff;
|
||||||
|
headerBlocks = bc.getBuffers(headerLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
int len = 0;
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
len += (1 + padLength);
|
||||||
|
}
|
||||||
|
len += (4 + headerLength);
|
||||||
|
this.length = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(length);
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
buf.put((byte)padLength);
|
||||||
|
}
|
||||||
|
buf.putInt(promisedStream);
|
||||||
|
for (int i=0; i<headerBlocks.length; i++) {
|
||||||
|
bg.addByteBuffer(headerBlocks[i]);
|
||||||
|
}
|
||||||
|
if ((flags & PADDED) != 0) {
|
||||||
|
bg.addPadding(padLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean endHeaders() {
|
||||||
|
return getFlag(END_HEADERS);
|
||||||
|
}
|
||||||
|
}
|
143
jdk/src/java.httpclient/share/classes/java/net/http/Queue.java
Normal file
143
jdk/src/java.httpclient/share/classes/java/net/http/Queue.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
// Each stream has one of these for input. Each Http2Connection has one
|
||||||
|
// for output. Can be used blocking or asynchronously.
|
||||||
|
|
||||||
|
class Queue<T> implements Closeable {
|
||||||
|
|
||||||
|
private final LinkedList<T> q = new LinkedList<>();
|
||||||
|
private volatile boolean closed = false;
|
||||||
|
private Runnable callback;
|
||||||
|
private boolean forceCallback;
|
||||||
|
private int waiters; // true if someone waiting
|
||||||
|
|
||||||
|
synchronized void putAll(T[] objs) throws IOException {
|
||||||
|
if (closed) {
|
||||||
|
throw new IOException("stream closed");
|
||||||
|
}
|
||||||
|
boolean wasEmpty = q.isEmpty();
|
||||||
|
|
||||||
|
for (T obj : objs) {
|
||||||
|
q.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waiters > 0)
|
||||||
|
notifyAll();
|
||||||
|
|
||||||
|
if (wasEmpty || forceCallback) {
|
||||||
|
forceCallback = false;
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized int size() {
|
||||||
|
return q.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void put(T obj) throws IOException {
|
||||||
|
if (closed) {
|
||||||
|
throw new IOException("stream closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
q.add(obj);
|
||||||
|
if (waiters > 0)
|
||||||
|
notifyAll();
|
||||||
|
|
||||||
|
if (q.size() == 1 || forceCallback) {
|
||||||
|
forceCallback = false;
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback is invoked any time put is called where
|
||||||
|
* the Queue was empty.
|
||||||
|
*/
|
||||||
|
synchronized void registerPutCallback(Runnable callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
if (callback != null && q.size() > 0)
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() {
|
||||||
|
closed = true;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized T take() throws IOException {
|
||||||
|
if (closed) {
|
||||||
|
throw new IOException("stream closed");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
while (q.size() == 0) {
|
||||||
|
waiters++;
|
||||||
|
wait();
|
||||||
|
waiters--;
|
||||||
|
}
|
||||||
|
return q.removeFirst();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new IOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized T poll() throws IOException {
|
||||||
|
if (closed)
|
||||||
|
throw new IOException("stream closed");
|
||||||
|
|
||||||
|
if (q.isEmpty())
|
||||||
|
return null;
|
||||||
|
T res = q.removeFirst();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized T[] pollAll(T[] type) throws IOException {
|
||||||
|
T[] ret = q.toArray(type);
|
||||||
|
q.clear();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void pushback(T v) {
|
||||||
|
forceCallback = true;
|
||||||
|
q.addFirst(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void pushbackAll(T[] v) {
|
||||||
|
forceCallback = true;
|
||||||
|
for (int i=v.length-1; i>=0; i--) {
|
||||||
|
q.addFirst(v[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,18 +28,18 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.channels.ByteChannel;
|
import java.nio.channels.ByteChannel;
|
||||||
import java.nio.channels.GatheringByteChannel;
|
import java.nio.channels.GatheringByteChannel;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
|
||||||
/**
|
//
|
||||||
* Used to implement WebSocket. Each RawChannel corresponds to
|
// Used to implement WebSocket. Each RawChannel corresponds to a TCP connection
|
||||||
* a TCP connection (SocketChannel) but is connected to a Selector
|
// (SocketChannel) but is connected to a Selector and an ExecutorService for
|
||||||
* and an ExecutorService for invoking the send and receive callbacks
|
// invoking the send and receive callbacks. Also includes SSL processing.
|
||||||
* Also includes SSL processing.
|
//
|
||||||
*/
|
final class RawChannel implements ByteChannel, GatheringByteChannel {
|
||||||
class RawChannel implements ByteChannel, GatheringByteChannel {
|
|
||||||
|
|
||||||
private final HttpClientImpl client;
|
private final HttpClientImpl client;
|
||||||
private final HttpConnection connection;
|
private final HttpConnection connection;
|
||||||
private boolean closed;
|
private volatile boolean closed;
|
||||||
|
|
||||||
private interface RawEvent {
|
private interface RawEvent {
|
||||||
|
|
||||||
@ -50,8 +50,6 @@ class RawChannel implements ByteChannel, GatheringByteChannel {
|
|||||||
void handle();
|
void handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BlockingEvent extends RawEvent { }
|
|
||||||
|
|
||||||
interface NonBlockingEvent extends RawEvent { }
|
interface NonBlockingEvent extends RawEvent { }
|
||||||
|
|
||||||
RawChannel(HttpClientImpl client, HttpConnection connection) {
|
RawChannel(HttpClientImpl client, HttpConnection connection) {
|
||||||
@ -64,39 +62,40 @@ class RawChannel implements ByteChannel, GatheringByteChannel {
|
|||||||
private final RawEvent re;
|
private final RawEvent re;
|
||||||
|
|
||||||
RawAsyncEvent(RawEvent re) {
|
RawAsyncEvent(RawEvent re) {
|
||||||
|
super(AsyncEvent.BLOCKING); // BLOCKING & !REPEATING
|
||||||
this.re = re;
|
this.re = re;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RawAsyncEvent(RawEvent re, int flags) {
|
||||||
|
super(flags);
|
||||||
|
this.re = re;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public SelectableChannel channel() {
|
public SelectableChannel channel() {
|
||||||
return connection.channel();
|
return connection.channel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// must return the selector interest op flags OR'd
|
// must return the selector interest op flags OR'd
|
||||||
|
@Override
|
||||||
public int interestOps() {
|
public int interestOps() {
|
||||||
return re.interestOps();
|
return re.interestOps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// called when event occurs
|
// called when event occurs
|
||||||
|
@Override
|
||||||
public void handle() {
|
public void handle() {
|
||||||
re.handle();
|
re.handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void abort() {}
|
@Override
|
||||||
|
public void abort() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BlockingRawAsyncEvent extends RawAsyncEvent
|
private class NonBlockingRawAsyncEvent extends RawAsyncEvent {
|
||||||
implements AsyncEvent.Blocking {
|
|
||||||
|
|
||||||
BlockingRawAsyncEvent(RawEvent re) {
|
|
||||||
super(re);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NonBlockingRawAsyncEvent extends RawAsyncEvent
|
|
||||||
implements AsyncEvent.NonBlocking {
|
|
||||||
|
|
||||||
NonBlockingRawAsyncEvent(RawEvent re) {
|
NonBlockingRawAsyncEvent(RawEvent re) {
|
||||||
super(re);
|
super(re, 0); // !BLOCKING & !REPEATING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,17 +104,24 @@ class RawChannel implements ByteChannel, GatheringByteChannel {
|
|||||||
* (i.e. register new event for each callback)
|
* (i.e. register new event for each callback)
|
||||||
*/
|
*/
|
||||||
public void registerEvent(RawEvent event) throws IOException {
|
public void registerEvent(RawEvent event) throws IOException {
|
||||||
if (event instanceof BlockingEvent) {
|
if (!(event instanceof NonBlockingEvent)) {
|
||||||
client.registerEvent(new BlockingRawAsyncEvent(event));
|
|
||||||
} else if (event instanceof NonBlockingEvent) {
|
|
||||||
client.registerEvent(new NonBlockingRawAsyncEvent(event));
|
|
||||||
} else {
|
|
||||||
throw new InternalError();
|
throw new InternalError();
|
||||||
}
|
}
|
||||||
|
if ((event.interestOps() & SelectionKey.OP_READ) != 0
|
||||||
|
&& connection.buffer.hasRemaining()) {
|
||||||
|
// FIXME: a hack to deal with leftovers from previous reads into an
|
||||||
|
// internal buffer (works in conjunction with change in
|
||||||
|
// java.net.http.PlainHttpConnection.readImpl(java.nio.ByteBuffer)
|
||||||
|
connection.channel().configureBlocking(false);
|
||||||
|
event.handle();
|
||||||
|
} else {
|
||||||
|
client.registerEvent(new NonBlockingRawAsyncEvent(event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(ByteBuffer dst) throws IOException {
|
public int read(ByteBuffer dst) throws IOException {
|
||||||
|
assert !connection.channel().isBlocking();
|
||||||
return connection.read(dst);
|
return connection.read(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,17 +33,19 @@ class RedirectFilter implements HeaderFilter {
|
|||||||
HttpRequestImpl requestImpl;
|
HttpRequestImpl requestImpl;
|
||||||
HttpRequest request;
|
HttpRequest request;
|
||||||
HttpClientImpl client;
|
HttpClientImpl client;
|
||||||
|
HttpClient.Redirect policy;
|
||||||
String method;
|
String method;
|
||||||
final static int DEFAULT_MAX_REDIRECTS = 5;
|
final static int DEFAULT_MAX_REDIRECTS = 5;
|
||||||
URI uri;
|
URI uri;
|
||||||
|
|
||||||
final static int max_redirects = Utils.getIntegerNetProperty(
|
final static int max_redirects = Utils.getIntegerNetProperty(
|
||||||
"sun.net.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
|
"java.net.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
|
||||||
);
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void request(HttpRequestImpl r) throws IOException {
|
public void request(HttpRequestImpl r) throws IOException {
|
||||||
this.request = r;
|
this.request = r;
|
||||||
|
this.policy = request.followRedirects();
|
||||||
this.client = r.getClient();
|
this.client = r.getClient();
|
||||||
this.method = r.method();
|
this.method = r.method();
|
||||||
this.requestImpl = r;
|
this.requestImpl = r;
|
||||||
@ -61,7 +63,7 @@ class RedirectFilter implements HeaderFilter {
|
|||||||
*/
|
*/
|
||||||
private HttpRequestImpl handleResponse(HttpResponseImpl r) {
|
private HttpRequestImpl handleResponse(HttpResponseImpl r) {
|
||||||
int rcode = r.statusCode();
|
int rcode = r.statusCode();
|
||||||
if (rcode == 200) {
|
if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (rcode >= 300 && rcode <= 399) {
|
if (rcode >= 300 && rcode <= 399) {
|
||||||
@ -79,6 +81,7 @@ class RedirectFilter implements HeaderFilter {
|
|||||||
|
|
||||||
private URI getRedirectedURI(HttpHeaders headers) {
|
private URI getRedirectedURI(HttpHeaders headers) {
|
||||||
URI redirectedURI;
|
URI redirectedURI;
|
||||||
|
String ss = headers.firstValue("Location").orElse("Not present");
|
||||||
redirectedURI = headers.firstValue("Location")
|
redirectedURI = headers.firstValue("Location")
|
||||||
.map((s) -> URI.create(s))
|
.map((s) -> URI.create(s))
|
||||||
.orElseThrow(() -> new UncheckedIOException(
|
.orElseThrow(() -> new UncheckedIOException(
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class ResetFrame extends ErrorFrame {
|
||||||
|
|
||||||
|
public final static int TYPE = 0x3;
|
||||||
|
|
||||||
|
// See ErrorFrame for error values
|
||||||
|
|
||||||
|
ResetFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResetFrame(int errorCode) {
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
errorCode = bc.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(4);
|
||||||
|
buf.putInt(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = 4;
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@ import java.util.Set;
|
|||||||
*
|
*
|
||||||
* This class is not thread-safe
|
* This class is not thread-safe
|
||||||
*/
|
*/
|
||||||
class ResponseHeaders implements HttpHeaders1 {
|
class ResponseHeaders implements HttpHeaders {
|
||||||
|
|
||||||
static final int DATA_SIZE = 16 * 1024; // initial space for headers
|
static final int DATA_SIZE = 16 * 1024; // initial space for headers
|
||||||
static final int NUM_HEADERS = 50; // initial expected max number of headers
|
static final int NUM_HEADERS = 50; // initial expected max number of headers
|
||||||
@ -368,10 +368,6 @@ class ResponseHeaders implements HttpHeaders1 {
|
|||||||
return Collections.unmodifiableList(l);
|
return Collections.unmodifiableList(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void makeUnmodifiable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delegates map to HashMap but converts keys to lower case
|
// Delegates map to HashMap but converts keys to lower case
|
||||||
|
|
||||||
static class HeaderMap implements Map<String,List<String>> {
|
static class HeaderMap implements Map<String,List<String>> {
|
||||||
|
@ -121,13 +121,8 @@ class SSLConnection extends HttpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void close() {
|
public void close() {
|
||||||
try {
|
Utils.close(delegate.channel());
|
||||||
//System.err.println ("Closing: " + this);
|
|
||||||
delegate.channel().close(); // TODO: proper close
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Log.logError(ex.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,13 +29,9 @@ import java.nio.channels.SocketChannel;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
import javax.net.ssl.SSLEngineResult;
|
|
||||||
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||||
import javax.net.ssl.SSLEngineResult.Status;
|
import javax.net.ssl.SSLEngineResult.Status;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.*;
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,16 +56,18 @@ class SSLDelegate {
|
|||||||
engine.setUseClientMode(true);
|
engine.setUseClientMode(true);
|
||||||
SSLParameters sslp = client.sslParameters().orElse(null);
|
SSLParameters sslp = client.sslParameters().orElse(null);
|
||||||
if (sslp == null) {
|
if (sslp == null) {
|
||||||
sslp = context.getDefaultSSLParameters();
|
sslp = context.getSupportedSSLParameters();
|
||||||
}
|
}
|
||||||
sslParameters = Utils.copySSLParameters(sslp);
|
sslParameters = Utils.copySSLParameters(sslp);
|
||||||
if (alpn != null) {
|
if (alpn != null) {
|
||||||
sslParameters.setApplicationProtocols(alpn);
|
sslParameters.setApplicationProtocols(alpn);
|
||||||
Log.logSSL("Setting application protocols: " + Arrays.toString(alpn));
|
Log.logSSL("Setting application protocols: " + Arrays.toString(alpn));
|
||||||
} else {
|
} else {
|
||||||
Log.logSSL("Warning no application protocols proposed!");
|
Log.logSSL("No application protocols proposed");
|
||||||
}
|
}
|
||||||
engine.setSSLParameters(sslParameters);
|
engine.setSSLParameters(sslParameters);
|
||||||
|
engine.setEnabledCipherSuites(sslp.getCipherSuites());
|
||||||
|
engine.setEnabledProtocols(sslp.getProtocols());
|
||||||
wrapper = new EngineWrapper(chan, engine);
|
wrapper = new EngineWrapper(chan, engine);
|
||||||
this.chan = chan;
|
this.chan = chan;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
@ -440,6 +438,27 @@ class SSLDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void printParams(SSLParameters p) {
|
||||||
|
System.out.println("SSLParameters:");
|
||||||
|
if (p == null) {
|
||||||
|
System.out.println("Null params");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String cipher : p.getCipherSuites()) {
|
||||||
|
System.out.printf("cipher: %s\n", cipher);
|
||||||
|
}
|
||||||
|
for (String approto : p.getApplicationProtocols()) {
|
||||||
|
System.out.printf("application protocol: %s\n", approto);
|
||||||
|
}
|
||||||
|
for (String protocol : p.getProtocols()) {
|
||||||
|
System.out.printf("protocol: %s\n", protocol);
|
||||||
|
}
|
||||||
|
if (p.getServerNames() != null)
|
||||||
|
for (SNIServerName sname : p.getServerNames()) {
|
||||||
|
System.out.printf("server name: %s\n", sname.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String getSessionInfo() {
|
String getSessionInfo() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
String application = engine.getApplicationProtocol();
|
String application = engine.getApplicationProtocol();
|
||||||
|
@ -130,12 +130,8 @@ class SSLTunnelConnection extends HttpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void close() {
|
public void close() {
|
||||||
try {
|
Utils.close(delegate.channel());
|
||||||
//System.err.println ("Closing: " + this);
|
|
||||||
delegate.channel().close(); // TODO: proper close
|
|
||||||
} catch (IOException ex) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class SettingsFrame extends Http2Frame {
|
||||||
|
|
||||||
|
int[] parameters;
|
||||||
|
|
||||||
|
public static final int TYPE = 0x4;
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
public static final int ACK = 0x1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String flagAsString(int flag) {
|
||||||
|
switch (flag) {
|
||||||
|
case ACK:
|
||||||
|
return "ACK";
|
||||||
|
}
|
||||||
|
return super.flagAsString(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(super.toString())
|
||||||
|
.append(" Settings: ");
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_PARAM; i++) {
|
||||||
|
if (parameters[i] != -1) {
|
||||||
|
sb.append(name(i))
|
||||||
|
.append("=")
|
||||||
|
.append(Integer.toString(parameters[i]))
|
||||||
|
.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
public static final int HEADER_TABLE_SIZE = 0x1;
|
||||||
|
public static final int ENABLE_PUSH = 0x2;
|
||||||
|
public static final int MAX_CONCURRENT_STREAMS = 0x3;
|
||||||
|
public static final int INITIAL_WINDOW_SIZE = 0x4;
|
||||||
|
public static final int MAX_FRAME_SIZE = 0x5;
|
||||||
|
public static final int MAX_HEADER_LIST_SIZE = 0x6;
|
||||||
|
|
||||||
|
private String name(int i) {
|
||||||
|
switch (i+1) {
|
||||||
|
case HEADER_TABLE_SIZE:
|
||||||
|
return "HEADER_TABLE_SIZE";
|
||||||
|
case ENABLE_PUSH:
|
||||||
|
return "ENABLE_PUSH";
|
||||||
|
case MAX_CONCURRENT_STREAMS:
|
||||||
|
return "MAX_CONCURRENT_STREAMS";
|
||||||
|
case INITIAL_WINDOW_SIZE:
|
||||||
|
return "INITIAL_WINDOW_SIZE";
|
||||||
|
case MAX_FRAME_SIZE:
|
||||||
|
return "MAX_FRAME_SIZE";
|
||||||
|
case MAX_HEADER_LIST_SIZE:
|
||||||
|
return "MAX_HEADER_LIST_SIZE";
|
||||||
|
}
|
||||||
|
return "unknown parameter";
|
||||||
|
}
|
||||||
|
public static final int MAX_PARAM = 0x6;
|
||||||
|
|
||||||
|
public SettingsFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
parameters = new int [MAX_PARAM];
|
||||||
|
for (int i=0; i < parameters.length; i++) {
|
||||||
|
parameters[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParameter(int paramID) {
|
||||||
|
if (paramID > MAX_PARAM) {
|
||||||
|
throw new IllegalArgumentException("illegal parameter");
|
||||||
|
}
|
||||||
|
return parameters[paramID-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingsFrame setParameter(int paramID, int value) {
|
||||||
|
if (paramID > MAX_PARAM) {
|
||||||
|
throw new IllegalArgumentException("illegal parameter");
|
||||||
|
}
|
||||||
|
parameters[paramID-1] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if (length % 6 != 0) {
|
||||||
|
throw new IOException("Protocol error: invalid settings frame");
|
||||||
|
}
|
||||||
|
int n = length / 6;
|
||||||
|
for (int i=0; i<n; i++) {
|
||||||
|
int id = bc.getShort();
|
||||||
|
int val = bc.getInt();
|
||||||
|
if (id > 0 || id <= MAX_PARAM) {
|
||||||
|
// a known parameter. Ignore otherwise
|
||||||
|
parameters[id-1] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = 0;
|
||||||
|
for (int i : parameters) {
|
||||||
|
if (i != -1) {
|
||||||
|
length += 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(length);
|
||||||
|
for (int i = 0; i < MAX_PARAM; i++) {
|
||||||
|
if (parameters[i] != -1) {
|
||||||
|
buf.putShort((short)(i+1));
|
||||||
|
buf.putInt(parameters[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int K = 1024;
|
||||||
|
|
||||||
|
public static SettingsFrame getDefaultSettings() {
|
||||||
|
SettingsFrame f = new SettingsFrame();
|
||||||
|
// TODO: check these values
|
||||||
|
f.setParameter(ENABLE_PUSH, 1);
|
||||||
|
f.setParameter(HEADER_TABLE_SIZE, 4 * K);
|
||||||
|
f.setParameter(MAX_CONCURRENT_STREAMS, 35);
|
||||||
|
f.setParameter(INITIAL_WINDOW_SIZE, 16 * K);
|
||||||
|
f.setParameter(MAX_FRAME_SIZE, 16 * K);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2016, 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,78 +24,819 @@
|
|||||||
|
|
||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
|
import sun.net.httpclient.hpack.DecodingCallback;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.LongConsumer;
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http/2 Stream
|
* Http/2 Stream handling.
|
||||||
|
*
|
||||||
|
* REQUESTS
|
||||||
|
*
|
||||||
|
* sendHeadersOnly() -- assembles HEADERS frame and puts on connection outbound Q
|
||||||
|
*
|
||||||
|
* sendRequest() -- sendHeadersOnly() + sendBody()
|
||||||
|
*
|
||||||
|
* sendBody() -- in calling thread: obeys all flow control (so may block)
|
||||||
|
* obtains data from request body processor and places on connection
|
||||||
|
* outbound Q.
|
||||||
|
*
|
||||||
|
* sendBodyAsync() -- calls sendBody() in an executor thread.
|
||||||
|
*
|
||||||
|
* sendHeadersAsync() -- calls sendHeadersOnly() which does not block
|
||||||
|
*
|
||||||
|
* sendRequestAsync() -- calls sendRequest() in an executor thread
|
||||||
|
*
|
||||||
|
* RESPONSES
|
||||||
|
*
|
||||||
|
* Multiple responses can be received per request. Responses are queued up on
|
||||||
|
* a LinkedList of CF<HttpResponse> and the the first one on the list is completed
|
||||||
|
* with the next response
|
||||||
|
*
|
||||||
|
* getResponseAsync() -- queries list of response CFs and returns first one
|
||||||
|
* if one exists. Otherwise, creates one and adds it to list
|
||||||
|
* and returns it. Completion is achieved through the
|
||||||
|
* incoming() upcall from connection reader thread.
|
||||||
|
*
|
||||||
|
* getResponse() -- calls getResponseAsync() and waits for CF to complete
|
||||||
|
*
|
||||||
|
* responseBody() -- in calling thread: blocks for incoming DATA frames on
|
||||||
|
* stream inputQ. Obeys remote and local flow control so may block.
|
||||||
|
* Calls user response body processor with data buffers.
|
||||||
|
*
|
||||||
|
* responseBodyAsync() -- calls responseBody() in an executor thread.
|
||||||
|
*
|
||||||
|
* incoming() -- entry point called from connection reader thread. Frames are
|
||||||
|
* either handled immediately without blocking or for data frames
|
||||||
|
* placed on the stream's inputQ which is consumed by the stream's
|
||||||
|
* reader thread.
|
||||||
|
*
|
||||||
|
* PushedStream sub class
|
||||||
|
* ======================
|
||||||
|
* Sending side methods are not used because the request comes from a PUSH_PROMISE
|
||||||
|
* frame sent by the server. When a PUSH_PROMISE is received the PushedStream
|
||||||
|
* is created. PushedStream does not use responseCF list as there can be only
|
||||||
|
* one response. The CF is created when the object created and when the response
|
||||||
|
* HEADERS frame is received the object is completed.
|
||||||
*/
|
*/
|
||||||
class Stream extends ExchangeImpl {
|
class Stream extends ExchangeImpl {
|
||||||
|
|
||||||
void debugPrint() {
|
final Queue<Http2Frame> inputQ;
|
||||||
}
|
|
||||||
|
volatile int streamid;
|
||||||
|
|
||||||
|
long responseContentLen = -1;
|
||||||
|
long responseBytesProcessed = 0;
|
||||||
|
long requestContentLen;
|
||||||
|
|
||||||
|
Http2Connection connection;
|
||||||
|
HttpClientImpl client;
|
||||||
|
final HttpRequestImpl request;
|
||||||
|
final DecodingCallback rspHeadersConsumer;
|
||||||
|
HttpHeadersImpl responseHeaders;
|
||||||
|
final HttpHeadersImpl requestHeaders;
|
||||||
|
final HttpHeadersImpl requestPseudoHeaders;
|
||||||
|
HttpResponse.BodyProcessor<?> responseProcessor;
|
||||||
|
final HttpRequest.BodyProcessor requestProcessor;
|
||||||
|
HttpResponse response;
|
||||||
|
|
||||||
|
// state flags
|
||||||
|
boolean requestSent, responseReceived;
|
||||||
|
|
||||||
|
final FlowController userRequestFlowController =
|
||||||
|
new FlowController();
|
||||||
|
final FlowController remoteRequestFlowController =
|
||||||
|
new FlowController();
|
||||||
|
final FlowController responseFlowController =
|
||||||
|
new FlowController();
|
||||||
|
|
||||||
|
final ExecutorWrapper executor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
<T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
|
<T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
|
||||||
return null;
|
this.responseProcessor = processor;
|
||||||
|
CompletableFuture<T> cf;
|
||||||
|
try {
|
||||||
|
T body = processor.onResponseBodyStart(
|
||||||
|
responseContentLen, responseHeaders,
|
||||||
|
responseFlowController); // TODO: filter headers
|
||||||
|
if (body != null) {
|
||||||
|
cf = CompletableFuture.completedFuture(body);
|
||||||
|
receiveDataAsync(processor);
|
||||||
|
} else
|
||||||
|
cf = receiveDataAsync(processor);
|
||||||
|
} catch (IOException e) {
|
||||||
|
cf = CompletableFuture.failedFuture(e);
|
||||||
}
|
}
|
||||||
|
PushGroup<?> pg = request.pushGroup();
|
||||||
Stream(HttpClientImpl client, Http2Connection connection, Exchange e) {
|
if (pg != null) {
|
||||||
super(e);
|
// if an error occurs make sure it is recorded in the PushGroup
|
||||||
|
cf = cf.whenComplete((t,e) -> pg.pushError(e));
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
HttpResponseImpl getResponse() throws IOException {
|
public String toString() {
|
||||||
return null;
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("streamid: ")
|
||||||
|
.append(streamid);
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// pushes entire response body into response processor
|
||||||
void sendRequest() throws IOException, InterruptedException {
|
// blocking when required by local or remote flow control
|
||||||
|
void receiveData() throws IOException {
|
||||||
|
Http2Frame frame;
|
||||||
|
DataFrame df = null;
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
frame = inputQ.take();
|
||||||
|
if (!(frame instanceof DataFrame)) {
|
||||||
|
assert false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
df = (DataFrame) frame;
|
||||||
|
int len = df.getDataLength();
|
||||||
|
ByteBuffer[] buffers = df.getData();
|
||||||
|
for (ByteBuffer b : buffers) {
|
||||||
|
responseFlowController.take();
|
||||||
|
responseProcessor.onResponseBodyChunk(b);
|
||||||
|
}
|
||||||
|
sendWindowUpdate(len);
|
||||||
|
} while (!df.getFlag(DataFrame.END_STREAM));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private <T> CompletableFuture<T> receiveDataAsync(HttpResponse.BodyProcessor<T> processor) {
|
||||||
void sendHeadersOnly() throws IOException, InterruptedException {
|
CompletableFuture<T> cf = new CompletableFuture<>();
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
receiveData();
|
||||||
|
T body = processor.onResponseComplete();
|
||||||
|
cf.complete(body);
|
||||||
|
responseReceived();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
cf.completeExceptionally(t);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void sendWindowUpdate(int increment)
|
||||||
void sendBody() throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
}
|
if (increment == 0)
|
||||||
|
return;
|
||||||
@Override
|
LinkedList<Http2Frame> list = new LinkedList<>();
|
||||||
CompletableFuture<Void> sendHeadersAsync() {
|
WindowUpdateFrame frame = new WindowUpdateFrame();
|
||||||
return null;
|
frame.streamid(streamid);
|
||||||
}
|
frame.setUpdate(increment);
|
||||||
|
list.add(frame);
|
||||||
@Override
|
frame = new WindowUpdateFrame();
|
||||||
CompletableFuture<HttpResponseImpl> getResponseAsync(Void v) {
|
frame.streamid(0);
|
||||||
return null;
|
frame.setUpdate(increment);
|
||||||
|
list.add(frame);
|
||||||
|
connection.sendFrames(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
CompletableFuture<Void> sendBodyAsync() {
|
CompletableFuture<Void> sendBodyAsync() {
|
||||||
return null;
|
final CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
sendBodyImpl();
|
||||||
|
cf.complete(null);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
cf.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Stream(HttpClientImpl client, Http2Connection connection, Exchange e) {
|
||||||
|
super(e);
|
||||||
|
this.client = client;
|
||||||
|
this.connection = connection;
|
||||||
|
this.request = e.request();
|
||||||
|
this.requestProcessor = request.requestProcessor();
|
||||||
|
responseHeaders = new HttpHeadersImpl();
|
||||||
|
requestHeaders = new HttpHeadersImpl();
|
||||||
|
rspHeadersConsumer = (name, value) -> {
|
||||||
|
responseHeaders.addHeader(name.toString(), value.toString());
|
||||||
|
};
|
||||||
|
this.executor = client.executorWrapper();
|
||||||
|
//this.response_cf = new CompletableFuture<HttpResponseImpl>();
|
||||||
|
this.requestPseudoHeaders = new HttpHeadersImpl();
|
||||||
|
// NEW
|
||||||
|
this.inputQ = new Queue<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Stream(HttpClientImpl client, Http2Connection connection, HttpRequestImpl req) {
|
||||||
|
super(null);
|
||||||
|
this.client = client;
|
||||||
|
this.connection = connection;
|
||||||
|
this.request = req;
|
||||||
|
this.requestProcessor = null;
|
||||||
|
responseHeaders = new HttpHeadersImpl();
|
||||||
|
requestHeaders = new HttpHeadersImpl();
|
||||||
|
rspHeadersConsumer = (name, value) -> {
|
||||||
|
responseHeaders.addHeader(name.toString(), value.toString());
|
||||||
|
};
|
||||||
|
this.executor = client.executorWrapper();
|
||||||
|
//this.response_cf = new CompletableFuture<HttpResponseImpl>();
|
||||||
|
this.requestPseudoHeaders = new HttpHeadersImpl();
|
||||||
|
// NEW
|
||||||
|
this.inputQ = new Queue<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point from Http2Connection reader thread.
|
||||||
|
*
|
||||||
|
* Data frames will be removed by response body thread.
|
||||||
|
*
|
||||||
|
* @param frame
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
void incoming(Http2Frame frame) throws IOException, InterruptedException {
|
||||||
|
if ((frame instanceof HeaderFrame) && ((HeaderFrame)frame).endHeaders()) {
|
||||||
|
// Complete headers accumulated. handle response.
|
||||||
|
// It's okay if there are multiple HeaderFrames.
|
||||||
|
handleResponse();
|
||||||
|
} else if (frame instanceof DataFrame) {
|
||||||
|
inputQ.put(frame);
|
||||||
|
} else {
|
||||||
|
otherFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void otherFrame(Http2Frame frame) throws IOException {
|
||||||
|
switch (frame.type()) {
|
||||||
|
case WindowUpdateFrame.TYPE:
|
||||||
|
incoming_windowUpdate((WindowUpdateFrame) frame);
|
||||||
|
break;
|
||||||
|
case ResetFrame.TYPE:
|
||||||
|
incoming_reset((ResetFrame) frame);
|
||||||
|
break;
|
||||||
|
case PriorityFrame.TYPE:
|
||||||
|
incoming_priority((PriorityFrame) frame);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
String msg = "Unexpected frame: " + frame.toString();
|
||||||
|
throw new IOException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Hpack decoder decodes into one of these consumers of name,value pairs
|
||||||
|
|
||||||
|
DecodingCallback rspHeadersConsumer() {
|
||||||
|
return rspHeadersConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and return the HttpResponseImpl
|
||||||
|
protected void handleResponse() throws IOException {
|
||||||
|
HttpConnection c = connection.connection; // TODO: improve
|
||||||
|
long statusCode = responseHeaders
|
||||||
|
.firstValueAsLong(":status")
|
||||||
|
.orElseThrow(() -> new IOException("no statuscode in response"));
|
||||||
|
|
||||||
|
this.response = new HttpResponseImpl((int)statusCode, exchange, responseHeaders, null,
|
||||||
|
c.sslParameters(), HttpClient.Version.HTTP_2, c);
|
||||||
|
this.responseContentLen = responseHeaders
|
||||||
|
.firstValueAsLong("content-length")
|
||||||
|
.orElse(-1L);
|
||||||
|
// different implementations for normal streams and pushed streams
|
||||||
|
completeResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void incoming_reset(ResetFrame frame) {
|
||||||
|
// TODO: implement reset
|
||||||
|
int error = frame.getErrorCode();
|
||||||
|
IOException e = new IOException(ErrorFrame.stringForCode(error));
|
||||||
|
completeResponseExceptionally(e);
|
||||||
|
throw new UnsupportedOperationException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
void incoming_priority(PriorityFrame frame) {
|
||||||
|
// TODO: implement priority
|
||||||
|
throw new UnsupportedOperationException("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
void incoming_windowUpdate(WindowUpdateFrame frame) {
|
||||||
|
int amount = frame.getUpdate();
|
||||||
|
if (amount > 0)
|
||||||
|
remoteRequestFlowController.accept(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void incoming_pushPromise(HttpRequestImpl pushReq, PushedStream pushStream) throws IOException {
|
||||||
|
if (Log.requests()) {
|
||||||
|
Log.logRequest("PUSH_PROMISE: " + pushReq.toString());
|
||||||
|
}
|
||||||
|
PushGroup<?> pushGroup = request.pushGroup();
|
||||||
|
if (pushGroup == null) {
|
||||||
|
cancelImpl(new IllegalStateException("unexpected push promise"));
|
||||||
|
}
|
||||||
|
// get the handler and call it.
|
||||||
|
BiFunction<HttpRequest,CompletableFuture<HttpResponse>,Boolean> ph =
|
||||||
|
pushGroup.pushHandler();
|
||||||
|
|
||||||
|
CompletableFuture<HttpResponse> pushCF = pushStream
|
||||||
|
.getResponseAsync(null)
|
||||||
|
.thenApply(r -> (HttpResponse)r);
|
||||||
|
boolean accept = ph.apply(pushReq, pushCF);
|
||||||
|
if (!accept) {
|
||||||
|
IOException ex = new IOException("Stream cancelled by user");
|
||||||
|
cancelImpl(ex);
|
||||||
|
pushCF.completeExceptionally(ex);
|
||||||
|
} else {
|
||||||
|
pushStream.requestSent();
|
||||||
|
pushGroup.addPush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutgoingHeaders headerFrame(long contentLength) {
|
||||||
|
HttpHeadersImpl h = request.getSystemHeaders();
|
||||||
|
if (contentLength > 0) {
|
||||||
|
h.setHeader("content-length", Long.toString(contentLength));
|
||||||
|
}
|
||||||
|
setPseudoHeaderFields();
|
||||||
|
OutgoingHeaders f = new OutgoingHeaders(h, request.getUserHeaders(), this);
|
||||||
|
if (contentLength == 0) {
|
||||||
|
f.setFlag(HeadersFrame.END_STREAM);
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPseudoHeaderFields() {
|
||||||
|
HttpHeadersImpl hdrs = requestPseudoHeaders;
|
||||||
|
String method = request.method();
|
||||||
|
hdrs.setHeader(":method", method);
|
||||||
|
URI uri = request.uri();
|
||||||
|
hdrs.setHeader(":scheme", uri.getScheme());
|
||||||
|
// TODO: userinfo deprecated. Needs to be removed
|
||||||
|
hdrs.setHeader(":authority", uri.getAuthority());
|
||||||
|
// TODO: ensure header names beginning with : not in user headers
|
||||||
|
String query = uri.getQuery();
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (path == null) {
|
||||||
|
if (method.equalsIgnoreCase("OPTIONS")) {
|
||||||
|
path = "*";
|
||||||
|
} else {
|
||||||
|
path = "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query != null) {
|
||||||
|
path += "?" + query;
|
||||||
|
}
|
||||||
|
hdrs.setHeader(":path", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeadersImpl getRequestPseudoHeaders() {
|
||||||
|
return requestPseudoHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
HttpResponseImpl getResponse() throws IOException {
|
||||||
|
try {
|
||||||
|
return getResponseAsync(null).join();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Throwable t = e.getCause();
|
||||||
|
if (t instanceof IOException) {
|
||||||
|
throw (IOException)t;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendRequest() throws IOException, InterruptedException {
|
||||||
|
sendHeadersOnly();
|
||||||
|
sendBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple general purpose blocking flow controller
|
||||||
|
*/
|
||||||
|
class FlowController implements LongConsumer {
|
||||||
|
int permits;
|
||||||
|
|
||||||
|
FlowController() {
|
||||||
|
this.permits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void accept(long n) {
|
||||||
|
if (n < 1) {
|
||||||
|
throw new InternalError("FlowController.accept called with " + n);
|
||||||
|
}
|
||||||
|
if (permits == 0) {
|
||||||
|
permits += n;
|
||||||
|
notifyAll();
|
||||||
|
} else {
|
||||||
|
permits += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void take() throws InterruptedException {
|
||||||
|
take(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void take(int amount) throws InterruptedException {
|
||||||
|
assert permits >= 0;
|
||||||
|
while (permits < amount) {
|
||||||
|
int n = Math.min(amount, permits);
|
||||||
|
permits -= n;
|
||||||
|
amount -= n;
|
||||||
|
if (amount > 0)
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendHeadersOnly() throws IOException, InterruptedException {
|
||||||
|
if (Log.requests() && request != null) {
|
||||||
|
Log.logRequest(request.toString());
|
||||||
|
}
|
||||||
|
requestContentLen = requestProcessor.onRequestStart(request, userRequestFlowController);
|
||||||
|
OutgoingHeaders f = headerFrame(requestContentLen);
|
||||||
|
connection.sendFrame(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendBody() throws IOException, InterruptedException {
|
||||||
|
sendBodyImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerStream(int id) {
|
||||||
|
this.streamid = id;
|
||||||
|
connection.putStream(this, streamid);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFrame getDataFrame() throws IOException, InterruptedException {
|
||||||
|
userRequestFlowController.take();
|
||||||
|
int maxpayloadLen = connection.getMaxSendFrameSize() - 9;
|
||||||
|
ByteBuffer buffer = connection.getBuffer();
|
||||||
|
buffer.limit(maxpayloadLen);
|
||||||
|
boolean complete = requestProcessor.onRequestBodyChunk(buffer);
|
||||||
|
buffer.flip();
|
||||||
|
int amount = buffer.remaining();
|
||||||
|
// wait for flow control if necessary. Following method will block
|
||||||
|
// until after headers frame is sent, so correct streamid is set.
|
||||||
|
remoteRequestFlowController.take(amount);
|
||||||
|
connection.obtainSendWindow(amount);
|
||||||
|
|
||||||
|
DataFrame df = new DataFrame();
|
||||||
|
df.streamid(streamid);
|
||||||
|
if (complete) {
|
||||||
|
df.setFlag(DataFrame.END_STREAM);
|
||||||
|
}
|
||||||
|
df.setData(buffer);
|
||||||
|
df.computeLength();
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompletableFuture<Void> sendHeadersAsync() {
|
||||||
|
try {
|
||||||
|
sendHeadersOnly();
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
} catch (IOException | InterruptedException ex) {
|
||||||
|
return CompletableFuture.failedFuture(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A List of responses relating to this stream. Normally there is only
|
||||||
|
* one response, but intermediate responses like 100 are allowed
|
||||||
|
* and must be passed up to higher level before continuing. Deals with races
|
||||||
|
* such as if responses are returned before the CFs get created by
|
||||||
|
* getResponseAsync()
|
||||||
|
*/
|
||||||
|
|
||||||
|
final List<CompletableFuture<HttpResponseImpl>> response_cfs = new LinkedList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompletableFuture<HttpResponseImpl> getResponseAsync(Void v) {
|
||||||
|
CompletableFuture<HttpResponseImpl> cf;
|
||||||
|
synchronized (response_cfs) {
|
||||||
|
if (!response_cfs.isEmpty()) {
|
||||||
|
cf = response_cfs.remove(0);
|
||||||
|
} else {
|
||||||
|
cf = new CompletableFuture<>();
|
||||||
|
response_cfs.add(cf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PushGroup<?> pg = request.pushGroup();
|
||||||
|
if (pg != null) {
|
||||||
|
// if an error occurs make sure it is recorded in the PushGroup
|
||||||
|
cf = cf.whenComplete((t,e) -> pg.pushError(e));
|
||||||
|
}
|
||||||
|
return cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the first uncompleted CF on list, and removes it. If there is no
|
||||||
|
* uncompleted CF then creates one (completes it) and adds to list
|
||||||
|
*/
|
||||||
|
void completeResponse(HttpResponse r) {
|
||||||
|
HttpResponseImpl resp = (HttpResponseImpl)r;
|
||||||
|
synchronized (response_cfs) {
|
||||||
|
for (CompletableFuture<HttpResponseImpl> cf : response_cfs) {
|
||||||
|
if (!cf.isDone()) {
|
||||||
|
cf.complete(resp);
|
||||||
|
response_cfs.remove(cf);
|
||||||
|
//responseHeaders = new HttpHeadersImpl(); // for any following header blocks
|
||||||
|
return;
|
||||||
|
} else
|
||||||
|
System.err.println("Stream: " + this + " ALREADY DONE");
|
||||||
|
}
|
||||||
|
response_cfs.add(CompletableFuture.completedFuture(resp));
|
||||||
|
//responseHeaders = new HttpHeadersImpl(); // for any following header blocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods to update state and remove stream when finished
|
||||||
|
|
||||||
|
synchronized void requestSent() {
|
||||||
|
requestSent = true;
|
||||||
|
if (responseReceived)
|
||||||
|
connection.deleteStream(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void responseReceived() {
|
||||||
|
responseReceived = true;
|
||||||
|
if (requestSent)
|
||||||
|
connection.deleteStream(this);
|
||||||
|
PushGroup<?> pg = request.pushGroup();
|
||||||
|
if (pg != null)
|
||||||
|
pg.noMorePushes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* same as above but for errors
|
||||||
|
*
|
||||||
|
* @param t
|
||||||
|
*/
|
||||||
|
void completeResponseExceptionally(Throwable t) {
|
||||||
|
synchronized (response_cfs) {
|
||||||
|
for (CompletableFuture<HttpResponseImpl> cf : response_cfs) {
|
||||||
|
if (!cf.isDone()) {
|
||||||
|
cf.completeExceptionally(t);
|
||||||
|
response_cfs.remove(cf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response_cfs.add(CompletableFuture.failedFuture(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendBodyImpl() throws IOException, InterruptedException {
|
||||||
|
if (requestContentLen == 0) {
|
||||||
|
// no body
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DataFrame df;
|
||||||
|
do {
|
||||||
|
df = getDataFrame();
|
||||||
|
// TODO: check accumulated content length (if not checked below)
|
||||||
|
connection.sendFrame(df);
|
||||||
|
} while (!df.getFlag(DataFrame.END_STREAM));
|
||||||
|
requestSent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void cancel() {
|
void cancel() {
|
||||||
|
cancelImpl(new Exception("Cancelled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void cancelImpl(Throwable e) {
|
||||||
|
Log.logTrace("cancelling stream: {0}\n", e.toString());
|
||||||
|
inputQ.close();
|
||||||
|
try {
|
||||||
|
connection.resetStream(streamid, ResetFrame.CANCEL);
|
||||||
|
} catch (IOException | InterruptedException ex) {
|
||||||
|
Log.logError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
CompletableFuture<Void> sendRequestAsync() {
|
CompletableFuture<Void> sendRequestAsync() {
|
||||||
return null;
|
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||||
|
executor.execute(() -> {
|
||||||
|
try {
|
||||||
|
sendRequest();
|
||||||
|
cf.complete(null);
|
||||||
|
} catch (IOException |InterruptedException e) {
|
||||||
|
cf.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
<T> T responseBody(HttpResponse.BodyProcessor<T> processor) throws IOException {
|
<T> T responseBody(HttpResponse.BodyProcessor<T> processor) throws IOException {
|
||||||
return null;
|
this.responseProcessor = processor;
|
||||||
|
T body = processor.onResponseBodyStart(
|
||||||
|
responseContentLen, responseHeaders,
|
||||||
|
responseFlowController); // TODO: filter headers
|
||||||
|
if (body == null) {
|
||||||
|
receiveData();
|
||||||
|
return processor.onResponseComplete();
|
||||||
|
} else
|
||||||
|
receiveDataAsync(processor);
|
||||||
|
responseReceived();
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
// called from Http2Connection reader thread
|
||||||
|
synchronized void updateOutgoingWindow(int update) {
|
||||||
|
remoteRequestFlowController.accept(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close(String msg) {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PushedStream extends Stream {
|
||||||
|
final PushGroup<?> pushGroup;
|
||||||
|
final private Stream parent; // used by server push streams
|
||||||
|
// push streams need the response CF allocated up front as it is
|
||||||
|
// given directly to user via the multi handler callback function.
|
||||||
|
final CompletableFuture<HttpResponseImpl> pushCF;
|
||||||
|
final HttpRequestImpl pushReq;
|
||||||
|
|
||||||
|
PushedStream(PushGroup<?> pushGroup, HttpClientImpl client,
|
||||||
|
Http2Connection connection, Stream parent,
|
||||||
|
HttpRequestImpl pushReq) {
|
||||||
|
super(client, connection, pushReq);
|
||||||
|
this.pushGroup = pushGroup;
|
||||||
|
this.pushReq = pushReq;
|
||||||
|
this.pushCF = new CompletableFuture<>();
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Following methods call the super class but in case of
|
||||||
|
// error record it in the PushGroup. The error method is called
|
||||||
|
// with a null value when no error occurred (is a no-op)
|
||||||
|
@Override
|
||||||
|
CompletableFuture<Void> sendBodyAsync() {
|
||||||
|
return super.sendBodyAsync()
|
||||||
|
.whenComplete((v, t) -> pushGroup.pushError(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompletableFuture<Void> sendHeadersAsync() {
|
||||||
|
return super.sendHeadersAsync()
|
||||||
|
.whenComplete((v, t) -> pushGroup.pushError(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompletableFuture<Void> sendRequestAsync() {
|
||||||
|
return super.sendRequestAsync()
|
||||||
|
.whenComplete((v, t) -> pushGroup.pushError(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CompletableFuture<HttpResponseImpl> getResponseAsync(Void vo) {
|
||||||
|
return pushCF.whenComplete((v, t) -> pushGroup.pushError(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
<T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
|
||||||
|
return super.responseBodyAsync(processor)
|
||||||
|
.whenComplete((v, t) -> pushGroup.pushError(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void completeResponse(HttpResponse r) {
|
||||||
|
HttpResponseImpl resp = (HttpResponseImpl)r;
|
||||||
|
Utils.logResponse(resp);
|
||||||
|
pushCF.complete(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void completeResponseExceptionally(Throwable t) {
|
||||||
|
pushCF.completeExceptionally(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
synchronized void responseReceived() {
|
||||||
|
super.responseReceived();
|
||||||
|
pushGroup.pushCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and return the PushResponseImpl
|
||||||
|
@Override
|
||||||
|
protected void handleResponse() {
|
||||||
|
HttpConnection c = connection.connection; // TODO: improve
|
||||||
|
long statusCode = responseHeaders
|
||||||
|
.firstValueAsLong(":status")
|
||||||
|
.orElse(-1L);
|
||||||
|
|
||||||
|
if (statusCode == -1L)
|
||||||
|
completeResponseExceptionally(new IOException("No status code"));
|
||||||
|
ImmutableHeaders h = new ImmutableHeaders(responseHeaders, Utils.ALL_HEADERS);
|
||||||
|
this.response = new HttpResponseImpl((int)statusCode, pushReq, h, this,
|
||||||
|
c.sslParameters());
|
||||||
|
this.responseContentLen = responseHeaders
|
||||||
|
.firstValueAsLong("content-length")
|
||||||
|
.orElse(-1L);
|
||||||
|
// different implementations for normal streams and pushed streams
|
||||||
|
completeResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One PushGroup object is associated with the parent Stream of
|
||||||
|
* the pushed Streams. This keeps track of all common state associated
|
||||||
|
* with the pushes.
|
||||||
|
*/
|
||||||
|
static class PushGroup<T> {
|
||||||
|
// the overall completion object, completed when all pushes are done.
|
||||||
|
final CompletableFuture<T> resultCF;
|
||||||
|
Throwable error; // any exception that occured during pushes
|
||||||
|
|
||||||
|
// CF for main response
|
||||||
|
final CompletableFuture<HttpResponse> mainResponse;
|
||||||
|
|
||||||
|
// user's processor object
|
||||||
|
final HttpResponse.MultiProcessor<T> multiProcessor;
|
||||||
|
|
||||||
|
// per push handler function provided by processor
|
||||||
|
final private BiFunction<HttpRequest,
|
||||||
|
CompletableFuture<HttpResponse>,
|
||||||
|
Boolean> pushHandler;
|
||||||
|
int numberOfPushes;
|
||||||
|
int remainingPushes;
|
||||||
|
boolean noMorePushes = false;
|
||||||
|
|
||||||
|
PushGroup(HttpResponse.MultiProcessor<T> multiProcessor, HttpRequestImpl req) {
|
||||||
|
this.resultCF = new CompletableFuture<>();
|
||||||
|
this.mainResponse = new CompletableFuture<>();
|
||||||
|
this.multiProcessor = multiProcessor;
|
||||||
|
this.pushHandler = multiProcessor.onStart(req, mainResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<T> groupResult() {
|
||||||
|
return resultCF;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<HttpResponse> mainResponse() {
|
||||||
|
return mainResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiFunction<HttpRequest,
|
||||||
|
CompletableFuture<HttpResponse>, Boolean> pushHandler()
|
||||||
|
{
|
||||||
|
return pushHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void addPush() {
|
||||||
|
numberOfPushes++;
|
||||||
|
remainingPushes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized int numberOfPushes() {
|
||||||
|
return numberOfPushes;
|
||||||
|
}
|
||||||
|
// This is called when the main body response completes because it means
|
||||||
|
// no more PUSH_PROMISEs are possible
|
||||||
|
synchronized void noMorePushes() {
|
||||||
|
noMorePushes = true;
|
||||||
|
checkIfCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void pushCompleted() {
|
||||||
|
remainingPushes--;
|
||||||
|
checkIfCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void checkIfCompleted() {
|
||||||
|
if (remainingPushes == 0 && error == null && noMorePushes) {
|
||||||
|
T overallResult = multiProcessor.onComplete();
|
||||||
|
resultCF.complete(overallResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void pushError(Throwable t) {
|
||||||
|
if (t == null)
|
||||||
|
return;
|
||||||
|
this.error = t;
|
||||||
|
resultCF.completeExceptionally(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,28 +21,37 @@
|
|||||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 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
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package java.net.http;
|
package java.net.http;
|
||||||
|
|
||||||
|
import sun.net.NetProperties;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.NetPermission;
|
import java.net.NetPermission;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLPermission;
|
import java.net.URLPermission;
|
||||||
|
import java.nio.Buffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.net.ssl.SSLParameters;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import sun.net.NetProperties;
|
import java.util.function.LongBinaryOperator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Miscellaneous utilities
|
* Miscellaneous utilities
|
||||||
*/
|
*/
|
||||||
class Utils {
|
final class Utils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocated buffer size. Must never be higher than 16K. But can be lower
|
* Allocated buffer size. Must never be higher than 16K. But can be lower
|
||||||
@ -51,7 +60,59 @@ class Utils {
|
|||||||
*/
|
*/
|
||||||
public static final int BUFSIZE = 16 * 1024;
|
public static final int BUFSIZE = 16 * 1024;
|
||||||
|
|
||||||
/** Validates a RFC7230 token */
|
private static final Set<String> DISALLOWED_HEADERS_SET = Set.of(
|
||||||
|
"authorization", "connection", "cookie", "content-length",
|
||||||
|
"date", "expect", "from", "host", "origin", "proxy-authorization",
|
||||||
|
"referer", "user-agent", "upgrade", "via", "warning");
|
||||||
|
|
||||||
|
static final Predicate<String>
|
||||||
|
ALLOWED_HEADERS = header -> !Utils.DISALLOWED_HEADERS_SET.contains(header);
|
||||||
|
|
||||||
|
static final Predicate<String>
|
||||||
|
ALL_HEADERS = header -> true;
|
||||||
|
|
||||||
|
static InetSocketAddress getAddress(HttpRequestImpl req) {
|
||||||
|
URI uri = req.uri();
|
||||||
|
if (uri == null) {
|
||||||
|
return req.authority();
|
||||||
|
}
|
||||||
|
int port = uri.getPort();
|
||||||
|
if (port == -1) {
|
||||||
|
if (uri.getScheme().equalsIgnoreCase("https")) {
|
||||||
|
port = 443;
|
||||||
|
} else {
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String host = uri.getHost();
|
||||||
|
if (req.proxy() == null) {
|
||||||
|
return new InetSocketAddress(host, port);
|
||||||
|
} else {
|
||||||
|
return InetSocketAddress.createUnresolved(host, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts position to limit and limit to capacity so we can resume reading
|
||||||
|
* into this buffer, but if required > 0 then limit may be reduced so that
|
||||||
|
* no more than required bytes are read next time.
|
||||||
|
*/
|
||||||
|
static void resumeChannelRead(ByteBuffer buf, int required) {
|
||||||
|
int limit = buf.limit();
|
||||||
|
buf.position(limit);
|
||||||
|
int capacity = buf.capacity() - limit;
|
||||||
|
if (required > 0 && required < capacity) {
|
||||||
|
buf.limit(limit + required);
|
||||||
|
} else {
|
||||||
|
buf.limit(buf.capacity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Utils() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a RFC7230 token
|
||||||
|
*/
|
||||||
static void validateToken(String token, String errormsg) {
|
static void validateToken(String token, String errormsg) {
|
||||||
int length = token.length();
|
int length = token.length();
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
@ -69,7 +130,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return sthe security permission required for the given details.
|
* Returns the security permission required for the given details.
|
||||||
* If method is CONNECT, then uri must be of form "scheme://host:port"
|
* If method is CONNECT, then uri must be of form "scheme://host:port"
|
||||||
*/
|
*/
|
||||||
static URLPermission getPermission(URI uri,
|
static URLPermission getPermission(URI uri,
|
||||||
@ -117,13 +178,13 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int getIntegerNetProperty(String name, int defaultValue) {
|
static int getIntegerNetProperty(String name, int defaultValue) {
|
||||||
return AccessController.doPrivileged((PrivilegedAction<Integer>)() ->
|
return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
|
||||||
NetProperties.getInteger(name, defaultValue) );
|
NetProperties.getInteger(name, defaultValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getNetProperty(String name) {
|
static String getNetProperty(String name) {
|
||||||
return AccessController.doPrivileged((PrivilegedAction<String>)() ->
|
return AccessController.doPrivileged((PrivilegedAction<String>) () ->
|
||||||
NetProperties.get(name) );
|
NetProperties.get(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
static SSLParameters copySSLParameters(SSLParameters p) {
|
static SSLParameters copySSLParameters(SSLParameters p) {
|
||||||
@ -134,7 +195,9 @@ class Utils {
|
|||||||
p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
|
p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
|
||||||
p1.setMaximumPacketSize(p.getMaximumPacketSize());
|
p1.setMaximumPacketSize(p.getMaximumPacketSize());
|
||||||
p1.setNeedClientAuth(p.getNeedClientAuth());
|
p1.setNeedClientAuth(p.getNeedClientAuth());
|
||||||
p1.setProtocols(p.getProtocols().clone());
|
String[] protocols = p.getProtocols();
|
||||||
|
if (protocols != null)
|
||||||
|
p1.setProtocols(protocols.clone());
|
||||||
p1.setSNIMatchers(p.getSNIMatchers());
|
p1.setSNIMatchers(p.getSNIMatchers());
|
||||||
p1.setServerNames(p.getServerNames());
|
p1.setServerNames(p.getServerNames());
|
||||||
p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
|
p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
|
||||||
@ -142,33 +205,14 @@ class Utils {
|
|||||||
return p1;
|
return p1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Resumes reading into the given buffer. */
|
|
||||||
static void unflip(ByteBuffer buf) {
|
|
||||||
buf.position(buf.limit());
|
|
||||||
buf.limit(buf.capacity());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set limit to position, and position to mark.
|
* Set limit to position, and position to mark.
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param buffer
|
|
||||||
* @param mark
|
|
||||||
*/
|
*/
|
||||||
static void flipToMark(ByteBuffer buffer, int mark) {
|
static void flipToMark(ByteBuffer buffer, int mark) {
|
||||||
buffer.limit(buffer.position());
|
buffer.limit(buffer.position());
|
||||||
buffer.position(mark);
|
buffer.position(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compact and leave ready for reading. */
|
|
||||||
static void compact(List<ByteBuffer> buffers) {
|
|
||||||
for (ByteBuffer b : buffers) {
|
|
||||||
b.compact();
|
|
||||||
b.flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String stackTrace(Throwable t) {
|
static String stackTrace(Throwable t) {
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
String s = null;
|
String s = null;
|
||||||
@ -182,8 +226,10 @@ class Utils {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Copies as much of src to dst as possible. */
|
/**
|
||||||
static void copy (ByteBuffer src, ByteBuffer dst) {
|
* Copies as much of src to dst as possible.
|
||||||
|
*/
|
||||||
|
static void copy(ByteBuffer src, ByteBuffer dst) {
|
||||||
int srcLen = src.remaining();
|
int srcLen = src.remaining();
|
||||||
int dstLen = dst.remaining();
|
int dstLen = dst.remaining();
|
||||||
if (srcLen > dstLen) {
|
if (srcLen > dstLen) {
|
||||||
@ -204,18 +250,101 @@ class Utils {
|
|||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String combine(String[] s) {
|
//
|
||||||
|
// Helps to trim long names (packages, nested/inner types) in logs/toString
|
||||||
|
//
|
||||||
|
static String toStringSimple(Object o) {
|
||||||
|
return o.getClass().getSimpleName() + "@" +
|
||||||
|
Integer.toHexString(System.identityHashCode(o));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 1. It adds a number of remaining bytes;
|
||||||
|
// 2. Standard Buffer-type toString for CharBuffer (since it adheres to the
|
||||||
|
// contract of java.lang.CharSequence.toString() which is both not too
|
||||||
|
// useful and not too private)
|
||||||
|
//
|
||||||
|
static String toString(Buffer b) {
|
||||||
|
return toStringSimple(b)
|
||||||
|
+ "[pos=" + b.position()
|
||||||
|
+ " lim=" + b.limit()
|
||||||
|
+ " cap=" + b.capacity()
|
||||||
|
+ " rem=" + b.remaining() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toString(CharSequence s) {
|
||||||
|
return s == null
|
||||||
|
? "null"
|
||||||
|
: toStringSimple(s) + "[len=" + s.length() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String dump(Object... objects) {
|
||||||
|
return Arrays.toString(objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final System.Logger logger = System.getLogger("java.net.http.WebSocket");
|
||||||
|
|
||||||
|
static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0);
|
||||||
|
|
||||||
|
static String webSocketSpecViolation(String section, String detail) {
|
||||||
|
return "RFC 6455 " + section + " " + detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logResponse(HttpResponseImpl r) {
|
||||||
|
if (!Log.requests()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append('[');
|
String method = r.request().method();
|
||||||
boolean first = true;
|
URI uri = r.uri();
|
||||||
for (String s1 : s) {
|
String uristring = uri == null ? "" : uri.toString();
|
||||||
if (!first) {
|
sb.append('(').append(method).append(" ").append(uristring).append(") ").append(Integer.toString(r.statusCode()));
|
||||||
sb.append(", ");
|
Log.logResponse(sb.toString());
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
sb.append(s1);
|
|
||||||
|
static int remaining(ByteBuffer[] bufs) {
|
||||||
|
int remain = 0;
|
||||||
|
for (ByteBuffer buf : bufs)
|
||||||
|
remain += buf.remaining();
|
||||||
|
return remain;
|
||||||
}
|
}
|
||||||
sb.append(']');
|
|
||||||
return sb.toString();
|
// assumes buffer was written into starting at position zero
|
||||||
|
static void unflip(ByteBuffer buf) {
|
||||||
|
buf.position(buf.limit());
|
||||||
|
buf.limit(buf.capacity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void close(Closeable... chans) {
|
||||||
|
for (Closeable chan : chans) {
|
||||||
|
System.err.println("Closing " + chan);
|
||||||
|
try {
|
||||||
|
chan.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ByteBuffer[] reduce(ByteBuffer[] bufs, int start, int number) {
|
||||||
|
if (start == 0 && number == bufs.length)
|
||||||
|
return bufs;
|
||||||
|
ByteBuffer[] nbufs = new ByteBuffer[number];
|
||||||
|
int j = 0;
|
||||||
|
for (int i=start; i<start+number; i++)
|
||||||
|
nbufs[j++] = bufs[i];
|
||||||
|
return nbufs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String asString(ByteBuffer buf) {
|
||||||
|
byte[] b = new byte[buf.remaining()];
|
||||||
|
buf.get(b);
|
||||||
|
return new String(b, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put all these static 'empty' singletons here
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
static CompletableFuture[] EMPTY_CFARRAY = new CompletableFuture[0];
|
||||||
|
|
||||||
|
static ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
|
||||||
|
static ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
class WindowUpdateFrame extends Http2Frame {
|
||||||
|
|
||||||
|
int windowUpdate;
|
||||||
|
|
||||||
|
WindowUpdateFrame() {
|
||||||
|
type = TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static int TYPE = 0x8;
|
||||||
|
|
||||||
|
public void setUpdate(int windowUpdate) {
|
||||||
|
this.windowUpdate = windowUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(super.toString())
|
||||||
|
.append(" WindowUpdate: ")
|
||||||
|
.append(windowUpdate);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUpdate() {
|
||||||
|
return this.windowUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
if (length != 4) {
|
||||||
|
throw new IOException("Invalid WindowUpdate frame");
|
||||||
|
}
|
||||||
|
windowUpdate = bc.getInt() & 0x7fffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
length = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void writeOutgoing(ByteBufferGenerator bg) {
|
||||||
|
super.writeOutgoing(bg);
|
||||||
|
ByteBuffer buf = bg.getBuffer(4);
|
||||||
|
buf.putInt(windowUpdate);
|
||||||
|
}
|
||||||
|
}
|
@ -24,11 +24,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <h2>High level HTTP API</h2>
|
* <h2>High level HTTP and WebSocket API</h2>
|
||||||
* This provides a high-level client interface to HTTP (versions 1.1 and 2).
|
* This provides a high-level client interfaces to HTTP (versions 1.1 and 2)
|
||||||
* Synchronous and asynchronous (via
|
* and WebSocket. Synchronous and asynchronous (via {@link
|
||||||
* {@link java.util.concurrent.CompletableFuture}) modes are provided. The main
|
* java.util.concurrent.CompletableFuture}) modes are provided for HTTP.
|
||||||
* classes defined are:
|
* WebSocket works in asynchronous mode only. The main types defined are:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link java.net.http.HttpClient}</li>
|
* <li>{@link java.net.http.HttpClient}</li>
|
||||||
* <li>{@link java.net.http.HttpRequest}</li>
|
* <li>{@link java.net.http.HttpRequest}</li>
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
* @bug 8087112
|
* @bug 8087112
|
||||||
* @library /lib/testlibrary/
|
* @library /lib/testlibrary/
|
||||||
* @build jdk.testlibrary.SimpleSSLContext ProxyServer
|
* @build jdk.testlibrary.SimpleSSLContext ProxyServer
|
||||||
|
* @build TestKit
|
||||||
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
||||||
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
||||||
* @run main/othervm APIErrors
|
* @run main/othervm APIErrors
|
||||||
@ -73,26 +74,6 @@ public class APIErrors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reject(Runnable r, Class<? extends Exception> extype) {
|
|
||||||
try {
|
|
||||||
r.run();
|
|
||||||
throw new RuntimeException("Expected: " + extype);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (!extype.isAssignableFrom(t.getClass())) {
|
|
||||||
throw new RuntimeException("Wrong exception type: " + extype + " / "
|
|
||||||
+t.getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void accept(Runnable r) {
|
|
||||||
try {
|
|
||||||
r.run();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
throw new RuntimeException("Unexpected exception: " + t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void checkNonNull(Supplier<?> r) {
|
static void checkNonNull(Supplier<?> r) {
|
||||||
if (r.get() == null)
|
if (r.get() == null)
|
||||||
throw new RuntimeException("Unexpected null return:");
|
throw new RuntimeException("Unexpected null return:");
|
||||||
@ -108,12 +89,14 @@ public class APIErrors {
|
|||||||
System.out.println("Test 1");
|
System.out.println("Test 1");
|
||||||
HttpClient.Builder cb = HttpClient.create();
|
HttpClient.Builder cb = HttpClient.create();
|
||||||
InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 5000);
|
InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 5000);
|
||||||
reject(() -> { cb.priority(-1);}, IllegalArgumentException.class);
|
TestKit.assertThrows(IllegalArgumentException.class, () -> cb.priority(-1));
|
||||||
reject(() -> { cb.priority(500);}, IllegalArgumentException.class);
|
TestKit.assertThrows(IllegalArgumentException.class, () -> cb.priority(500));
|
||||||
accept(() -> { cb.priority(1);});
|
TestKit.assertNotThrows(() -> cb.priority(1));
|
||||||
accept(() -> { cb.priority(255);});
|
TestKit.assertNotThrows(() -> cb.priority(255));
|
||||||
|
TestKit.assertNotThrows(() -> {
|
||||||
accept(() -> {clients.add(cb.build()); clients.add(cb.build());});
|
clients.add(cb.build());
|
||||||
|
clients.add(cb.build());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test2() throws Exception {
|
static void test2() throws Exception {
|
||||||
@ -139,7 +122,7 @@ public class APIErrors {
|
|||||||
|
|
||||||
static void test3() throws Exception {
|
static void test3() throws Exception {
|
||||||
System.out.println("Test 3");
|
System.out.println("Test 3");
|
||||||
reject(()-> {
|
TestKit.assertThrows(IllegalStateException.class, ()-> {
|
||||||
try {
|
try {
|
||||||
HttpRequest r1 = request();
|
HttpRequest r1 = request();
|
||||||
HttpResponse resp = r1.response();
|
HttpResponse resp = r1.response();
|
||||||
@ -147,9 +130,9 @@ public class APIErrors {
|
|||||||
} catch (IOException |InterruptedException e) {
|
} catch (IOException |InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}, IllegalStateException.class);
|
});
|
||||||
|
|
||||||
reject(()-> {
|
TestKit.assertThrows(IllegalStateException.class, ()-> {
|
||||||
try {
|
try {
|
||||||
HttpRequest r1 = request();
|
HttpRequest r1 = request();
|
||||||
HttpResponse resp = r1.response();
|
HttpResponse resp = r1.response();
|
||||||
@ -157,8 +140,8 @@ public class APIErrors {
|
|||||||
} catch (IOException |InterruptedException | ExecutionException e) {
|
} catch (IOException |InterruptedException | ExecutionException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}, IllegalStateException.class);
|
});
|
||||||
reject(()-> {
|
TestKit.assertThrows(IllegalStateException.class, ()-> {
|
||||||
try {
|
try {
|
||||||
HttpRequest r1 = request();
|
HttpRequest r1 = request();
|
||||||
HttpResponse resp1 = r1.responseAsync().get();
|
HttpResponse resp1 = r1.responseAsync().get();
|
||||||
@ -166,7 +149,7 @@ public class APIErrors {
|
|||||||
} catch (IOException |InterruptedException | ExecutionException e) {
|
} catch (IOException |InterruptedException | ExecutionException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}, IllegalStateException.class);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Auth extends java.net.Authenticator {
|
static class Auth extends java.net.Authenticator {
|
||||||
|
87
jdk/test/java/net/httpclient/EchoHandler.java
Normal file
87
jdk/test/java/net/httpclient/EchoHandler.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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 com.sun.net.httpserver.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.net.http.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
|
import static java.net.http.HttpRequest.*;
|
||||||
|
import static java.net.http.HttpResponse.*;
|
||||||
|
import java.util.logging.ConsoleHandler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class EchoHandler implements HttpHandler {
|
||||||
|
public EchoHandler() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpExchange t)
|
||||||
|
throws IOException {
|
||||||
|
try {
|
||||||
|
System.err.println("EchoHandler received request to " + t.getRequestURI());
|
||||||
|
InputStream is = t.getRequestBody();
|
||||||
|
Headers map = t.getRequestHeaders();
|
||||||
|
Headers map1 = t.getResponseHeaders();
|
||||||
|
map1.add("X-Hello", "world");
|
||||||
|
map1.add("X-Bye", "universe");
|
||||||
|
String fixedrequest = map.getFirst("XFixed");
|
||||||
|
File outfile = File.createTempFile("foo", "bar");
|
||||||
|
FileOutputStream fos = new FileOutputStream(outfile);
|
||||||
|
int count = (int) is.transferTo(fos);
|
||||||
|
is.close();
|
||||||
|
fos.close();
|
||||||
|
InputStream is1 = new FileInputStream(outfile);
|
||||||
|
OutputStream os = null;
|
||||||
|
// return the number of bytes received (no echo)
|
||||||
|
String summary = map.getFirst("XSummary");
|
||||||
|
if (fixedrequest != null && summary == null) {
|
||||||
|
t.sendResponseHeaders(200, count);
|
||||||
|
os = t.getResponseBody();
|
||||||
|
is1.transferTo(os);
|
||||||
|
} else {
|
||||||
|
t.sendResponseHeaders(200, 0);
|
||||||
|
os = t.getResponseBody();
|
||||||
|
is1.transferTo(os);
|
||||||
|
|
||||||
|
if (summary != null) {
|
||||||
|
String s = Integer.toString(count);
|
||||||
|
os.write(s.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outfile.delete();
|
||||||
|
os.close();
|
||||||
|
is1.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,10 +22,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @library /lib/testlibrary/
|
* library /lib/testlibrary/ /
|
||||||
* @build jdk.testlibrary.SimpleSSLContext ProxyServer
|
* build jdk.testlibrary.SimpleSSLContext ProxyServer EchoHandler
|
||||||
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
* compile ../../../com/sun/net/httpserver/LogFilter.java
|
||||||
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
* compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
||||||
*/
|
*/
|
||||||
import com.sun.net.httpserver.Headers;
|
import com.sun.net.httpserver.Headers;
|
||||||
import com.sun.net.httpserver.HttpContext;
|
import com.sun.net.httpserver.HttpContext;
|
||||||
|
@ -24,18 +24,17 @@
|
|||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @bug 8087112
|
* @bug 8087112
|
||||||
* @library /lib/testlibrary/
|
* @library /lib/testlibrary/ /
|
||||||
* @build jdk.testlibrary.SimpleSSLContext
|
* @build jdk.testlibrary.SimpleSSLContext EchoHandler
|
||||||
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
||||||
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
||||||
* @run main/othervm ManyRequests
|
* @run main/othervm/timeout=40 -Djava.net.http.HttpClient.log=ssl ManyRequests
|
||||||
* @summary Send a large number of requests asynchronously
|
* @summary Send a large number of requests asynchronously
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//package javaapplication16;
|
//package javaapplication16;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpsConfigurator;
|
import com.sun.net.httpserver.*;
|
||||||
import com.sun.net.httpserver.HttpsServer;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
@ -47,18 +46,25 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.logging.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.*;
|
||||||
import jdk.testlibrary.SimpleSSLContext;
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
|
|
||||||
public class ManyRequests {
|
public class ManyRequests {
|
||||||
|
|
||||||
|
volatile static int counter = 0;
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
Logger logger = Logger.getLogger("com.sun.net.httpserver");
|
||||||
|
logger.setLevel(Level.ALL);
|
||||||
|
logger.info("TEST");
|
||||||
|
|
||||||
SSLContext ctx = new SimpleSSLContext().get();
|
SSLContext ctx = new SimpleSSLContext().get();
|
||||||
|
|
||||||
InetSocketAddress addr = new InetSocketAddress(0);
|
InetSocketAddress addr = new InetSocketAddress(0);
|
||||||
HttpsServer server = HttpsServer.create(addr, 0);
|
HttpsServer server = HttpsServer.create(addr, 0);
|
||||||
server.setHttpsConfigurator(new HttpsConfigurator(ctx));
|
server.setHttpsConfigurator(new Configurator(ctx));
|
||||||
|
|
||||||
HttpClient client = HttpClient.create()
|
HttpClient client = HttpClient.create()
|
||||||
.sslContext(ctx)
|
.sslContext(ctx)
|
||||||
@ -72,7 +78,8 @@ public class ManyRequests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final int REQUESTS = 1000;
|
//static final int REQUESTS = 1000;
|
||||||
|
static final int REQUESTS = 20;
|
||||||
|
|
||||||
static void test(HttpsServer server, HttpClient client) throws Exception {
|
static void test(HttpsServer server, HttpClient client) throws Exception {
|
||||||
int port = server.getAddress().getPort();
|
int port = server.getAddress().getPort();
|
||||||
@ -102,6 +109,9 @@ public class ManyRequests {
|
|||||||
resp.bodyAsync(HttpResponse.ignoreBody());
|
resp.bodyAsync(HttpResponse.ignoreBody());
|
||||||
String s = "Expected 200, got: " + resp.statusCode();
|
String s = "Expected 200, got: " + resp.statusCode();
|
||||||
return completedWithIOException(s);
|
return completedWithIOException(s);
|
||||||
|
} else {
|
||||||
|
counter++;
|
||||||
|
System.out.println("Result from " + counter);
|
||||||
}
|
}
|
||||||
return resp.bodyAsync(HttpResponse.asByteArray())
|
return resp.bodyAsync(HttpResponse.asByteArray())
|
||||||
.thenApply((b) -> new Pair<>(resp, b));
|
.thenApply((b) -> new Pair<>(resp, b));
|
||||||
@ -114,14 +124,18 @@ public class ManyRequests {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for them all to complete and throw exception in case of error
|
// wait for them all to complete and throw exception in case of error
|
||||||
|
//try {
|
||||||
CompletableFuture.allOf(results).join();
|
CompletableFuture.allOf(results).join();
|
||||||
|
//} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
//throw e;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T> CompletableFuture<T> completedWithIOException(String message) {
|
static <T> CompletableFuture<T> completedWithIOException(String message) {
|
||||||
CompletableFuture<T> cf = new CompletableFuture<>();
|
return CompletableFuture.failedFuture(new IOException(message));
|
||||||
cf.completeExceptionally(new IOException(message));
|
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class Pair<T,U> {
|
static final class Pair<T,U> {
|
||||||
@ -192,3 +206,14 @@ public class ManyRequests {
|
|||||||
throw new RuntimeException(sb.toString());
|
throw new RuntimeException(sb.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Configurator extends HttpsConfigurator {
|
||||||
|
public Configurator(SSLContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configure (HttpsParameters params) {
|
||||||
|
params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -23,10 +23,11 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @test @bug 8087112
|
* @test @bug 8087112
|
||||||
* @library /lib/testlibrary/
|
* @library /lib/testlibrary/ /
|
||||||
* @build jdk.testlibrary.SimpleSSLContext ProxyServer
|
|
||||||
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
||||||
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
||||||
|
* @build LightWeightHttpServer
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext ProxyServer
|
||||||
* @run main/othervm RequestBodyTest
|
* @run main/othervm RequestBodyTest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -24,15 +24,13 @@
|
|||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @bug 8087112
|
* @bug 8087112
|
||||||
* @library /lib/testlibrary/
|
* @library /lib/testlibrary/ /
|
||||||
* @build jdk.testlibrary.SimpleSSLContext ProxyServer
|
* @build jdk.testlibrary.SimpleSSLContext ProxyServer EchoHandler
|
||||||
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
* @compile ../../../com/sun/net/httpserver/LogFilter.java
|
||||||
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
* @compile ../../../com/sun/net/httpserver/FileServerHandler.java
|
||||||
* @run main/othervm SmokeTest
|
* @run main/othervm SmokeTest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//package javaapplication16;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.*;
|
import com.sun.net.httpserver.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.net.http.*;
|
import java.net.http.*;
|
||||||
@ -69,6 +67,7 @@ import java.util.logging.Logger;
|
|||||||
*/
|
*/
|
||||||
public class SmokeTest {
|
public class SmokeTest {
|
||||||
static SSLContext ctx;
|
static SSLContext ctx;
|
||||||
|
static SSLParameters sslparams;
|
||||||
static HttpServer s1 ;
|
static HttpServer s1 ;
|
||||||
static HttpsServer s2;
|
static HttpsServer s2;
|
||||||
static ExecutorService executor;
|
static ExecutorService executor;
|
||||||
@ -107,6 +106,7 @@ public class SmokeTest {
|
|||||||
|
|
||||||
client = HttpClient.create()
|
client = HttpClient.create()
|
||||||
.sslContext(ctx)
|
.sslContext(ctx)
|
||||||
|
.sslParameters(sslparams)
|
||||||
.followRedirects(HttpClient.Redirect.ALWAYS)
|
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||||
.executorService(Executors.newCachedThreadPool())
|
.executorService(Executors.newCachedThreadPool())
|
||||||
.build();
|
.build();
|
||||||
@ -285,6 +285,7 @@ public class SmokeTest {
|
|||||||
HttpClient cl = HttpClient.create()
|
HttpClient cl = HttpClient.create()
|
||||||
.proxy(ProxySelector.of(proxyAddr))
|
.proxy(ProxySelector.of(proxyAddr))
|
||||||
.sslContext(ctx)
|
.sslContext(ctx)
|
||||||
|
.sslParameters(sslparams)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
CompletableFuture<String> fut = cl.request(uri)
|
CompletableFuture<String> fut = cl.request(uri)
|
||||||
@ -672,7 +673,8 @@ public class SmokeTest {
|
|||||||
s1.setExecutor(executor);
|
s1.setExecutor(executor);
|
||||||
s2.setExecutor(executor);
|
s2.setExecutor(executor);
|
||||||
ctx = new SimpleSSLContext().get();
|
ctx = new SimpleSSLContext().get();
|
||||||
s2.setHttpsConfigurator(new HttpsConfigurator(ctx));
|
sslparams = ctx.getSupportedSSLParameters();
|
||||||
|
s2.setHttpsConfigurator(new Configurator(ctx));
|
||||||
s1.start();
|
s1.start();
|
||||||
s2.start();
|
s2.start();
|
||||||
|
|
||||||
@ -689,6 +691,16 @@ public class SmokeTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Configurator extends HttpsConfigurator {
|
||||||
|
public Configurator(SSLContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configure (HttpsParameters params) {
|
||||||
|
params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class UploadServer extends Thread {
|
class UploadServer extends Thread {
|
||||||
int statusCode;
|
int statusCode;
|
||||||
ServerSocket ss;
|
ServerSocket ss;
|
||||||
|
101
jdk/test/java/net/httpclient/TestKit.java
Normal file
101
jdk/test/java/net/httpclient/TestKit.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 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.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
//
|
||||||
|
// A set of testing utility functions
|
||||||
|
//
|
||||||
|
public final class TestKit {
|
||||||
|
|
||||||
|
private TestKit() { }
|
||||||
|
|
||||||
|
public static void assertNotThrows(ThrowingProcedure code) {
|
||||||
|
requireNonNull(code, "code");
|
||||||
|
assertNotThrows(() -> {
|
||||||
|
code.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> V assertNotThrows(ThrowingFunction<V> code) {
|
||||||
|
requireNonNull(code, "code");
|
||||||
|
try {
|
||||||
|
return code.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new RuntimeException("Expected to run normally, but threw "
|
||||||
|
+ t.getClass().getCanonicalName(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Throwable> T assertThrows(Class<? extends T> clazz,
|
||||||
|
ThrowingProcedure code) {
|
||||||
|
requireNonNull(clazz, "clazz");
|
||||||
|
requireNonNull(code, "code");
|
||||||
|
try {
|
||||||
|
code.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (clazz.isInstance(t)) {
|
||||||
|
return clazz.cast(t);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Expected to catch an exception of type "
|
||||||
|
+ clazz.getCanonicalName() + ", but caught "
|
||||||
|
+ t.getClass().getCanonicalName(), t);
|
||||||
|
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Expected to catch an exception of type "
|
||||||
|
+ clazz.getCanonicalName() + ", but caught nothing");
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ThrowingProcedure {
|
||||||
|
void run() throws Throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ThrowingFunction<V> {
|
||||||
|
V run() throws Throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rationale behind asking for a regex is to not pollute variable names
|
||||||
|
// space in the scope of assertion: if it's something as simple as checking
|
||||||
|
// a message, we can do it inside
|
||||||
|
public static <T extends Throwable> T assertThrows(Class<? extends T> clazz,
|
||||||
|
String messageRegex,
|
||||||
|
ThrowingProcedure code) {
|
||||||
|
requireNonNull(messageRegex, "messagePattern");
|
||||||
|
T t = assertThrows(clazz, code);
|
||||||
|
String m = t.getMessage();
|
||||||
|
if (m == null) {
|
||||||
|
throw new RuntimeException(String.format(
|
||||||
|
"Expected exception message to match the regex '%s', " +
|
||||||
|
"but the message was null", messageRegex), t);
|
||||||
|
}
|
||||||
|
if (!Pattern.matches(messageRegex, m)) {
|
||||||
|
throw new RuntimeException(String.format(
|
||||||
|
"Expected exception message to match the regex '%s', " +
|
||||||
|
"actual message: %s", messageRegex, m), t);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
130
jdk/test/java/net/httpclient/TestKitTest.java
Normal file
130
jdk/test/java/net/httpclient/TestKitTest.java
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 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.IOException;
|
||||||
|
import java.util.IllegalFormatException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.assertNull;
|
||||||
|
import static org.testng.Assert.assertTrue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @compile TestKit.java
|
||||||
|
* @run testng TestKitTest
|
||||||
|
*/
|
||||||
|
public final class TestKitTest {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
testAssertNotThrows();
|
||||||
|
testAssertThrows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testAssertNotThrows() {
|
||||||
|
Integer integer = TestKit.assertNotThrows(
|
||||||
|
() -> TestKit.assertNotThrows(() -> 1)
|
||||||
|
);
|
||||||
|
assertEquals(integer, Integer.valueOf(1));
|
||||||
|
|
||||||
|
RuntimeException re = TestKit.assertThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> TestKit.assertNotThrows(() -> { throw new IOException(); })
|
||||||
|
);
|
||||||
|
assertEquals(re.getMessage(),
|
||||||
|
"Expected to run normally, but threw "
|
||||||
|
+ "java.io.IOException");
|
||||||
|
|
||||||
|
TestKit.assertNotThrows(
|
||||||
|
() -> TestKit.assertNotThrows(() -> { })
|
||||||
|
);
|
||||||
|
|
||||||
|
re = TestKit.assertThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> TestKit.assertNotThrows((TestKit.ThrowingProcedure) () -> { throw new IOException(); })
|
||||||
|
);
|
||||||
|
assertEquals(re.getMessage(),
|
||||||
|
"Expected to run normally, but threw "
|
||||||
|
+ "java.io.IOException");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testAssertThrows() {
|
||||||
|
NullPointerException npe = TestKit.assertThrows(
|
||||||
|
NullPointerException.class,
|
||||||
|
() -> TestKit.assertThrows(null, null)
|
||||||
|
);
|
||||||
|
assertNotNull(npe);
|
||||||
|
assertTrue(Set.of("clazz", "code").contains(npe.getMessage()), npe.getMessage());
|
||||||
|
|
||||||
|
npe = TestKit.assertThrows(
|
||||||
|
NullPointerException.class,
|
||||||
|
() -> TestKit.assertThrows(IOException.class, null)
|
||||||
|
);
|
||||||
|
assertNotNull(npe);
|
||||||
|
assertEquals(npe.getMessage(), "code");
|
||||||
|
|
||||||
|
npe = TestKit.assertThrows(
|
||||||
|
NullPointerException.class,
|
||||||
|
() -> TestKit.assertThrows(null, () -> { })
|
||||||
|
);
|
||||||
|
assertEquals(npe.getMessage(), "clazz");
|
||||||
|
|
||||||
|
npe = TestKit.assertThrows(
|
||||||
|
NullPointerException.class,
|
||||||
|
() -> { throw new NullPointerException(); }
|
||||||
|
);
|
||||||
|
assertNotNull(npe);
|
||||||
|
assertNull(npe.getMessage());
|
||||||
|
assertEquals(npe.getClass(), NullPointerException.class);
|
||||||
|
|
||||||
|
RuntimeException re = TestKit.assertThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> TestKit.assertThrows(NullPointerException.class, () -> { })
|
||||||
|
);
|
||||||
|
assertEquals(re.getClass(), RuntimeException.class);
|
||||||
|
assertEquals(re.getMessage(),
|
||||||
|
"Expected to catch an exception of type "
|
||||||
|
+ "java.lang.NullPointerException, but caught nothing");
|
||||||
|
|
||||||
|
re = TestKit.assertThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> { throw new NullPointerException(); }
|
||||||
|
);
|
||||||
|
assertNotNull(re);
|
||||||
|
assertNull(re.getMessage());
|
||||||
|
assertEquals(re.getClass(), NullPointerException.class);
|
||||||
|
|
||||||
|
re = TestKit.assertThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> TestKit.assertThrows(
|
||||||
|
IllegalFormatException.class,
|
||||||
|
() -> { throw new IndexOutOfBoundsException(); }
|
||||||
|
));
|
||||||
|
assertNotNull(re);
|
||||||
|
assertEquals(re.getClass(), RuntimeException.class);
|
||||||
|
assertEquals(re.getMessage(),
|
||||||
|
"Expected to catch an exception of type java.util.IllegalFormatException"
|
||||||
|
+ ", but caught java.lang.IndexOutOfBoundsException");
|
||||||
|
}
|
||||||
|
}
|
222
jdk/test/java/net/httpclient/http2/BasicTest.java
Normal file
222
jdk/test/java/net/httpclient/http2/BasicTest.java
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8087112
|
||||||
|
* @library /lib/testlibrary
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext
|
||||||
|
* @modules java.httpclient
|
||||||
|
* @compile/module=java.httpclient java/net/http/BodyOutputStream.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/BodyInputStream.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/EchoHandler.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2Handler.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2TestExchange.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2TestServerConnection.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2TestServer.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/OutgoingPushPromise.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/TestUtil.java
|
||||||
|
* @run testng/othervm -Djava.net.http.HttpClient.log=ssl,requests,responses,errors BasicTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.net.http.*;
|
||||||
|
import static java.net.http.HttpClient.Version.HTTP_2;
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
|
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import org.testng.annotations.Parameters;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public class BasicTest {
|
||||||
|
static int httpPort, httpsPort;
|
||||||
|
static Http2TestServer httpServer, httpsServer;
|
||||||
|
static HttpClient client = null;
|
||||||
|
static ExecutorService exec;
|
||||||
|
static SSLContext sslContext;
|
||||||
|
|
||||||
|
static String httpURIString, httpsURIString;
|
||||||
|
|
||||||
|
static void initialize() throws Exception {
|
||||||
|
try {
|
||||||
|
SimpleSSLContext sslct = new SimpleSSLContext();
|
||||||
|
sslContext = sslct.get();
|
||||||
|
client = getClient();
|
||||||
|
exec = client.executorService();
|
||||||
|
httpServer = new Http2TestServer(false, 0, new EchoHandler(),
|
||||||
|
exec, sslContext);
|
||||||
|
httpPort = httpServer.getAddress().getPort();
|
||||||
|
|
||||||
|
httpsServer = new Http2TestServer(true, 0, new EchoHandler(),
|
||||||
|
exec, sslContext);
|
||||||
|
|
||||||
|
httpsPort = httpsServer.getAddress().getPort();
|
||||||
|
httpURIString = "http://127.0.0.1:" + Integer.toString(httpPort) +
|
||||||
|
"/foo/";
|
||||||
|
httpsURIString = "https://127.0.0.1:" + Integer.toString(httpsPort) +
|
||||||
|
"/bar/";
|
||||||
|
|
||||||
|
httpServer.start();
|
||||||
|
httpsServer.start();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.err.println("Throwing now");
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeOut=30000)
|
||||||
|
public static void test() throws Exception {
|
||||||
|
try {
|
||||||
|
initialize();
|
||||||
|
simpleTest(false);
|
||||||
|
simpleTest(true);
|
||||||
|
streamTest(false);
|
||||||
|
streamTest(true);
|
||||||
|
Thread.sleep(1000 * 4);
|
||||||
|
} finally {
|
||||||
|
httpServer.stop();
|
||||||
|
httpsServer.stop();
|
||||||
|
exec.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpClient getClient() {
|
||||||
|
if (client == null) {
|
||||||
|
client = HttpClient.create()
|
||||||
|
.sslContext(sslContext)
|
||||||
|
.version(HTTP_2)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
static URI getURI(boolean secure) {
|
||||||
|
if (secure)
|
||||||
|
return URI.create(httpsURIString);
|
||||||
|
else
|
||||||
|
return URI.create(httpURIString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkStatus(int expected, int found) throws Exception {
|
||||||
|
if (expected != found) {
|
||||||
|
System.err.printf ("Test failed: wrong status code %d/%d\n",
|
||||||
|
expected, found);
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkStrings(String expected, String found) throws Exception {
|
||||||
|
if (!expected.equals(found)) {
|
||||||
|
System.err.printf ("Test failed: wrong string %s/%s\n",
|
||||||
|
expected, found);
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Void compareFiles(Path path1, Path path2) {
|
||||||
|
return java.net.http.TestUtil.compareFiles(path1, path2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Path tempFile() {
|
||||||
|
return java.net.http.TestUtil.tempFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String SIMPLE_STRING = "Hello world Goodbye world";
|
||||||
|
|
||||||
|
static final int LOOPS = 13;
|
||||||
|
static final int FILESIZE = 64 * 1024;
|
||||||
|
|
||||||
|
static void streamTest(boolean secure) throws Exception {
|
||||||
|
URI uri = getURI(secure);
|
||||||
|
System.err.printf("streamTest %b to %s\n" , secure, uri);
|
||||||
|
|
||||||
|
HttpClient client = getClient();
|
||||||
|
Path src = java.net.http.TestUtil.getAFile(FILESIZE * 4);
|
||||||
|
HttpRequest req = client.request(uri)
|
||||||
|
.body(HttpRequest.fromFile(src))
|
||||||
|
.POST();
|
||||||
|
|
||||||
|
CompletableFuture<InputStream> response = req.responseAsync()
|
||||||
|
.thenCompose(resp -> {
|
||||||
|
if (resp.statusCode() != 200)
|
||||||
|
throw new RuntimeException();
|
||||||
|
return resp.bodyAsync(HttpResponse.asInputStream());
|
||||||
|
});
|
||||||
|
InputStream is = response.join();
|
||||||
|
File dest = File.createTempFile("foo","bar");
|
||||||
|
dest.deleteOnExit();
|
||||||
|
FileOutputStream os = new FileOutputStream(dest);
|
||||||
|
is.transferTo(os);
|
||||||
|
is.close();
|
||||||
|
os.close();
|
||||||
|
int count = 0;
|
||||||
|
compareFiles(src, dest.toPath());
|
||||||
|
System.err.println("DONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void simpleTest(boolean secure) throws Exception {
|
||||||
|
URI uri = getURI(secure);
|
||||||
|
System.err.println("Request to " + uri);
|
||||||
|
|
||||||
|
// Do a simple warmup request
|
||||||
|
|
||||||
|
HttpClient client = getClient();
|
||||||
|
HttpRequest req = client.request(uri)
|
||||||
|
.body(HttpRequest.fromString(SIMPLE_STRING))
|
||||||
|
.POST();
|
||||||
|
HttpResponse response = req.response();
|
||||||
|
HttpHeaders h = response.headers();
|
||||||
|
|
||||||
|
checkStatus(200, response.statusCode());
|
||||||
|
|
||||||
|
String responseBody = response.body(HttpResponse.asString());
|
||||||
|
checkStrings(SIMPLE_STRING, responseBody);
|
||||||
|
|
||||||
|
checkStrings(h.firstValue("x-hello").get(), "world");
|
||||||
|
checkStrings(h.firstValue("x-bye").get(), "universe");
|
||||||
|
|
||||||
|
// Do loops asynchronously
|
||||||
|
|
||||||
|
CompletableFuture[] responses = new CompletableFuture[LOOPS];
|
||||||
|
final Path source = java.net.http.TestUtil.getAFile(FILESIZE);
|
||||||
|
for (int i = 0; i < LOOPS; i++) {
|
||||||
|
responses[i] = client.request(uri)
|
||||||
|
.body(HttpRequest.fromFile(source))
|
||||||
|
.version(HTTP_2)
|
||||||
|
.POST()
|
||||||
|
.responseAsync()
|
||||||
|
.thenCompose(r -> r.bodyAsync(HttpResponse.asFile(tempFile())))
|
||||||
|
.thenApply(path -> compareFiles(path, source));
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
CompletableFuture.allOf(responses).join();
|
||||||
|
System.err.println("DONE");
|
||||||
|
}
|
||||||
|
}
|
107
jdk/test/java/net/httpclient/http2/ServerPush.java
Normal file
107
jdk/test/java/net/httpclient/http2/ServerPush.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8087112
|
||||||
|
* @library /lib/testlibrary
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext
|
||||||
|
* @modules java.httpclient
|
||||||
|
* @compile/module=java.httpclient java/net/http/BodyOutputStream.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/BodyInputStream.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/PushHandler.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2Handler.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2TestExchange.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2TestServerConnection.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/Http2TestServer.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/OutgoingPushPromise.java
|
||||||
|
* @compile/module=java.httpclient java/net/http/TestUtil.java
|
||||||
|
* @run testng/othervm -Djava.net.http.HttpClient.log=requests,responses ServerPush
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.file.attribute.*;
|
||||||
|
import java.net.http.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
public class ServerPush {
|
||||||
|
|
||||||
|
static ExecutorService e = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
static final int LOOPS = 13;
|
||||||
|
static final int FILE_SIZE = 32 * 1024;
|
||||||
|
|
||||||
|
static Path tempFile;
|
||||||
|
|
||||||
|
@Test(timeOut=30000)
|
||||||
|
public static void test() throws Exception {
|
||||||
|
Http2TestServer server = null;
|
||||||
|
Path dir = null;
|
||||||
|
try {
|
||||||
|
server = new Http2TestServer(false, 0,
|
||||||
|
new PushHandler(FILE_SIZE, LOOPS));
|
||||||
|
tempFile = TestUtil.getAFile(FILE_SIZE);
|
||||||
|
|
||||||
|
System.err.println("Server listening on port " + server.getAddress().getPort());
|
||||||
|
server.start();
|
||||||
|
int port = server.getAddress().getPort();
|
||||||
|
dir = Files.createTempDirectory("serverPush");
|
||||||
|
|
||||||
|
URI uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/foo");
|
||||||
|
HttpRequest request = HttpRequest.create(uri)
|
||||||
|
.version(HttpClient.Version.HTTP_2)
|
||||||
|
.GET();
|
||||||
|
|
||||||
|
CompletableFuture<Map<URI,Path>> cf =
|
||||||
|
request.multiResponseAsync(HttpResponse.multiFile(dir));
|
||||||
|
Map<URI,Path> results = cf.get();
|
||||||
|
|
||||||
|
//HttpResponse resp = request.response();
|
||||||
|
System.err.println(results.size());
|
||||||
|
Set<URI> uris = results.keySet();
|
||||||
|
for (URI u : uris) {
|
||||||
|
Path result = results.get(u);
|
||||||
|
System.err.printf("%s -> %s\n", u.toString(), result.toString());
|
||||||
|
TestUtil.compareFiles(result, tempFile);
|
||||||
|
}
|
||||||
|
System.out.println("TEST OK");
|
||||||
|
} finally {
|
||||||
|
e.shutdownNow();
|
||||||
|
server.stop();
|
||||||
|
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
|
||||||
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
|
||||||
|
dir.toFile().delete();
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
|
||||||
|
path.toFile().delete();
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
jdk/test/java/net/httpclient/http2/TEST.properties
Normal file
1
jdk/test/java/net/httpclient/http2/TEST.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
bootclasspath.dirs = /java/net/httpclient
|
@ -0,0 +1,107 @@
|
|||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InputStream reads frames off stream q and supplies read demand from any
|
||||||
|
* DataFrames it finds. Window updates are sent back on the connections send
|
||||||
|
* q.
|
||||||
|
*/
|
||||||
|
class BodyInputStream extends InputStream {
|
||||||
|
|
||||||
|
final Queue<Http2Frame> q;
|
||||||
|
final int streamid;
|
||||||
|
boolean closed;
|
||||||
|
boolean eof;
|
||||||
|
final Http2TestServerConnection conn;
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
BodyInputStream(Queue q, int streamid, Http2TestServerConnection conn) {
|
||||||
|
this.q = q;
|
||||||
|
this.streamid = streamid;
|
||||||
|
this.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFrame df;
|
||||||
|
ByteBuffer[] buffers;
|
||||||
|
ByteBuffer buffer;
|
||||||
|
int nextIndex = -1;
|
||||||
|
|
||||||
|
private DataFrame getData() throws IOException {
|
||||||
|
if (eof) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Http2Frame frame;
|
||||||
|
do {
|
||||||
|
frame = q.take();
|
||||||
|
if (frame.type() == ResetFrame.TYPE) {
|
||||||
|
conn.handleStreamReset((ResetFrame) frame); // throws IOException
|
||||||
|
}
|
||||||
|
// ignoring others for now Wupdates handled elsewhere
|
||||||
|
if (frame.type() != DataFrame.TYPE) {
|
||||||
|
System.out.println("Ignoring " + frame.toString() + " CHECK THIS");
|
||||||
|
}
|
||||||
|
} while (frame.type() != DataFrame.TYPE);
|
||||||
|
df = (DataFrame) frame;
|
||||||
|
int len = df.getDataLength();
|
||||||
|
eof = frame.getFlag(DataFrame.END_STREAM);
|
||||||
|
// acknowledge
|
||||||
|
conn.sendWindowUpdates(len, streamid);
|
||||||
|
return (DataFrame) frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// null return means EOF
|
||||||
|
private ByteBuffer getBuffer() throws IOException {
|
||||||
|
if (buffer == null || !buffer.hasRemaining()) {
|
||||||
|
if (nextIndex == -1 || nextIndex == buffers.length) {
|
||||||
|
DataFrame df = getData();
|
||||||
|
if (df == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int len = df.getDataLength();
|
||||||
|
if ((len == 0) && eof) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
buffers = df.getData();
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
buffer = buffers[nextIndex++];
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buf, int offset, int length) throws IOException {
|
||||||
|
if (closed) {
|
||||||
|
throw new IOException("closed");
|
||||||
|
}
|
||||||
|
ByteBuffer b = getBuffer();
|
||||||
|
if (b == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int remaining = b.remaining();
|
||||||
|
if (remaining < length) {
|
||||||
|
length = remaining;
|
||||||
|
}
|
||||||
|
b.get(buf, offset, length);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] one = new byte[1];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
int c = read(one, 0, 1);
|
||||||
|
if (c == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return one[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// TODO reset this stream
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OutputStream. Incoming window updates handled by the main connection
|
||||||
|
* reader thread.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
class BodyOutputStream extends OutputStream {
|
||||||
|
final static byte[] EMPTY_BARRAY = new byte[0];
|
||||||
|
|
||||||
|
final int streamid;
|
||||||
|
int window;
|
||||||
|
boolean closed;
|
||||||
|
boolean goodToGo = false; // not allowed to send until headers sent
|
||||||
|
final Http2TestServerConnection conn;
|
||||||
|
final Queue outputQ;
|
||||||
|
|
||||||
|
BodyOutputStream(int streamid, int initialWindow, Http2TestServerConnection conn) {
|
||||||
|
this.window = initialWindow;
|
||||||
|
this.streamid = streamid;
|
||||||
|
this.conn = conn;
|
||||||
|
this.outputQ = conn.outputQ;
|
||||||
|
conn.registerStreamWindowUpdater(streamid, this::updateWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// called from connection reader thread as all incoming window
|
||||||
|
// updates are handled there.
|
||||||
|
synchronized void updateWindow(int update) {
|
||||||
|
window += update;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForWindow(int demand) throws InterruptedException {
|
||||||
|
// first wait for the connection window
|
||||||
|
conn.obtainConnectionWindow(demand);
|
||||||
|
// now wait for the stream window
|
||||||
|
synchronized (this) {
|
||||||
|
while (demand > 0) {
|
||||||
|
int n = Math.min(demand, window);
|
||||||
|
demand -= n;
|
||||||
|
window -= n;
|
||||||
|
if (demand > 0) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void goodToGo() {
|
||||||
|
goodToGo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buf, int offset, int len) throws IOException {
|
||||||
|
if (closed) {
|
||||||
|
throw new IOException("closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!goodToGo) {
|
||||||
|
throw new IllegalStateException("sendResponseHeaders must be called first");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
waitForWindow(len);
|
||||||
|
send(buf, offset, len, 0);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new IOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(byte[] buf, int offset, int len, int flags) throws IOException {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(len);
|
||||||
|
buffer.put(buf, offset, len);
|
||||||
|
buffer.flip();
|
||||||
|
DataFrame df = new DataFrame();
|
||||||
|
assert streamid != 0;
|
||||||
|
df.streamid(streamid);
|
||||||
|
df.setFlags(flags);
|
||||||
|
df.setData(buffer);
|
||||||
|
outputQ.put(df);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] one = new byte[1];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
one[0] = (byte) b;
|
||||||
|
write(one, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closed = true;
|
||||||
|
try {
|
||||||
|
send(EMPTY_BARRAY, 0, 0, DataFrame.END_STREAM);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.err.println("TestServer: OutputStream.close exception: " + ex);
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2005, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
|
||||||
|
public class EchoHandler implements Http2Handler {
|
||||||
|
public EchoHandler() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(Http2TestExchange t)
|
||||||
|
throws IOException {
|
||||||
|
try {
|
||||||
|
System.err.println("EchoHandler received request to " + t.getRequestURI());
|
||||||
|
InputStream is = t.getRequestBody();
|
||||||
|
HttpHeadersImpl map = t.getRequestHeaders();
|
||||||
|
HttpHeadersImpl map1 = t.getResponseHeaders();
|
||||||
|
map1.addHeader("X-Hello", "world");
|
||||||
|
map1.addHeader("X-Bye", "universe");
|
||||||
|
String fixedrequest = map.firstValue("XFixed").orElse(null);
|
||||||
|
File outfile = File.createTempFile("foo", "bar");
|
||||||
|
FileOutputStream fos = new FileOutputStream(outfile);
|
||||||
|
int count = (int) is.transferTo(fos);
|
||||||
|
System.err.printf("EchoHandler read %d bytes\n", count);
|
||||||
|
is.close();
|
||||||
|
fos.close();
|
||||||
|
InputStream is1 = new FileInputStream(outfile);
|
||||||
|
OutputStream os = null;
|
||||||
|
// return the number of bytes received (no echo)
|
||||||
|
String summary = map.firstValue("XSummary").orElse(null);
|
||||||
|
if (fixedrequest != null && summary == null) {
|
||||||
|
t.sendResponseHeaders(200, count);
|
||||||
|
os = t.getResponseBody();
|
||||||
|
is1.transferTo(os);
|
||||||
|
} else {
|
||||||
|
t.sendResponseHeaders(200, 0);
|
||||||
|
os = t.getResponseBody();
|
||||||
|
int count1 = (int)is1.transferTo(os);
|
||||||
|
System.err.printf("EchoHandler wrote %d bytes\n", count1);
|
||||||
|
|
||||||
|
if (summary != null) {
|
||||||
|
String s = Integer.toString(count);
|
||||||
|
os.write(s.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outfile.delete();
|
||||||
|
os.close();
|
||||||
|
is1.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler which is invoked to process HTTP exchanges. Each
|
||||||
|
* HTTP exchange is handled by one of these handlers.
|
||||||
|
*/
|
||||||
|
public interface Http2Handler {
|
||||||
|
/**
|
||||||
|
* Handle the given request and generate an appropriate response.
|
||||||
|
* @param exchange the exchange containing the request from the
|
||||||
|
* client and used to send the response
|
||||||
|
* @throws NullPointerException if exchange is <code>null</code>
|
||||||
|
*/
|
||||||
|
public abstract void handle (Http2TestExchange exchange) throws IOException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
|||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public class Http2TestExchange {
|
||||||
|
|
||||||
|
final HttpHeadersImpl reqheaders;
|
||||||
|
final HttpHeadersImpl rspheaders;
|
||||||
|
final URI uri;
|
||||||
|
final String method;
|
||||||
|
final InputStream is;
|
||||||
|
final BodyOutputStream os;
|
||||||
|
final int streamid;
|
||||||
|
final boolean pushAllowed;
|
||||||
|
final Http2TestServerConnection conn;
|
||||||
|
final Http2TestServer server;
|
||||||
|
|
||||||
|
int responseCode = -1;
|
||||||
|
long responseLength;
|
||||||
|
|
||||||
|
Http2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
|
||||||
|
HttpHeadersImpl rspheaders, URI uri, InputStream is,
|
||||||
|
BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed) {
|
||||||
|
this.reqheaders = reqheaders;
|
||||||
|
this.rspheaders = rspheaders;
|
||||||
|
this.uri = uri;
|
||||||
|
this.method = method;
|
||||||
|
this.is = is;
|
||||||
|
this.streamid = streamid;
|
||||||
|
this.os = os;
|
||||||
|
this.pushAllowed = pushAllowed;
|
||||||
|
this.conn = conn;
|
||||||
|
this.server = conn.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpHeadersImpl getRequestHeaders() {
|
||||||
|
return reqheaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpHeadersImpl getResponseHeaders() {
|
||||||
|
return rspheaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getRequestURI() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
os.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("TestServer: HttpExchange.close exception: " + e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getRequestBody() {
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getResponseBody() {
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
|
||||||
|
this.responseLength = responseLength;
|
||||||
|
if (responseLength > 0 || responseLength < 0) {
|
||||||
|
long clen = responseLength > 0 ? responseLength : 0;
|
||||||
|
rspheaders.setHeader("Content-length", Long.toString(clen));
|
||||||
|
}
|
||||||
|
|
||||||
|
rspheaders.setHeader(":status", Integer.toString(rCode));
|
||||||
|
|
||||||
|
Http2TestServerConnection.ResponseHeaders response
|
||||||
|
= new Http2TestServerConnection.ResponseHeaders(rspheaders);
|
||||||
|
response.streamid(streamid);
|
||||||
|
response.setFlag(HeaderFrame.END_HEADERS);
|
||||||
|
conn.outputQ.put(response);
|
||||||
|
os.goodToGo();
|
||||||
|
System.err.println("Sent response headers " + rCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getRemoteAddress() {
|
||||||
|
return (InetSocketAddress) conn.socket.getRemoteSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponseCode() {
|
||||||
|
return responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getLocalAddress() {
|
||||||
|
return server.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
return "HTTP/2";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean serverPushAllowed() {
|
||||||
|
return pushAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void serverPush(URI uri, HttpHeadersImpl headers, InputStream content) {
|
||||||
|
OutgoingPushPromise pp = new OutgoingPushPromise(
|
||||||
|
streamid, uri, headers, content);
|
||||||
|
headers.setHeader(":method", "GET");
|
||||||
|
headers.setHeader(":scheme", uri.getScheme());
|
||||||
|
headers.setHeader(":authority", uri.getAuthority());
|
||||||
|
headers.setHeader(":path", uri.getPath());
|
||||||
|
try {
|
||||||
|
conn.outputQ.put(pp);
|
||||||
|
// writeLoop will spin up thread to read the InputStream
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.err.println("TestServer: pushPromise exception: " + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import javax.net.ServerSocketFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for incoming TCP connections from a client and establishes
|
||||||
|
* a HTTP2 connection. Two threads are created per connection. One for reading
|
||||||
|
* and one for writing. Incoming requests are dispatched to the supplied
|
||||||
|
* Http2Handler on additional threads. All threads
|
||||||
|
* obtained from the supplied ExecutorService.
|
||||||
|
*/
|
||||||
|
public class Http2TestServer {
|
||||||
|
final ServerSocket server;
|
||||||
|
boolean secure;
|
||||||
|
SettingsFrame serverSettings, clientSettings;
|
||||||
|
final ExecutorService exec;
|
||||||
|
volatile boolean stopping = false;
|
||||||
|
final Http2Handler handler;
|
||||||
|
final SSLContext sslContext;
|
||||||
|
final HashMap<InetSocketAddress,Http2TestServerConnection> connections;
|
||||||
|
|
||||||
|
private static ThreadFactory defaultThreadFac =
|
||||||
|
(Runnable r) -> {
|
||||||
|
Thread t = new Thread(r);
|
||||||
|
t.setName("Test-server-pool");
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static ExecutorService getDefaultExecutor() {
|
||||||
|
return Executors.newCachedThreadPool(defaultThreadFac);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Http2TestServer(boolean secure, int port, Http2Handler handler) throws Exception {
|
||||||
|
this(secure, port, handler, getDefaultExecutor(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getAddress() {
|
||||||
|
return (InetSocketAddress)server.getLocalSocketAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Http2Server listening on the given port. Currently needs
|
||||||
|
* to know in advance whether incoming connections are plain TCP "h2c"
|
||||||
|
* or TLS "h2"/
|
||||||
|
*
|
||||||
|
* @param secure https or http
|
||||||
|
* @param port listen port
|
||||||
|
* @param handler the handler which receives incoming requests
|
||||||
|
* @param exec executor service (cached thread pool is used if null)
|
||||||
|
* @param context the SSLContext used when secure is true
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public Http2TestServer(boolean secure, int port, Http2Handler handler,
|
||||||
|
ExecutorService exec, SSLContext context) throws Exception {
|
||||||
|
if (secure) {
|
||||||
|
server = initSecure(port);
|
||||||
|
} else {
|
||||||
|
server = initPlaintext(port);
|
||||||
|
}
|
||||||
|
this.secure = secure;
|
||||||
|
this.exec = exec == null ? getDefaultExecutor() : exec;
|
||||||
|
this.handler = handler;
|
||||||
|
this.sslContext = context;
|
||||||
|
this.connections = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerSocket initPlaintext(int port) throws Exception {
|
||||||
|
return new ServerSocket(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
// TODO: clean shutdown GoAway
|
||||||
|
stopping = true;
|
||||||
|
for (Http2TestServerConnection connection : connections.values()) {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
server.close();
|
||||||
|
} catch (IOException e) {}
|
||||||
|
exec.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final ServerSocket initSecure(int port) throws Exception {
|
||||||
|
ServerSocketFactory fac;
|
||||||
|
if (sslContext != null) {
|
||||||
|
fac = sslContext.getServerSocketFactory();
|
||||||
|
} else {
|
||||||
|
fac = SSLServerSocketFactory.getDefault();
|
||||||
|
}
|
||||||
|
SSLServerSocket se = (SSLServerSocket) fac.createServerSocket(port);
|
||||||
|
SSLParameters sslp = se.getSSLParameters();
|
||||||
|
sslp.setApplicationProtocols(new String[]{"h2"});
|
||||||
|
se.setSSLParameters(sslp);
|
||||||
|
se.setEnabledCipherSuites(se.getSupportedCipherSuites());
|
||||||
|
se.setEnabledProtocols(se.getSupportedProtocols());
|
||||||
|
// other initialisation here
|
||||||
|
return se;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start thread which waits for incoming connections.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
exec.submit(() -> {
|
||||||
|
try {
|
||||||
|
while (!stopping) {
|
||||||
|
Socket socket = server.accept();
|
||||||
|
InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||||
|
Http2TestServerConnection c = new Http2TestServerConnection(this, socket);
|
||||||
|
connections.put(addr, c);
|
||||||
|
c.run();
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (!stopping) {
|
||||||
|
System.err.println("TestServer: start exception: " + e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,730 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import static java.net.http.SettingsFrame.HEADER_TABLE_SIZE;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import sun.net.httpclient.hpack.Decoder;
|
||||||
|
import sun.net.httpclient.hpack.DecodingCallback;
|
||||||
|
import sun.net.httpclient.hpack.Encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents one HTTP2 connection, either plaintext upgraded from HTTP/1.1
|
||||||
|
* or HTTPS opened using "h2" ALPN.
|
||||||
|
*/
|
||||||
|
public class Http2TestServerConnection {
|
||||||
|
final Http2TestServer server;
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
final Map<Integer, Queue> streams; // input q per stream
|
||||||
|
final Queue<Http2Frame> outputQ;
|
||||||
|
int nextstream;
|
||||||
|
final Socket socket;
|
||||||
|
final InputStream is;
|
||||||
|
final OutputStream os;
|
||||||
|
Encoder hpackOut;
|
||||||
|
Decoder hpackIn;
|
||||||
|
SettingsFrame clientSettings, serverSettings;
|
||||||
|
final ExecutorService exec;
|
||||||
|
final boolean secure;
|
||||||
|
final Http2Handler handler;
|
||||||
|
volatile boolean stopping;
|
||||||
|
int nextPushStreamId = 2;
|
||||||
|
|
||||||
|
final static ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
|
||||||
|
final static byte[] EMPTY_BARRAY = new byte[0];
|
||||||
|
|
||||||
|
final static byte[] clientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes();
|
||||||
|
|
||||||
|
Http2TestServerConnection(Http2TestServer server, Socket socket) throws IOException {
|
||||||
|
System.err.println("New connection from " + socket);
|
||||||
|
this.server = server;
|
||||||
|
this.streams = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
this.outputQ = new Queue<>();
|
||||||
|
this.socket = socket;
|
||||||
|
this.clientSettings = server.clientSettings;
|
||||||
|
this.serverSettings = server.serverSettings;
|
||||||
|
this.exec = server.exec;
|
||||||
|
this.secure = server.secure;
|
||||||
|
this.handler = server.handler;
|
||||||
|
is = new BufferedInputStream(socket.getInputStream());
|
||||||
|
os = new BufferedOutputStream(socket.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
streams.forEach((i, q) -> {
|
||||||
|
q.close();
|
||||||
|
});
|
||||||
|
stopping = true;
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
// TODO: put a reset on each stream
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readPreface() throws IOException {
|
||||||
|
int len = clientPreface.length;
|
||||||
|
byte[] bytes = new byte[len];
|
||||||
|
is.readNBytes(bytes, 0, len);
|
||||||
|
if (Arrays.compare(clientPreface, bytes) != 0) {
|
||||||
|
throw new IOException("Invalid preface: " + new String(bytes, 0, len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String doUpgrade() throws IOException {
|
||||||
|
String upgrade = readHttp1Request();
|
||||||
|
String h2c = getHeader(upgrade, "Upgrade");
|
||||||
|
if (h2c == null || !h2c.equals("h2c")) {
|
||||||
|
throw new IOException("Bad upgrade 1 " + h2c);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendHttp1Response(101, "Switching Protocols", "Connection", "Upgrade",
|
||||||
|
"Upgrade", "h2c");
|
||||||
|
|
||||||
|
sendSettingsFrame();
|
||||||
|
readPreface();
|
||||||
|
|
||||||
|
String clientSettingsString = getHeader(upgrade, "HTTP2-Settings");
|
||||||
|
clientSettings = getSettingsFromString(clientSettingsString);
|
||||||
|
|
||||||
|
return upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client settings payload provided in base64 HTTP1 header. Decode it
|
||||||
|
* and add a header so we can interpret it.
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private SettingsFrame getSettingsFromString(String s) throws IOException {
|
||||||
|
Base64.Decoder decoder = Base64.getUrlDecoder();
|
||||||
|
byte[] payload = decoder.decode(s);
|
||||||
|
ByteBuffer bb1 = ByteBuffer.wrap(payload);
|
||||||
|
// simulate header of Settings Frame
|
||||||
|
ByteBuffer bb0 = ByteBuffer.wrap(
|
||||||
|
new byte[] {0, 0, (byte)payload.length, 4, 0, 0, 0, 0, 0});
|
||||||
|
ByteBufferConsumer bbc = new ByteBufferConsumer(
|
||||||
|
new LinkedList<ByteBuffer>(List.of(bb0, bb1)),
|
||||||
|
this::getBuffer);
|
||||||
|
Http2Frame frame = Http2Frame.readIncoming(bbc);
|
||||||
|
if (!(frame instanceof SettingsFrame))
|
||||||
|
throw new IOException("Expected SettingsFrame");
|
||||||
|
return (SettingsFrame)frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() throws Exception {
|
||||||
|
String upgrade = null;
|
||||||
|
if (!secure) {
|
||||||
|
upgrade = doUpgrade();
|
||||||
|
} else {
|
||||||
|
readPreface();
|
||||||
|
sendSettingsFrame(true);
|
||||||
|
clientSettings = (SettingsFrame) readFrame();
|
||||||
|
nextstream = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));
|
||||||
|
hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
|
||||||
|
|
||||||
|
exec.submit(() -> {
|
||||||
|
readLoop();
|
||||||
|
});
|
||||||
|
exec.submit(() -> {
|
||||||
|
writeLoop();
|
||||||
|
});
|
||||||
|
if (!secure) {
|
||||||
|
createPrimordialStream(upgrade);
|
||||||
|
nextstream = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BufferPool implements BufferHandler {
|
||||||
|
|
||||||
|
public void setMinBufferSize(int size) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer getBuffer(int size) {
|
||||||
|
if (size == -1)
|
||||||
|
size = 32 * 1024;
|
||||||
|
return ByteBuffer.allocate(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void returnBuffer(ByteBuffer buffer) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static BufferPool bufferpool = new BufferPool();
|
||||||
|
|
||||||
|
private void writeFrame(Http2Frame frame) throws IOException {
|
||||||
|
ByteBufferGenerator bg = new ByteBufferGenerator(bufferpool);
|
||||||
|
frame.computeLength();
|
||||||
|
System.err.println("Writing frame " + frame.toString());
|
||||||
|
frame.writeOutgoing(bg);
|
||||||
|
ByteBuffer[] bufs = bg.getBufferArray();
|
||||||
|
int c = 0;
|
||||||
|
for (ByteBuffer buf : bufs) {
|
||||||
|
byte[] ba = buf.array();
|
||||||
|
int start = buf.arrayOffset() + buf.position();
|
||||||
|
c += buf.remaining();
|
||||||
|
os.write(ba, start, buf.remaining());
|
||||||
|
}
|
||||||
|
os.flush();
|
||||||
|
System.err.printf("wrote %d bytes\n", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleStreamReset(ResetFrame resetFrame) throws IOException {
|
||||||
|
// TODO: cleanup
|
||||||
|
throw new IOException("Stream reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCommonFrame(Http2Frame f) throws IOException {
|
||||||
|
if (f instanceof SettingsFrame) {
|
||||||
|
serverSettings = (SettingsFrame) f;
|
||||||
|
if (serverSettings.getFlag(SettingsFrame.ACK)) // ignore
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// otherwise acknowledge it
|
||||||
|
SettingsFrame frame = new SettingsFrame();
|
||||||
|
frame.setFlag(SettingsFrame.ACK);
|
||||||
|
frame.streamid(0);
|
||||||
|
outputQ.put(frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
System.err.println("Received ---> " + f.toString());
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendWindowUpdates(int len, int streamid) throws IOException {
|
||||||
|
if (len == 0)
|
||||||
|
return;
|
||||||
|
WindowUpdateFrame wup = new WindowUpdateFrame();
|
||||||
|
wup.streamid(streamid);
|
||||||
|
wup.setUpdate(len);
|
||||||
|
outputQ.put(wup);
|
||||||
|
wup = new WindowUpdateFrame();
|
||||||
|
wup.streamid(0);
|
||||||
|
wup.setUpdate(len);
|
||||||
|
outputQ.put(wup);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeadersImpl decodeHeaders(List<HeaderFrame> frames) {
|
||||||
|
HttpHeadersImpl headers = new HttpHeadersImpl();
|
||||||
|
|
||||||
|
DecodingCallback cb = (name, value) -> {
|
||||||
|
headers.addHeader(name.toString(), value.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (HeaderFrame frame : frames) {
|
||||||
|
ByteBuffer[] buffers = frame.getHeaderBlock();
|
||||||
|
for (ByteBuffer buffer : buffers) {
|
||||||
|
hpackIn.decode(buffer, false, cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hpackIn.decode(EMPTY_BUFFER, true, cb);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRequestLine(String request) {
|
||||||
|
int eol = request.indexOf(CRLF);
|
||||||
|
return request.substring(0, eol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First stream (1) comes from a plaintext HTTP/1.1 request
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
void createPrimordialStream(String request) throws IOException {
|
||||||
|
HttpHeadersImpl headers = new HttpHeadersImpl();
|
||||||
|
String requestLine = getRequestLine(request);
|
||||||
|
String[] tokens = requestLine.split(" ");
|
||||||
|
if (!tokens[2].equals("HTTP/1.1")) {
|
||||||
|
throw new IOException("bad request line");
|
||||||
|
}
|
||||||
|
URI uri = null;
|
||||||
|
try {
|
||||||
|
uri = new URI(tokens[1]);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
String host = getHeader(request, "Host");
|
||||||
|
if (host == null) {
|
||||||
|
throw new IOException("missing Host");
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.setHeader(":method", tokens[0]);
|
||||||
|
headers.setHeader(":scheme", "http"); // always in this case
|
||||||
|
headers.setHeader(":authority", host);
|
||||||
|
headers.setHeader(":path", uri.getPath());
|
||||||
|
Queue q = new Queue();
|
||||||
|
String body = getRequestBody(request);
|
||||||
|
headers.setHeader("Content-length", Integer.toString(body.length()));
|
||||||
|
|
||||||
|
addRequestBodyToQueue(body, q);
|
||||||
|
streams.put(1, q);
|
||||||
|
exec.submit(() -> {
|
||||||
|
handleRequest(headers, q, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other streams created here
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
void createStream(HeaderFrame frame) throws IOException {
|
||||||
|
List<HeaderFrame> frames = new LinkedList<>();
|
||||||
|
frames.add(frame);
|
||||||
|
int streamid = frame.streamid();
|
||||||
|
if (streamid != nextstream) {
|
||||||
|
throw new IOException("unexpected stream id");
|
||||||
|
}
|
||||||
|
nextstream += 2;
|
||||||
|
|
||||||
|
while (!frame.getFlag(HeaderFrame.END_HEADERS)) {
|
||||||
|
Http2Frame f = readFrame();
|
||||||
|
if (!(f instanceof HeaderFrame)) {
|
||||||
|
handleCommonFrame(f); // should only be error frames
|
||||||
|
} else {
|
||||||
|
frame = (HeaderFrame) f;
|
||||||
|
frames.add(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpHeadersImpl headers = decodeHeaders(frames);
|
||||||
|
Queue q = new Queue();
|
||||||
|
streams.put(streamid, q);
|
||||||
|
exec.submit(() -> {
|
||||||
|
handleRequest(headers, q, streamid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// runs in own thread. Handles request from start to finish. Incoming frames
|
||||||
|
// for this stream/request delivered on Q
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
void handleRequest(HttpHeadersImpl headers, Queue queue, int streamid) {
|
||||||
|
String method = headers.firstValue(":method").orElse("");
|
||||||
|
System.out.println("method = " + method);
|
||||||
|
String path = headers.firstValue(":path").orElse("");
|
||||||
|
System.out.println("path = " + path);
|
||||||
|
String scheme = headers.firstValue(":scheme").orElse("");
|
||||||
|
System.out.println("scheme = " + scheme);
|
||||||
|
String authority = headers.firstValue(":authority").orElse("");
|
||||||
|
System.out.println("authority = " + authority);
|
||||||
|
HttpHeadersImpl rspheaders = new HttpHeadersImpl();
|
||||||
|
int winsize = clientSettings.getParameter(
|
||||||
|
SettingsFrame.INITIAL_WINDOW_SIZE);
|
||||||
|
System.err.println ("Stream window size = " + winsize);
|
||||||
|
try (
|
||||||
|
BodyInputStream bis = new BodyInputStream(queue, streamid, this);
|
||||||
|
BodyOutputStream bos = new BodyOutputStream(streamid, winsize, this);
|
||||||
|
)
|
||||||
|
{
|
||||||
|
String us = scheme + "://" + authority + path;
|
||||||
|
URI uri = new URI(us);
|
||||||
|
boolean pushAllowed = clientSettings.getParameter(SettingsFrame.ENABLE_PUSH) == 1;
|
||||||
|
Http2TestExchange exchange = new Http2TestExchange(streamid, method,
|
||||||
|
headers, rspheaders, uri, bis, bos, this, pushAllowed);
|
||||||
|
|
||||||
|
// give to user
|
||||||
|
handler.handle(exchange);
|
||||||
|
|
||||||
|
// everything happens in the exchange from here. Hopefully will
|
||||||
|
// return though.
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.err.println("TestServer: handleRequest exception: " + e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs in own thread
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
void readLoop() {
|
||||||
|
try {
|
||||||
|
while (!stopping) {
|
||||||
|
Http2Frame frame = readFrame();
|
||||||
|
int stream = frame.streamid();
|
||||||
|
if (stream == 0) {
|
||||||
|
if (frame.type() == WindowUpdateFrame.TYPE) {
|
||||||
|
WindowUpdateFrame wup = (WindowUpdateFrame) frame;
|
||||||
|
updateConnectionWindow(wup.getUpdate());
|
||||||
|
} else {
|
||||||
|
// other common frame types
|
||||||
|
handleCommonFrame(frame);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Queue q = streams.get(stream);
|
||||||
|
if (frame.type() == HeadersFrame.TYPE) {
|
||||||
|
if (q != null) {
|
||||||
|
System.err.println("HEADERS frame for existing stream! Error.");
|
||||||
|
// TODO: close connection
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
createStream((HeadersFrame) frame);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (q == null) {
|
||||||
|
System.err.printf("Non Headers frame received with"+
|
||||||
|
" non existing stream (%d) ", frame.streamid());
|
||||||
|
System.err.println(frame);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (frame.type() == WindowUpdateFrame.TYPE) {
|
||||||
|
WindowUpdateFrame wup = (WindowUpdateFrame) frame;
|
||||||
|
synchronized (updaters) {
|
||||||
|
Consumer<Integer> r = updaters.get(stream);
|
||||||
|
r.accept(wup.getUpdate());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
q.put(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
close();
|
||||||
|
if (!stopping) {
|
||||||
|
System.err.println("Http server reader thread shutdown");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set streamid outside plus other specific fields
|
||||||
|
void encodeHeaders(HttpHeadersImpl headers, HeaderFrame out) {
|
||||||
|
List<ByteBuffer> buffers = new LinkedList<>();
|
||||||
|
|
||||||
|
ByteBuffer buf = getBuffer();
|
||||||
|
boolean encoded;
|
||||||
|
for (Map.Entry<String, List<String>> entry : headers.map().entrySet()) {
|
||||||
|
List<String> values = entry.getValue();
|
||||||
|
String key = entry.getKey().toLowerCase();
|
||||||
|
for (String value : values) {
|
||||||
|
do {
|
||||||
|
hpackOut.header(key, value);
|
||||||
|
encoded = hpackOut.encode(buf);
|
||||||
|
if (!encoded) {
|
||||||
|
buf.flip();
|
||||||
|
buffers.add(buf);
|
||||||
|
buf = getBuffer();
|
||||||
|
}
|
||||||
|
} while (!encoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
buffers.add(buf);
|
||||||
|
out.setFlags(HeaderFrame.END_HEADERS);
|
||||||
|
out.setHeaderBlock(buffers.toArray(bbarray));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void closeIgnore(Closeable c) {
|
||||||
|
try {
|
||||||
|
c.close();
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs in own thread
|
||||||
|
void writeLoop() {
|
||||||
|
try {
|
||||||
|
while (!stopping) {
|
||||||
|
Http2Frame frame = outputQ.take();
|
||||||
|
if (frame instanceof ResponseHeaders) {
|
||||||
|
ResponseHeaders rh = (ResponseHeaders)frame;
|
||||||
|
HeadersFrame hf = new HeadersFrame();
|
||||||
|
encodeHeaders(rh.headers, hf);
|
||||||
|
hf.streamid(rh.streamid());
|
||||||
|
writeFrame(hf);
|
||||||
|
} else if (frame instanceof OutgoingPushPromise) {
|
||||||
|
handlePush((OutgoingPushPromise)frame);
|
||||||
|
} else
|
||||||
|
writeFrame(frame);
|
||||||
|
}
|
||||||
|
System.err.println("Connection writer stopping");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
/*close();
|
||||||
|
if (!stopping) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("TestServer: writeLoop exception: " + e);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePush(OutgoingPushPromise op) throws IOException {
|
||||||
|
PushPromiseFrame pp = new PushPromiseFrame();
|
||||||
|
encodeHeaders(op.headers, pp);
|
||||||
|
int promisedStreamid = nextPushStreamId;
|
||||||
|
nextPushStreamId += 2;
|
||||||
|
pp.streamid(op.parentStream);
|
||||||
|
pp.setPromisedStream(promisedStreamid);
|
||||||
|
writeFrame(pp);
|
||||||
|
final InputStream ii = op.is;
|
||||||
|
final BodyOutputStream oo = new BodyOutputStream(
|
||||||
|
promisedStreamid,
|
||||||
|
clientSettings.getParameter(
|
||||||
|
SettingsFrame.INITIAL_WINDOW_SIZE), this);
|
||||||
|
oo.goodToGo();
|
||||||
|
exec.submit(() -> {
|
||||||
|
try {
|
||||||
|
ResponseHeaders oh = getPushResponse(promisedStreamid);
|
||||||
|
outputQ.put(oh);
|
||||||
|
ii.transferTo(oo);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
System.err.printf("TestServer: pushing response error: %s\n",
|
||||||
|
ex.toString());
|
||||||
|
} finally {
|
||||||
|
closeIgnore(ii);
|
||||||
|
closeIgnore(oo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a minimal response with status 200
|
||||||
|
// that is the response to the push promise just sent
|
||||||
|
private ResponseHeaders getPushResponse(int streamid) {
|
||||||
|
HttpHeadersImpl h = new HttpHeadersImpl();
|
||||||
|
h.addHeader(":status", "200");
|
||||||
|
ResponseHeaders oh = new ResponseHeaders(h);
|
||||||
|
oh.streamid(streamid);
|
||||||
|
return oh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer getBuffer() {
|
||||||
|
return ByteBuffer.allocate(8 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Http2Frame readFrame() throws IOException {
|
||||||
|
byte[] buf = new byte[9];
|
||||||
|
if (is.readNBytes(buf, 0, 9) != 9)
|
||||||
|
throw new IOException("readFrame: connection closed");
|
||||||
|
int len = 0;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int n = buf[i] & 0xff;
|
||||||
|
//System.err.println("n = " + n);
|
||||||
|
len = (len << 8) + n;
|
||||||
|
}
|
||||||
|
byte[] rest = new byte[len];
|
||||||
|
int n = is.readNBytes(rest, 0, len);
|
||||||
|
if (n != len)
|
||||||
|
throw new IOException("Error reading frame");
|
||||||
|
ByteBufferConsumer bc = new ByteBufferConsumer(
|
||||||
|
new LinkedList<ByteBuffer>(List.of(ByteBuffer.wrap(buf), ByteBuffer.wrap(rest))),
|
||||||
|
this::getBuffer);
|
||||||
|
return Http2Frame.readIncoming(bc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSettingsFrame() throws IOException {
|
||||||
|
sendSettingsFrame(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSettingsFrame(boolean now) throws IOException {
|
||||||
|
if (serverSettings == null) {
|
||||||
|
serverSettings = SettingsFrame.getDefaultSettings();
|
||||||
|
}
|
||||||
|
if (now) {
|
||||||
|
writeFrame(serverSettings);
|
||||||
|
} else {
|
||||||
|
outputQ.put(serverSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String readUntil(String end) throws IOException {
|
||||||
|
int number = end.length();
|
||||||
|
int found = 0;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (found < number) {
|
||||||
|
char expected = end.charAt(found);
|
||||||
|
int c = is.read();
|
||||||
|
if (c == -1) {
|
||||||
|
throw new IOException("Connection closed");
|
||||||
|
}
|
||||||
|
char c0 = (char) c;
|
||||||
|
sb.append(c0);
|
||||||
|
if (c0 != expected) {
|
||||||
|
found = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getContentLength(String headers) {
|
||||||
|
return getIntHeader(headers, "Content-length");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getIntHeader(String headers, String name) {
|
||||||
|
String val = getHeader(headers, name);
|
||||||
|
if (val == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeader(String headers, String name) {
|
||||||
|
String headers1 = headers.toLowerCase(); // not efficient
|
||||||
|
name = CRLF + name.toLowerCase();
|
||||||
|
int start = headers1.indexOf(name);
|
||||||
|
if (start == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
start += 2;
|
||||||
|
int end = headers1.indexOf(CRLF, start);
|
||||||
|
String line = headers.substring(start, end);
|
||||||
|
start = line.indexOf(':');
|
||||||
|
if (start == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return line.substring(start + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
final static String CRLF = "\r\n";
|
||||||
|
|
||||||
|
String readHttp1Request() throws IOException {
|
||||||
|
String headers = readUntil(CRLF + CRLF);
|
||||||
|
int clen = getContentLength(headers);
|
||||||
|
// read the content. There shouldn't be content but ..
|
||||||
|
byte[] buf = new byte[clen];
|
||||||
|
is.readNBytes(buf, 0, clen);
|
||||||
|
String body = new String(buf, "US-ASCII");
|
||||||
|
return headers + body;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendHttp1Response(int code, String msg, String... headers) throws IOException {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("HTTP/1.1 ")
|
||||||
|
.append(code)
|
||||||
|
.append(' ')
|
||||||
|
.append(msg)
|
||||||
|
.append(CRLF);
|
||||||
|
int numheaders = headers.length;
|
||||||
|
for (int i = 0; i < numheaders; i += 2) {
|
||||||
|
sb.append(headers[i])
|
||||||
|
.append(": ")
|
||||||
|
.append(headers[i + 1])
|
||||||
|
.append(CRLF);
|
||||||
|
}
|
||||||
|
sb.append(CRLF);
|
||||||
|
String s = sb.toString();
|
||||||
|
os.write(s.getBytes("US-ASCII"));
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unexpectedFrame(Http2Frame frame) {
|
||||||
|
System.err.println("OOPS. Unexpected");
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final static ByteBuffer[] bbarray = new ByteBuffer[0];
|
||||||
|
|
||||||
|
// wrapper around a BlockingQueue that throws an exception when it's closed
|
||||||
|
// Each stream has one of these
|
||||||
|
|
||||||
|
String getRequestBody(String request) {
|
||||||
|
int bodystart = request.indexOf(CRLF+CRLF);
|
||||||
|
String body;
|
||||||
|
if (bodystart == -1)
|
||||||
|
body = "";
|
||||||
|
else
|
||||||
|
body = request.substring(bodystart+4);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes","unchecked"})
|
||||||
|
void addRequestBodyToQueue(String body, Queue q) throws IOException {
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(body.getBytes(StandardCharsets.US_ASCII));
|
||||||
|
DataFrame df = new DataFrame();
|
||||||
|
df.streamid(1); // only used for primordial stream
|
||||||
|
df.setData(buf);
|
||||||
|
df.computeLength();
|
||||||
|
df.setFlag(DataFrame.END_STREAM);
|
||||||
|
q.put(df);
|
||||||
|
}
|
||||||
|
|
||||||
|
// window updates done in main reader thread because they may
|
||||||
|
// be used to unblock BodyOutputStreams waiting for WUPs
|
||||||
|
|
||||||
|
HashMap<Integer,Consumer<Integer>> updaters = new HashMap<>();
|
||||||
|
|
||||||
|
void registerStreamWindowUpdater(int streamid, Consumer<Integer> r) {
|
||||||
|
synchronized(updaters) {
|
||||||
|
updaters.put(streamid, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int sendWindow = 64 * 1024 - 1; // connection level send window
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BodyOutputStreams call this to get the connection window first.
|
||||||
|
*
|
||||||
|
* @param amount
|
||||||
|
*/
|
||||||
|
synchronized void obtainConnectionWindow(int amount) throws InterruptedException {
|
||||||
|
while (amount > 0) {
|
||||||
|
int n = Math.min(amount, sendWindow);
|
||||||
|
amount -= n;
|
||||||
|
sendWindow -= n;
|
||||||
|
if (amount > 0)
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void updateConnectionWindow(int amount) {
|
||||||
|
sendWindow += amount;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// simplified output headers class. really just a type safe container
|
||||||
|
// for the hashmap.
|
||||||
|
|
||||||
|
static class ResponseHeaders extends Http2Frame {
|
||||||
|
HttpHeadersImpl headers;
|
||||||
|
|
||||||
|
ResponseHeaders(HttpHeadersImpl headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported ever!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
throw new UnsupportedOperationException("Not supported ever!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
|
||||||
|
// will be converted to a PushPromiseFrame in the writeLoop
|
||||||
|
// a thread is then created to produce the DataFrames from the InputStream
|
||||||
|
class OutgoingPushPromise extends Http2Frame {
|
||||||
|
final HttpHeadersImpl headers;
|
||||||
|
final URI uri;
|
||||||
|
final InputStream is;
|
||||||
|
final int parentStream; // not the pushed streamid
|
||||||
|
|
||||||
|
OutgoingPushPromise(int parentStream, URI uri, HttpHeadersImpl headers, InputStream is) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.headers = headers;
|
||||||
|
this.is = is;
|
||||||
|
this.parentStream = parentStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void readIncomingImpl(ByteBufferConsumer bc) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void computeLength() {
|
||||||
|
throw new UnsupportedOperationException("Not supported yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.file.attribute.*;
|
||||||
|
import java.net.http.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class PushHandler implements Http2Handler {
|
||||||
|
|
||||||
|
final Path tempFile;
|
||||||
|
final int loops;
|
||||||
|
|
||||||
|
public PushHandler(int file_size, int loops) throws Exception {
|
||||||
|
tempFile = TestUtil.getAFile(file_size);
|
||||||
|
this.loops = loops;
|
||||||
|
}
|
||||||
|
|
||||||
|
int invocation = 0;
|
||||||
|
|
||||||
|
public void handle(Http2TestExchange ee) {
|
||||||
|
try {
|
||||||
|
System.err.println ("Server: handle " + ee);
|
||||||
|
invocation++;
|
||||||
|
|
||||||
|
if (ee.serverPushAllowed()) {
|
||||||
|
for (int i=0; i<loops; i++) {
|
||||||
|
InputStream is = new FileInputStream(tempFile.toFile());
|
||||||
|
URI u = new URI ("http://www.foo.com/" + Integer.toString(i));
|
||||||
|
HttpHeadersImpl h = new HttpHeadersImpl();
|
||||||
|
h.addHeader("X-foo", "bar");
|
||||||
|
ee.serverPush(u, h, is);
|
||||||
|
}
|
||||||
|
System.err.println ("Server: sent all pushes");
|
||||||
|
}
|
||||||
|
ee.sendResponseHeaders(200, 0);
|
||||||
|
OutputStream os = ee.getResponseBody();
|
||||||
|
InputStream iis = new FileInputStream(tempFile.toFile());
|
||||||
|
iis.transferTo(os);
|
||||||
|
os.close();
|
||||||
|
iis.close();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.err.println ("Server: exception " + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2016, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.net.http;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class TestUtil {
|
||||||
|
|
||||||
|
final static String fileContent = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // repeated
|
||||||
|
|
||||||
|
public static Path getAFile(int size) throws IOException {
|
||||||
|
Path p = tempFile();
|
||||||
|
BufferedWriter writer = Files.newBufferedWriter(p);
|
||||||
|
int len = fileContent.length();
|
||||||
|
int iterations = size / len;
|
||||||
|
int remainder = size - (iterations * len);
|
||||||
|
for (int i=0; i<iterations; i++)
|
||||||
|
writer.write(fileContent, 0, len);
|
||||||
|
writer.write(fileContent, 0, remainder);
|
||||||
|
writer.close();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path tempFile() {
|
||||||
|
try {
|
||||||
|
Path p = Files.createTempFile("foo", "test");
|
||||||
|
p.toFile().deleteOnExit();
|
||||||
|
return p;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Void compareFiles(Path path1, Path path2) {
|
||||||
|
try {
|
||||||
|
if (Files.size(path1) != Files.size(path2))
|
||||||
|
throw new RuntimeException("File sizes do not match");
|
||||||
|
compareContents(path1, path2);
|
||||||
|
return null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void compareContents(Path path1, Path path2) {
|
||||||
|
try {
|
||||||
|
byte[] b1 = Files.readAllBytes(path1);
|
||||||
|
byte[] b2 = Files.readAllBytes(path2);
|
||||||
|
if (!Arrays.equals(b1, b2))
|
||||||
|
throw new RuntimeException ("Files do not match");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user