Merge
This commit is contained in:
commit
a51fe99bfd
1
.hgtags
1
.hgtags
@ -490,3 +490,4 @@ a11c1cb542bbd1671d25b85efe7d09b983c48525 jdk-11+15
|
||||
02934b0d661b82b7fe1052a04998d2091352e08d jdk-11+16
|
||||
64e4b1686141e57a681936a8283983341484676e jdk-11+17
|
||||
e1b3def126240d5433902f3cb0e91a4c27f6db50 jdk-11+18
|
||||
fb8b3f4672774e15654958295558a1af1b576919 jdk-11+19
|
||||
|
@ -549,6 +549,7 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_HELPER],
|
||||
elif test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
|
||||
WARNING_CFLAGS="-W3"
|
||||
WARNING_CFLAGS_JDK="-wd4800"
|
||||
WARNING_CFLAGS_JVM="-wd4800"
|
||||
fi
|
||||
|
||||
# Set some additional per-OS defines.
|
||||
|
@ -166,7 +166,6 @@ void C1_MacroAssembler::try_allocate(Register obj, Register var_size_in_bytes, i
|
||||
tlab_allocate(obj, var_size_in_bytes, con_size_in_bytes, t1, t2, slow_case);
|
||||
} else {
|
||||
eden_allocate(obj, var_size_in_bytes, con_size_in_bytes, t1, slow_case);
|
||||
incr_allocated_bytes(noreg, var_size_in_bytes, con_size_in_bytes, t1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,7 +722,6 @@ OopMapSet* Runtime1::generate_code_for(StubID id, StubAssembler* sasm) {
|
||||
__ ldrw(obj_size, Address(klass, Klass::layout_helper_offset()));
|
||||
|
||||
__ eden_allocate(obj, obj_size, 0, t1, slow_path);
|
||||
__ incr_allocated_bytes(rthread, obj_size, 0, rscratch1);
|
||||
|
||||
__ initialize_object(obj, klass, obj_size, 0, t1, t2, /* is_tlab_allocated */ false);
|
||||
__ verify_oop(obj);
|
||||
@ -823,7 +822,6 @@ OopMapSet* Runtime1::generate_code_for(StubID id, StubAssembler* sasm) {
|
||||
__ andr(arr_size, arr_size, ~MinObjAlignmentInBytesMask);
|
||||
|
||||
__ eden_allocate(obj, arr_size, 0, t1, slow_path); // preserves arr_size
|
||||
__ incr_allocated_bytes(rthread, arr_size, 0, rscratch1);
|
||||
|
||||
__ initialize_header(obj, klass, length, t1, t2);
|
||||
__ ldrb(t1, Address(klass, in_bytes(Klass::layout_helper_offset()) + (Klass::_lh_header_size_shift / BitsPerByte)));
|
||||
|
@ -24,7 +24,9 @@
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "gc/shared/barrierSetAssembler.hpp"
|
||||
#include "gc/shared/collectedHeap.hpp"
|
||||
#include "runtime/jniHandles.hpp"
|
||||
#include "runtime/thread.hpp"
|
||||
|
||||
#define __ masm->
|
||||
|
||||
@ -121,3 +123,109 @@ void BarrierSetAssembler::try_resolve_jobject_in_native(MacroAssembler* masm, Re
|
||||
__ andr(obj, obj, ~JNIHandles::weak_tag_mask);
|
||||
__ ldr(obj, Address(obj, 0)); // *obj
|
||||
}
|
||||
|
||||
// Defines obj, preserves var_size_in_bytes, okay for t2 == var_size_in_bytes.
|
||||
void BarrierSetAssembler::tlab_allocate(MacroAssembler* masm, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Register t2,
|
||||
Label& slow_case) {
|
||||
assert_different_registers(obj, t2);
|
||||
assert_different_registers(obj, var_size_in_bytes);
|
||||
Register end = t2;
|
||||
|
||||
// verify_tlab();
|
||||
|
||||
__ ldr(obj, Address(rthread, JavaThread::tlab_top_offset()));
|
||||
if (var_size_in_bytes == noreg) {
|
||||
__ lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
__ lea(end, Address(obj, var_size_in_bytes));
|
||||
}
|
||||
__ ldr(rscratch1, Address(rthread, JavaThread::tlab_end_offset()));
|
||||
__ cmp(end, rscratch1);
|
||||
__ br(Assembler::HI, slow_case);
|
||||
|
||||
// update the tlab top pointer
|
||||
__ str(end, Address(rthread, JavaThread::tlab_top_offset()));
|
||||
|
||||
// recover var_size_in_bytes if necessary
|
||||
if (var_size_in_bytes == end) {
|
||||
__ sub(var_size_in_bytes, var_size_in_bytes, obj);
|
||||
}
|
||||
// verify_tlab();
|
||||
}
|
||||
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void BarrierSetAssembler::eden_allocate(MacroAssembler* masm, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case) {
|
||||
assert_different_registers(obj, var_size_in_bytes, t1);
|
||||
if (!Universe::heap()->supports_inline_contig_alloc()) {
|
||||
__ b(slow_case);
|
||||
} else {
|
||||
Register end = t1;
|
||||
Register heap_end = rscratch2;
|
||||
Label retry;
|
||||
__ bind(retry);
|
||||
{
|
||||
unsigned long offset;
|
||||
__ adrp(rscratch1, ExternalAddress((address) Universe::heap()->end_addr()), offset);
|
||||
__ ldr(heap_end, Address(rscratch1, offset));
|
||||
}
|
||||
|
||||
ExternalAddress heap_top((address) Universe::heap()->top_addr());
|
||||
|
||||
// Get the current top of the heap
|
||||
{
|
||||
unsigned long offset;
|
||||
__ adrp(rscratch1, heap_top, offset);
|
||||
// Use add() here after ARDP, rather than lea().
|
||||
// lea() does not generate anything if its offset is zero.
|
||||
// However, relocs expect to find either an ADD or a load/store
|
||||
// insn after an ADRP. add() always generates an ADD insn, even
|
||||
// for add(Rn, Rn, 0).
|
||||
__ add(rscratch1, rscratch1, offset);
|
||||
__ ldaxr(obj, rscratch1);
|
||||
}
|
||||
|
||||
// Adjust it my the size of our new object
|
||||
if (var_size_in_bytes == noreg) {
|
||||
__ lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
__ lea(end, Address(obj, var_size_in_bytes));
|
||||
}
|
||||
|
||||
// if end < obj then we wrapped around high memory
|
||||
__ cmp(end, obj);
|
||||
__ br(Assembler::LO, slow_case);
|
||||
|
||||
__ cmp(end, heap_end);
|
||||
__ br(Assembler::HI, slow_case);
|
||||
|
||||
// If heap_top hasn't been changed by some other thread, update it.
|
||||
__ stlxr(rscratch2, end, rscratch1);
|
||||
__ cbnzw(rscratch2, retry);
|
||||
|
||||
incr_allocated_bytes(masm, var_size_in_bytes, con_size_in_bytes, t1);
|
||||
}
|
||||
}
|
||||
|
||||
void BarrierSetAssembler::incr_allocated_bytes(MacroAssembler* masm,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1) {
|
||||
assert(t1->is_valid(), "need temp reg");
|
||||
|
||||
__ ldr(t1, Address(rthread, in_bytes(JavaThread::allocated_bytes_offset())));
|
||||
if (var_size_in_bytes->is_valid()) {
|
||||
__ add(t1, t1, var_size_in_bytes);
|
||||
} else {
|
||||
__ add(t1, t1, con_size_in_bytes);
|
||||
}
|
||||
__ str(t1, Address(rthread, in_bytes(JavaThread::allocated_bytes_offset())));
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,11 @@
|
||||
#include "oops/access.hpp"
|
||||
|
||||
class BarrierSetAssembler: public CHeapObj<mtGC> {
|
||||
private:
|
||||
void incr_allocated_bytes(MacroAssembler* masm,
|
||||
Register var_size_in_bytes, int con_size_in_bytes,
|
||||
Register t1 = noreg);
|
||||
|
||||
public:
|
||||
virtual void arraycopy_prologue(MacroAssembler* masm, DecoratorSet decorators, bool is_oop,
|
||||
Register addr, Register count, RegSet saved_regs) {}
|
||||
@ -46,6 +51,22 @@ public:
|
||||
virtual void try_resolve_jobject_in_native(MacroAssembler* masm, Register jni_env,
|
||||
Register obj, Register tmp, Label& slowpath);
|
||||
|
||||
virtual void tlab_allocate(MacroAssembler* masm,
|
||||
Register obj, // result: pointer to object after successful allocation
|
||||
Register var_size_in_bytes, // object size in bytes if unknown at compile time; invalid otherwise
|
||||
int con_size_in_bytes, // object size in bytes if known at compile time
|
||||
Register t1, // temp register
|
||||
Register t2, // temp register
|
||||
Label& slow_case // continuation point if fast allocation fails
|
||||
);
|
||||
|
||||
void eden_allocate(MacroAssembler* masm,
|
||||
Register obj, // result: pointer to object after successful allocation
|
||||
Register var_size_in_bytes, // object size in bytes if unknown at compile time; invalid otherwise
|
||||
int con_size_in_bytes, // object size in bytes if known at compile time
|
||||
Register t1, // temp register
|
||||
Label& slow_case // continuation point if fast allocation fails
|
||||
);
|
||||
virtual void barrier_stubs_init() {}
|
||||
};
|
||||
|
||||
|
@ -2440,24 +2440,6 @@ ATOMIC_XCHG(xchgalw, swpal, ldaxrw, stlxrw, Assembler::word)
|
||||
|
||||
#undef ATOMIC_XCHG
|
||||
|
||||
void MacroAssembler::incr_allocated_bytes(Register thread,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1) {
|
||||
if (!thread->is_valid()) {
|
||||
thread = rthread;
|
||||
}
|
||||
assert(t1->is_valid(), "need temp reg");
|
||||
|
||||
ldr(t1, Address(thread, in_bytes(JavaThread::allocated_bytes_offset())));
|
||||
if (var_size_in_bytes->is_valid()) {
|
||||
add(t1, t1, var_size_in_bytes);
|
||||
} else {
|
||||
add(t1, t1, con_size_in_bytes);
|
||||
}
|
||||
str(t1, Address(thread, in_bytes(JavaThread::allocated_bytes_offset())));
|
||||
}
|
||||
|
||||
#ifndef PRODUCT
|
||||
extern "C" void findpc(intptr_t x);
|
||||
#endif
|
||||
@ -4085,30 +4067,18 @@ void MacroAssembler::tlab_allocate(Register obj,
|
||||
Register t1,
|
||||
Register t2,
|
||||
Label& slow_case) {
|
||||
assert_different_registers(obj, t2);
|
||||
assert_different_registers(obj, var_size_in_bytes);
|
||||
Register end = t2;
|
||||
BarrierSetAssembler *bs = BarrierSet::barrier_set()->barrier_set_assembler();
|
||||
bs->tlab_allocate(this, obj, var_size_in_bytes, con_size_in_bytes, t1, t2, slow_case);
|
||||
}
|
||||
|
||||
// verify_tlab();
|
||||
|
||||
ldr(obj, Address(rthread, JavaThread::tlab_top_offset()));
|
||||
if (var_size_in_bytes == noreg) {
|
||||
lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
lea(end, Address(obj, var_size_in_bytes));
|
||||
}
|
||||
ldr(rscratch1, Address(rthread, JavaThread::tlab_end_offset()));
|
||||
cmp(end, rscratch1);
|
||||
br(Assembler::HI, slow_case);
|
||||
|
||||
// update the tlab top pointer
|
||||
str(end, Address(rthread, JavaThread::tlab_top_offset()));
|
||||
|
||||
// recover var_size_in_bytes if necessary
|
||||
if (var_size_in_bytes == end) {
|
||||
sub(var_size_in_bytes, var_size_in_bytes, obj);
|
||||
}
|
||||
// verify_tlab();
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void MacroAssembler::eden_allocate(Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case) {
|
||||
BarrierSetAssembler *bs = BarrierSet::barrier_set()->barrier_set_assembler();
|
||||
bs->eden_allocate(this, obj, var_size_in_bytes, con_size_in_bytes, t1, slow_case);
|
||||
}
|
||||
|
||||
// Zero words; len is in bytes
|
||||
@ -4173,61 +4143,6 @@ void MacroAssembler::zero_memory(Register addr, Register len, Register t1) {
|
||||
cbnz(len, loop);
|
||||
}
|
||||
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void MacroAssembler::eden_allocate(Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case) {
|
||||
assert_different_registers(obj, var_size_in_bytes, t1);
|
||||
if (!Universe::heap()->supports_inline_contig_alloc()) {
|
||||
b(slow_case);
|
||||
} else {
|
||||
Register end = t1;
|
||||
Register heap_end = rscratch2;
|
||||
Label retry;
|
||||
bind(retry);
|
||||
{
|
||||
unsigned long offset;
|
||||
adrp(rscratch1, ExternalAddress((address) Universe::heap()->end_addr()), offset);
|
||||
ldr(heap_end, Address(rscratch1, offset));
|
||||
}
|
||||
|
||||
ExternalAddress heap_top((address) Universe::heap()->top_addr());
|
||||
|
||||
// Get the current top of the heap
|
||||
{
|
||||
unsigned long offset;
|
||||
adrp(rscratch1, heap_top, offset);
|
||||
// Use add() here after ARDP, rather than lea().
|
||||
// lea() does not generate anything if its offset is zero.
|
||||
// However, relocs expect to find either an ADD or a load/store
|
||||
// insn after an ADRP. add() always generates an ADD insn, even
|
||||
// for add(Rn, Rn, 0).
|
||||
add(rscratch1, rscratch1, offset);
|
||||
ldaxr(obj, rscratch1);
|
||||
}
|
||||
|
||||
// Adjust it my the size of our new object
|
||||
if (var_size_in_bytes == noreg) {
|
||||
lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
lea(end, Address(obj, var_size_in_bytes));
|
||||
}
|
||||
|
||||
// if end < obj then we wrapped around high memory
|
||||
cmp(end, obj);
|
||||
br(Assembler::LO, slow_case);
|
||||
|
||||
cmp(end, heap_end);
|
||||
br(Assembler::HI, slow_case);
|
||||
|
||||
// If heap_top hasn't been changed by some other thread, update it.
|
||||
stlxr(rscratch2, end, rscratch1);
|
||||
cbnzw(rscratch2, retry);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroAssembler::verify_tlab() {
|
||||
#ifdef ASSERT
|
||||
if (UseTLAB && VerifyOops) {
|
||||
|
@ -866,10 +866,6 @@ public:
|
||||
void zero_memory(Register addr, Register len, Register t1);
|
||||
void verify_tlab();
|
||||
|
||||
void incr_allocated_bytes(Register thread,
|
||||
Register var_size_in_bytes, int con_size_in_bytes,
|
||||
Register t1 = noreg);
|
||||
|
||||
// interface method calling
|
||||
void lookup_interface_method(Register recv_klass,
|
||||
Register intf_klass,
|
||||
|
@ -3562,7 +3562,6 @@ void TemplateTable::_new() {
|
||||
// r3: instance size in bytes
|
||||
if (allow_shared_alloc) {
|
||||
__ eden_allocate(r0, r3, 0, r10, slow_case);
|
||||
__ incr_allocated_bytes(rthread, r3, 0, rscratch1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3023,8 +3023,8 @@ void MacroAssembler::fast_lock(Register Roop, Register Rbox, Register Rscratch,
|
||||
mov(Rscratch, SP);
|
||||
sub(Rscratch, Rmark, Rscratch);
|
||||
ands(Rscratch, Rscratch, imm);
|
||||
b(done, ne); // exit with failure
|
||||
str(Rscratch, Address(Rbox, BasicLock::displaced_header_offset_in_bytes())); // set to zero
|
||||
// set to zero if recursive lock, set to non zero otherwise (see discussion in JDK-8153107)
|
||||
str(Rscratch, Address(Rbox, BasicLock::displaced_header_offset_in_bytes()));
|
||||
b(done);
|
||||
|
||||
#else
|
||||
@ -3034,7 +3034,8 @@ void MacroAssembler::fast_lock(Register Roop, Register Rbox, Register Rscratch,
|
||||
sub(Rscratch, Rmark, SP, eq);
|
||||
movs(Rscratch, AsmOperand(Rscratch, lsr, exact_log2(os::vm_page_size())), eq);
|
||||
// If still 'eq' then recursive locking OK
|
||||
str(Rscratch, Address(Rbox, BasicLock::displaced_header_offset_in_bytes()), eq); // set to zero
|
||||
// set to zero if recursive lock, set to non zero otherwise (see discussion in JDK-8153107)
|
||||
str(Rscratch, Address(Rbox, BasicLock::displaced_header_offset_in_bytes()));
|
||||
b(done);
|
||||
#endif
|
||||
|
||||
|
@ -139,10 +139,9 @@ void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void C1_MacroAssembler::try_allocate(Register obj, Register var_size_in_bytes, int con_size_in_bytes, Register t1, Register t2, Label& slow_case) {
|
||||
if (UseTLAB) {
|
||||
tlab_allocate(obj, var_size_in_bytes, con_size_in_bytes, t1, t2, slow_case);
|
||||
tlab_allocate(noreg, obj, var_size_in_bytes, con_size_in_bytes, t1, t2, slow_case);
|
||||
} else {
|
||||
eden_allocate(obj, var_size_in_bytes, con_size_in_bytes, t1, slow_case);
|
||||
incr_allocated_bytes(noreg, var_size_in_bytes, con_size_in_bytes, t1);
|
||||
eden_allocate(noreg, obj, var_size_in_bytes, con_size_in_bytes, t1, slow_case);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1056,8 +1056,7 @@ OopMapSet* Runtime1::generate_code_for(StubID id, StubAssembler* sasm) {
|
||||
// get the instance size (size is postive so movl is fine for 64bit)
|
||||
__ movl(obj_size, Address(klass, Klass::layout_helper_offset()));
|
||||
|
||||
__ eden_allocate(obj, obj_size, 0, t1, slow_path);
|
||||
__ incr_allocated_bytes(thread, obj_size, 0);
|
||||
__ eden_allocate(thread, obj, obj_size, 0, t1, slow_path);
|
||||
|
||||
__ initialize_object(obj, klass, obj_size, 0, t1, t2, /* is_tlab_allocated */ false);
|
||||
__ verify_oop(obj);
|
||||
@ -1155,12 +1154,10 @@ OopMapSet* Runtime1::generate_code_for(StubID id, StubAssembler* sasm) {
|
||||
__ addptr(arr_size, MinObjAlignmentInBytesMask); // align up
|
||||
__ andptr(arr_size, ~MinObjAlignmentInBytesMask);
|
||||
|
||||
__ eden_allocate(obj, arr_size, 0, t1, slow_path); // preserves arr_size
|
||||
|
||||
// Using t2 for non 64-bit.
|
||||
const Register thread = NOT_LP64(t2) LP64_ONLY(r15_thread);
|
||||
NOT_LP64(__ get_thread(thread));
|
||||
__ incr_allocated_bytes(thread, arr_size, 0);
|
||||
__ eden_allocate(thread, obj, arr_size, 0, t1, slow_path); // preserves arr_size
|
||||
|
||||
__ initialize_header(obj, klass, length, t1, t2);
|
||||
__ movb(t1, Address(klass, in_bytes(Klass::layout_helper_offset()) + (Klass::_lh_header_size_shift / BitsPerByte)));
|
||||
|
@ -24,8 +24,10 @@
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "gc/shared/barrierSetAssembler.hpp"
|
||||
#include "gc/shared/collectedHeap.hpp"
|
||||
#include "interpreter/interp_masm.hpp"
|
||||
#include "runtime/jniHandles.hpp"
|
||||
#include "runtime/thread.hpp"
|
||||
|
||||
#define __ masm->
|
||||
|
||||
@ -213,3 +215,110 @@ void BarrierSetAssembler::try_resolve_jobject_in_native(MacroAssembler* masm, Re
|
||||
__ clear_jweak_tag(obj);
|
||||
__ movptr(obj, Address(obj, 0));
|
||||
}
|
||||
|
||||
void BarrierSetAssembler::tlab_allocate(MacroAssembler* masm,
|
||||
Register thread, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Register t2,
|
||||
Label& slow_case) {
|
||||
assert_different_registers(obj, t1, t2);
|
||||
assert_different_registers(obj, var_size_in_bytes, t1);
|
||||
Register end = t2;
|
||||
if (!thread->is_valid()) {
|
||||
#ifdef _LP64
|
||||
thread = r15_thread;
|
||||
#else
|
||||
assert(t1->is_valid(), "need temp reg");
|
||||
thread = t1;
|
||||
__ get_thread(thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
__ verify_tlab();
|
||||
|
||||
__ movptr(obj, Address(thread, JavaThread::tlab_top_offset()));
|
||||
if (var_size_in_bytes == noreg) {
|
||||
__ lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
__ lea(end, Address(obj, var_size_in_bytes, Address::times_1));
|
||||
}
|
||||
__ cmpptr(end, Address(thread, JavaThread::tlab_end_offset()));
|
||||
__ jcc(Assembler::above, slow_case);
|
||||
|
||||
// update the tlab top pointer
|
||||
__ movptr(Address(thread, JavaThread::tlab_top_offset()), end);
|
||||
|
||||
// recover var_size_in_bytes if necessary
|
||||
if (var_size_in_bytes == end) {
|
||||
__ subptr(var_size_in_bytes, obj);
|
||||
}
|
||||
__ verify_tlab();
|
||||
}
|
||||
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void BarrierSetAssembler::eden_allocate(MacroAssembler* masm,
|
||||
Register thread, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case) {
|
||||
assert(obj == rax, "obj must be in rax, for cmpxchg");
|
||||
assert_different_registers(obj, var_size_in_bytes, t1);
|
||||
if (!Universe::heap()->supports_inline_contig_alloc()) {
|
||||
__ jmp(slow_case);
|
||||
} else {
|
||||
Register end = t1;
|
||||
Label retry;
|
||||
__ bind(retry);
|
||||
ExternalAddress heap_top((address) Universe::heap()->top_addr());
|
||||
__ movptr(obj, heap_top);
|
||||
if (var_size_in_bytes == noreg) {
|
||||
__ lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
__ lea(end, Address(obj, var_size_in_bytes, Address::times_1));
|
||||
}
|
||||
// if end < obj then we wrapped around => object too long => slow case
|
||||
__ cmpptr(end, obj);
|
||||
__ jcc(Assembler::below, slow_case);
|
||||
__ cmpptr(end, ExternalAddress((address) Universe::heap()->end_addr()));
|
||||
__ jcc(Assembler::above, slow_case);
|
||||
// Compare obj with the top addr, and if still equal, store the new top addr in
|
||||
// end at the address of the top addr pointer. Sets ZF if was equal, and clears
|
||||
// it otherwise. Use lock prefix for atomicity on MPs.
|
||||
__ locked_cmpxchgptr(end, heap_top);
|
||||
__ jcc(Assembler::notEqual, retry);
|
||||
incr_allocated_bytes(masm, thread, var_size_in_bytes, con_size_in_bytes, thread->is_valid() ? noreg : t1);
|
||||
}
|
||||
}
|
||||
|
||||
void BarrierSetAssembler::incr_allocated_bytes(MacroAssembler* masm, Register thread,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1) {
|
||||
if (!thread->is_valid()) {
|
||||
#ifdef _LP64
|
||||
thread = r15_thread;
|
||||
#else
|
||||
assert(t1->is_valid(), "need temp reg");
|
||||
thread = t1;
|
||||
__ get_thread(thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _LP64
|
||||
if (var_size_in_bytes->is_valid()) {
|
||||
__ addq(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), var_size_in_bytes);
|
||||
} else {
|
||||
__ addq(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), con_size_in_bytes);
|
||||
}
|
||||
#else
|
||||
if (var_size_in_bytes->is_valid()) {
|
||||
__ addl(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), var_size_in_bytes);
|
||||
} else {
|
||||
__ addl(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), con_size_in_bytes);
|
||||
}
|
||||
__ adcl(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())+4), 0);
|
||||
#endif
|
||||
}
|
||||
|
@ -32,7 +32,12 @@
|
||||
class InterpreterMacroAssembler;
|
||||
|
||||
class BarrierSetAssembler: public CHeapObj<mtGC> {
|
||||
protected:
|
||||
private:
|
||||
void incr_allocated_bytes(MacroAssembler* masm, Register thread,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1);
|
||||
|
||||
public:
|
||||
virtual void arraycopy_prologue(MacroAssembler* masm, DecoratorSet decorators, BasicType type,
|
||||
Register src, Register dst, Register count) {}
|
||||
@ -60,6 +65,19 @@ public:
|
||||
virtual void try_resolve_jobject_in_native(MacroAssembler* masm, Register jni_env,
|
||||
Register obj, Register tmp, Label& slowpath);
|
||||
|
||||
virtual void tlab_allocate(MacroAssembler* masm,
|
||||
Register thread, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1, Register t2,
|
||||
Label& slow_case);
|
||||
virtual void eden_allocate(MacroAssembler* masm,
|
||||
Register thread, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case);
|
||||
|
||||
virtual void barrier_stubs_init() {}
|
||||
};
|
||||
|
||||
|
@ -2959,40 +2959,6 @@ void MacroAssembler::empty_FPU_stack() {
|
||||
#endif // !LP64 || C1 || !C2 || INCLUDE_JVMCI
|
||||
|
||||
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void MacroAssembler::eden_allocate(Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case) {
|
||||
assert(obj == rax, "obj must be in rax, for cmpxchg");
|
||||
assert_different_registers(obj, var_size_in_bytes, t1);
|
||||
if (!Universe::heap()->supports_inline_contig_alloc()) {
|
||||
jmp(slow_case);
|
||||
} else {
|
||||
Register end = t1;
|
||||
Label retry;
|
||||
bind(retry);
|
||||
ExternalAddress heap_top((address) Universe::heap()->top_addr());
|
||||
movptr(obj, heap_top);
|
||||
if (var_size_in_bytes == noreg) {
|
||||
lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
lea(end, Address(obj, var_size_in_bytes, Address::times_1));
|
||||
}
|
||||
// if end < obj then we wrapped around => object too long => slow case
|
||||
cmpptr(end, obj);
|
||||
jcc(Assembler::below, slow_case);
|
||||
cmpptr(end, ExternalAddress((address) Universe::heap()->end_addr()));
|
||||
jcc(Assembler::above, slow_case);
|
||||
// Compare obj with the top addr, and if still equal, store the new top addr in
|
||||
// end at the address of the top addr pointer. Sets ZF if was equal, and clears
|
||||
// it otherwise. Use lock prefix for atomicity on MPs.
|
||||
locked_cmpxchgptr(end, heap_top);
|
||||
jcc(Assembler::notEqual, retry);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroAssembler::enter() {
|
||||
push(rbp);
|
||||
mov(rbp, rsp);
|
||||
@ -5310,38 +5276,24 @@ void MacroAssembler::testptr(Register dst, Register src) {
|
||||
}
|
||||
|
||||
// Defines obj, preserves var_size_in_bytes, okay for t2 == var_size_in_bytes.
|
||||
void MacroAssembler::tlab_allocate(Register obj,
|
||||
void MacroAssembler::tlab_allocate(Register thread, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Register t2,
|
||||
Label& slow_case) {
|
||||
assert_different_registers(obj, t1, t2);
|
||||
assert_different_registers(obj, var_size_in_bytes, t1);
|
||||
Register end = t2;
|
||||
Register thread = NOT_LP64(t1) LP64_ONLY(r15_thread);
|
||||
BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler();
|
||||
bs->tlab_allocate(this, thread, obj, var_size_in_bytes, con_size_in_bytes, t1, t2, slow_case);
|
||||
}
|
||||
|
||||
verify_tlab();
|
||||
|
||||
NOT_LP64(get_thread(thread));
|
||||
|
||||
movptr(obj, Address(thread, JavaThread::tlab_top_offset()));
|
||||
if (var_size_in_bytes == noreg) {
|
||||
lea(end, Address(obj, con_size_in_bytes));
|
||||
} else {
|
||||
lea(end, Address(obj, var_size_in_bytes, Address::times_1));
|
||||
}
|
||||
cmpptr(end, Address(thread, JavaThread::tlab_end_offset()));
|
||||
jcc(Assembler::above, slow_case);
|
||||
|
||||
// update the tlab top pointer
|
||||
movptr(Address(thread, JavaThread::tlab_top_offset()), end);
|
||||
|
||||
// recover var_size_in_bytes if necessary
|
||||
if (var_size_in_bytes == end) {
|
||||
subptr(var_size_in_bytes, obj);
|
||||
}
|
||||
verify_tlab();
|
||||
// Defines obj, preserves var_size_in_bytes
|
||||
void MacroAssembler::eden_allocate(Register thread, Register obj,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1,
|
||||
Label& slow_case) {
|
||||
BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler();
|
||||
bs->eden_allocate(this, thread, obj, var_size_in_bytes, con_size_in_bytes, t1, slow_case);
|
||||
}
|
||||
|
||||
// Preserves the contents of address, destroys the contents length_in_bytes and temp.
|
||||
@ -5400,36 +5352,6 @@ void MacroAssembler::zero_memory(Register address, Register length_in_bytes, int
|
||||
bind(done);
|
||||
}
|
||||
|
||||
void MacroAssembler::incr_allocated_bytes(Register thread,
|
||||
Register var_size_in_bytes,
|
||||
int con_size_in_bytes,
|
||||
Register t1) {
|
||||
if (!thread->is_valid()) {
|
||||
#ifdef _LP64
|
||||
thread = r15_thread;
|
||||
#else
|
||||
assert(t1->is_valid(), "need temp reg");
|
||||
thread = t1;
|
||||
get_thread(thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _LP64
|
||||
if (var_size_in_bytes->is_valid()) {
|
||||
addq(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), var_size_in_bytes);
|
||||
} else {
|
||||
addq(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), con_size_in_bytes);
|
||||
}
|
||||
#else
|
||||
if (var_size_in_bytes->is_valid()) {
|
||||
addl(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), var_size_in_bytes);
|
||||
} else {
|
||||
addl(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())), con_size_in_bytes);
|
||||
}
|
||||
adcl(Address(thread, in_bytes(JavaThread::allocated_bytes_offset())+4), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Look up the method for a megamorphic invokeinterface call.
|
||||
// The target method is determined by <intf_klass, itable_index>.
|
||||
// The receiver klass is in recv_klass.
|
||||
|
@ -504,6 +504,7 @@ class MacroAssembler: public Assembler {
|
||||
|
||||
// allocation
|
||||
void eden_allocate(
|
||||
Register thread, // Current thread
|
||||
Register obj, // result: pointer to object after successful allocation
|
||||
Register var_size_in_bytes, // object size in bytes if unknown at compile time; invalid otherwise
|
||||
int con_size_in_bytes, // object size in bytes if known at compile time
|
||||
@ -511,6 +512,7 @@ class MacroAssembler: public Assembler {
|
||||
Label& slow_case // continuation point if fast allocation fails
|
||||
);
|
||||
void tlab_allocate(
|
||||
Register thread, // Current thread
|
||||
Register obj, // result: pointer to object after successful allocation
|
||||
Register var_size_in_bytes, // object size in bytes if unknown at compile time; invalid otherwise
|
||||
int con_size_in_bytes, // object size in bytes if known at compile time
|
||||
@ -520,10 +522,6 @@ class MacroAssembler: public Assembler {
|
||||
);
|
||||
void zero_memory(Register address, Register length_in_bytes, int offset_in_bytes, Register temp);
|
||||
|
||||
void incr_allocated_bytes(Register thread,
|
||||
Register var_size_in_bytes, int con_size_in_bytes,
|
||||
Register t1 = noreg);
|
||||
|
||||
// interface method calling
|
||||
void lookup_interface_method(Register recv_klass,
|
||||
Register intf_klass,
|
||||
|
@ -4013,7 +4013,7 @@ void TemplateTable::_new() {
|
||||
#endif // _LP64
|
||||
|
||||
if (UseTLAB) {
|
||||
__ tlab_allocate(rax, rdx, 0, rcx, rbx, slow_case);
|
||||
__ tlab_allocate(thread, rax, rdx, 0, rcx, rbx, slow_case);
|
||||
if (ZeroTLAB) {
|
||||
// the fields have been already cleared
|
||||
__ jmp(initialize_header);
|
||||
@ -4025,10 +4025,7 @@ void TemplateTable::_new() {
|
||||
// Allocation in the shared Eden, if allowed.
|
||||
//
|
||||
// rdx: instance size in bytes
|
||||
if (allow_shared_alloc) {
|
||||
__ eden_allocate(rax, rdx, 0, rbx, slow_case);
|
||||
__ incr_allocated_bytes(thread, rdx, 0);
|
||||
}
|
||||
__ eden_allocate(thread, rax, rdx, 0, rbx, slow_case);
|
||||
}
|
||||
|
||||
// If UseTLAB or allow_shared_alloc are true, the object is created above and
|
||||
|
@ -3737,16 +3737,6 @@ bool os::message_box(const char* title, const char* message) {
|
||||
return buf[0] == 'y' || buf[0] == 'Y';
|
||||
}
|
||||
|
||||
int os::stat(const char *path, struct stat *sbuf) {
|
||||
char pathbuf[MAX_PATH];
|
||||
if (strlen(path) > MAX_PATH - 1) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
os::native_path(strcpy(pathbuf, path));
|
||||
return ::stat(pathbuf, sbuf);
|
||||
}
|
||||
|
||||
// Is a (classpath) directory empty?
|
||||
bool os::dir_is_empty(const char* path) {
|
||||
DIR *dir = NULL;
|
||||
|
@ -93,10 +93,6 @@ inline int os::fsync(int fd) {
|
||||
return ::fsync(fd);
|
||||
}
|
||||
|
||||
inline char* os::native_path(char *path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
inline int os::ftruncate(int fd, jlong length) {
|
||||
return ::ftruncate64(fd, length);
|
||||
}
|
||||
|
@ -3489,16 +3489,6 @@ bool os::message_box(const char* title, const char* message) {
|
||||
return buf[0] == 'y' || buf[0] == 'Y';
|
||||
}
|
||||
|
||||
int os::stat(const char *path, struct stat *sbuf) {
|
||||
char pathbuf[MAX_PATH];
|
||||
if (strlen(path) > MAX_PATH - 1) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
os::native_path(strcpy(pathbuf, path));
|
||||
return ::stat(pathbuf, sbuf);
|
||||
}
|
||||
|
||||
static inline struct timespec get_mtime(const char* filename) {
|
||||
struct stat st;
|
||||
int ret = os::stat(filename, &st);
|
||||
|
@ -96,10 +96,6 @@ inline int os::fsync(int fd) {
|
||||
return ::fsync(fd);
|
||||
}
|
||||
|
||||
inline char* os::native_path(char *path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
inline int os::ftruncate(int fd, jlong length) {
|
||||
return ::ftruncate(fd, length);
|
||||
}
|
||||
|
@ -5378,16 +5378,6 @@ bool os::message_box(const char* title, const char* message) {
|
||||
return buf[0] == 'y' || buf[0] == 'Y';
|
||||
}
|
||||
|
||||
int os::stat(const char *path, struct stat *sbuf) {
|
||||
char pathbuf[MAX_PATH];
|
||||
if (strlen(path) > MAX_PATH - 1) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
os::native_path(strcpy(pathbuf, path));
|
||||
return ::stat(pathbuf, sbuf);
|
||||
}
|
||||
|
||||
// Is a (classpath) directory empty?
|
||||
bool os::dir_is_empty(const char* path) {
|
||||
DIR *dir = NULL;
|
||||
|
@ -88,10 +88,6 @@ inline int os::fsync(int fd) {
|
||||
return ::fsync(fd);
|
||||
}
|
||||
|
||||
inline char* os::native_path(char *path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
inline int os::ftruncate(int fd, jlong length) {
|
||||
return ::ftruncate64(fd, length);
|
||||
}
|
||||
|
@ -1334,6 +1334,13 @@ char* os::Posix::realpath(const char* filename, char* outbuf, size_t outbuflen)
|
||||
|
||||
}
|
||||
|
||||
int os::stat(const char *path, struct stat *sbuf) {
|
||||
return ::stat(path, sbuf);
|
||||
}
|
||||
|
||||
char * os::native_path(char *path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Check minimum allowable stack sizes for thread creation and to initialize
|
||||
// the java system classes, including StackOverflowError - depends on page
|
||||
|
@ -1667,16 +1667,6 @@ void* os::get_default_process_handle() {
|
||||
return (void*)::dlopen(NULL, RTLD_LAZY);
|
||||
}
|
||||
|
||||
int os::stat(const char *path, struct stat *sbuf) {
|
||||
char pathbuf[MAX_PATH];
|
||||
if (strlen(path) > MAX_PATH - 1) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
os::native_path(strcpy(pathbuf, path));
|
||||
return ::stat(pathbuf, sbuf);
|
||||
}
|
||||
|
||||
static inline time_t get_mtime(const char* filename) {
|
||||
struct stat st;
|
||||
int ret = os::stat(filename, &st);
|
||||
@ -4474,10 +4464,6 @@ jlong os::lseek(int fd, jlong offset, int whence) {
|
||||
return (jlong) ::lseek64(fd, offset, whence);
|
||||
}
|
||||
|
||||
char * os::native_path(char *path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
int os::ftruncate(int fd, jlong length) {
|
||||
return ::ftruncate64(fd, length);
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ private:
|
||||
void check_concurrent_work();
|
||||
void trigger_concurrent_work();
|
||||
|
||||
static uintx item_added();
|
||||
static size_t item_added();
|
||||
static void item_removed();
|
||||
size_t add_items_to_clean(size_t ndead);
|
||||
|
||||
|
@ -409,7 +409,7 @@ void CodeCache::add_heap(ReservedSpace rs, const char* name, int code_blob_type)
|
||||
add_heap(heap);
|
||||
|
||||
// Reserve Space
|
||||
size_t size_initial = MIN2(InitialCodeCacheSize, rs.size());
|
||||
size_t size_initial = MIN2((size_t)InitialCodeCacheSize, rs.size());
|
||||
size_initial = align_up(size_initial, os::vm_page_size());
|
||||
if (!heap->reserve(rs, size_initial, CodeCacheSegmentSize)) {
|
||||
vm_exit_during_initialization(err_msg("Could not reserve enough space in %s (" SIZE_FORMAT "K)",
|
||||
|
@ -198,7 +198,7 @@ void CompilerConfig::set_tiered_flags() {
|
||||
// Increase the code cache size - tiered compiles a lot more.
|
||||
if (FLAG_IS_DEFAULT(ReservedCodeCacheSize)) {
|
||||
FLAG_SET_ERGO(uintx, ReservedCodeCacheSize,
|
||||
MIN2(CODE_CACHE_DEFAULT_LIMIT, ReservedCodeCacheSize * 5));
|
||||
MIN2(CODE_CACHE_DEFAULT_LIMIT, (size_t)ReservedCodeCacheSize * 5));
|
||||
}
|
||||
// Enable SegmentedCodeCache if TieredCompilation is enabled and ReservedCodeCacheSize >= 240M
|
||||
if (FLAG_IS_DEFAULT(SegmentedCodeCache) && ReservedCodeCacheSize >= 240*M) {
|
||||
|
@ -203,7 +203,7 @@ bool ParScanThreadState::take_from_overflow_stack() {
|
||||
const size_t num_overflow_elems = of_stack->size();
|
||||
const size_t space_available = queue->max_elems() - queue->size();
|
||||
const size_t num_take_elems = MIN3(space_available / 4,
|
||||
ParGCDesiredObjsFromOverflowList,
|
||||
(size_t)ParGCDesiredObjsFromOverflowList,
|
||||
num_overflow_elems);
|
||||
// Transfer the most recent num_take_elems from the overflow
|
||||
// stack to our work queue.
|
||||
|
@ -2350,7 +2350,7 @@ void G1CMTask::drain_local_queue(bool partially) {
|
||||
// of things to do) or totally (at the very end).
|
||||
size_t target_size;
|
||||
if (partially) {
|
||||
target_size = MIN2((size_t)_task_queue->max_elems()/3, GCDrainStackTargetSize);
|
||||
target_size = MIN2((size_t)_task_queue->max_elems()/3, (size_t)GCDrainStackTargetSize);
|
||||
} else {
|
||||
target_size = 0;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2018, 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
|
||||
@ -31,7 +31,7 @@ void G1CMObjArrayProcessor::push_array_slice(HeapWord* what) {
|
||||
}
|
||||
|
||||
size_t G1CMObjArrayProcessor::process_array_slice(objArrayOop obj, HeapWord* start_from, size_t remaining) {
|
||||
size_t words_to_scan = MIN2(remaining, ObjArrayMarkingStride);
|
||||
size_t words_to_scan = MIN2(remaining, (size_t)ObjArrayMarkingStride);
|
||||
|
||||
if (remaining > ObjArrayMarkingStride) {
|
||||
push_array_slice(start_from + ObjArrayMarkingStride);
|
||||
|
@ -91,7 +91,7 @@ class G1PageBasedVirtualSpace {
|
||||
void pretouch_internal(size_t start_page, size_t end_page);
|
||||
|
||||
// Returns the index of the page which contains the given address.
|
||||
uintptr_t addr_to_page_index(char* addr) const;
|
||||
size_t addr_to_page_index(char* addr) const;
|
||||
// Returns the address of the given page index.
|
||||
char* page_start(size_t index) const;
|
||||
|
||||
|
@ -52,12 +52,12 @@
|
||||
"Use maximum compaction in the Parallel Old garbage collector " \
|
||||
"for a system GC") \
|
||||
\
|
||||
product(uintx, ParallelOldDeadWoodLimiterMean, 50, \
|
||||
product(size_t, ParallelOldDeadWoodLimiterMean, 50, \
|
||||
"The mean used by the parallel compact dead wood " \
|
||||
"limiter (a number between 0-100)") \
|
||||
range(0, 100) \
|
||||
\
|
||||
product(uintx, ParallelOldDeadWoodLimiterStdDev, 80, \
|
||||
product(size_t, ParallelOldDeadWoodLimiterStdDev, 80, \
|
||||
"The standard deviation used by the parallel compact dead wood " \
|
||||
"limiter (a number between 0-100)") \
|
||||
range(0, 100) \
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2010, 2018, 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
|
||||
@ -118,7 +118,7 @@ inline void oop_pc_follow_contents_specialized(objArrayOop obj, int index, ParCo
|
||||
const size_t beg_index = size_t(index);
|
||||
assert(beg_index < len || len == 0, "index too large");
|
||||
|
||||
const size_t stride = MIN2(len - beg_index, ObjArrayMarkingStride);
|
||||
const size_t stride = MIN2(len - beg_index, (size_t)ObjArrayMarkingStride);
|
||||
const size_t end_index = beg_index + stride;
|
||||
T* const base = (T*)obj->base_raw();
|
||||
T* const beg = base + beg_index;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2018, 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
|
||||
@ -32,7 +32,7 @@
|
||||
|
||||
size_t PLAB::min_size() {
|
||||
// Make sure that we return something that is larger than AlignmentReserve
|
||||
return align_object_size(MAX2(MinTLABSize / HeapWordSize, (uintx)oopDesc::header_size())) + AlignmentReserve;
|
||||
return align_object_size(MAX2(MinTLABSize / HeapWordSize, (size_t)oopDesc::header_size())) + AlignmentReserve;
|
||||
}
|
||||
|
||||
size_t PLAB::max_size() {
|
||||
|
@ -654,7 +654,7 @@ void StringDedupTable::print_statistics() {
|
||||
STRDEDUP_BYTES_PARAM(_table->_size * sizeof(StringDedupEntry*) + (_table->_entries + _entry_cache->size()) * sizeof(StringDedupEntry)));
|
||||
log.debug(" Size: " SIZE_FORMAT ", Min: " SIZE_FORMAT ", Max: " SIZE_FORMAT, _table->_size, _min_size, _max_size);
|
||||
log.debug(" Entries: " UINTX_FORMAT ", Load: " STRDEDUP_PERCENT_FORMAT_NS ", Cached: " UINTX_FORMAT ", Added: " UINTX_FORMAT ", Removed: " UINTX_FORMAT,
|
||||
_table->_entries, percent_of(_table->_entries, _table->_size), _entry_cache->size(), _entries_added, _entries_removed);
|
||||
_table->_entries, percent_of((size_t)_table->_entries, _table->_size), _entry_cache->size(), _entries_added, _entries_removed);
|
||||
log.debug(" Resize Count: " UINTX_FORMAT ", Shrink Threshold: " UINTX_FORMAT "(" STRDEDUP_PERCENT_FORMAT_NS "), Grow Threshold: " UINTX_FORMAT "(" STRDEDUP_PERCENT_FORMAT_NS ")",
|
||||
_resize_count, _table->_shrink_threshold, _shrink_load_factor * 100.0, _table->_grow_threshold, _grow_load_factor * 100.0);
|
||||
log.debug(" Rehash Count: " UINTX_FORMAT ", Rehash Threshold: " UINTX_FORMAT ", Hash Seed: 0x%x", _rehash_count, _rehash_threshold, _table->_hash_seed);
|
||||
|
@ -91,6 +91,10 @@ void ZArguments::initialize() {
|
||||
FLAG_SET_DEFAULT(VerifyDuringStartup, false);
|
||||
FLAG_SET_DEFAULT(VerifyBeforeExit, false);
|
||||
|
||||
// Verification before heap iteration not (yet) supported, for the
|
||||
// same reason we need fixup_partial_loads
|
||||
FLAG_SET_DEFAULT(VerifyBeforeIteration, false);
|
||||
|
||||
// Verification of stacks not (yet) supported, for the same reason
|
||||
// we need fixup_partial_loads
|
||||
DEBUG_ONLY(FLAG_SET_DEFAULT(VerifyStack, false));
|
||||
|
@ -138,6 +138,7 @@ ZServiceabilityMemoryUsageTracker::~ZServiceabilityMemoryUsageTracker() {
|
||||
ZServiceabilityManagerStatsTracer::ZServiceabilityManagerStatsTracer(bool is_gc_begin, bool is_gc_end) :
|
||||
_stats(ZHeap::heap()->serviceability_memory_manager(),
|
||||
ZCollectedHeap::heap()->gc_cause() /* cause */,
|
||||
true /* allMemoryPoolsAffected */,
|
||||
is_gc_begin /* recordGCBeginTime */,
|
||||
is_gc_begin /* recordPreGCUsage */,
|
||||
true /* recordPeakUsage */,
|
||||
|
@ -42,6 +42,11 @@ ObjectSampler* LeakProfiler::_object_sampler = NULL;
|
||||
|
||||
static volatile jbyte suspended = 0;
|
||||
bool LeakProfiler::start(jint sample_count) {
|
||||
if (UseZGC) {
|
||||
log_warning(jfr)("LeakProfiler is currently not supported in combination with ZGC");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_object_sampler != NULL) {
|
||||
// already started
|
||||
return true;
|
||||
|
@ -87,10 +87,28 @@ bool JfrRecorder::on_vm_init() {
|
||||
|
||||
static JfrStartFlightRecordingDCmd* _startup_recording = NULL;
|
||||
|
||||
static void release_startup_recording() {
|
||||
if (_startup_recording != NULL) {
|
||||
delete _startup_recording;
|
||||
_startup_recording = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void teardown_startup_support() {
|
||||
release_startup_recording();
|
||||
JfrOptionSet::release_startup_recordings();
|
||||
}
|
||||
|
||||
|
||||
// Parsing options here to detect errors as soon as possible
|
||||
static bool parse_startup_recording(TRAPS) {
|
||||
assert(StartFlightRecording != NULL, "invariant");
|
||||
CmdLine cmdline(StartFlightRecording, strlen(StartFlightRecording), true);
|
||||
static bool parse_recording_options(const char* options, TRAPS) {
|
||||
assert(options != NULL, "invariant");
|
||||
if (_startup_recording != NULL) {
|
||||
delete _startup_recording;
|
||||
}
|
||||
CmdLine cmdline(options, strlen(options), true);
|
||||
_startup_recording = new (ResourceObj::C_HEAP, mtTracing) JfrStartFlightRecordingDCmd(tty, true);
|
||||
assert(_startup_recording != NULL, "invariant");
|
||||
_startup_recording->parse(&cmdline, ',', THREAD);
|
||||
if (HAS_PENDING_EXCEPTION) {
|
||||
java_lang_Throwable::print(PENDING_EXCEPTION, tty);
|
||||
@ -100,31 +118,61 @@ static bool parse_startup_recording(TRAPS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool initialize_startup_recording(TRAPS) {
|
||||
if (StartFlightRecording != NULL) {
|
||||
_startup_recording = new (ResourceObj::C_HEAP, mtTracing) JfrStartFlightRecordingDCmd(tty, true);
|
||||
return _startup_recording != NULL && parse_startup_recording(THREAD);
|
||||
static bool validate_recording_options(TRAPS) {
|
||||
const GrowableArray<const char*>* startup_options = JfrOptionSet::startup_recordings();
|
||||
if (startup_options == NULL) {
|
||||
return true;
|
||||
}
|
||||
const int length = startup_options->length();
|
||||
assert(length >= 1, "invariant");
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (!parse_recording_options(startup_options->at(i), THREAD)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool startup_recording(TRAPS) {
|
||||
if (_startup_recording == NULL) {
|
||||
return true;
|
||||
}
|
||||
log_trace(jfr, system)("Starting up Jfr startup recording");
|
||||
static bool launch_recording(TRAPS) {
|
||||
assert(_startup_recording != NULL, "invariant");
|
||||
log_trace(jfr, system)("Starting a recording");
|
||||
_startup_recording->execute(DCmd_Source_Internal, Thread::current());
|
||||
delete _startup_recording;
|
||||
_startup_recording = NULL;
|
||||
if (HAS_PENDING_EXCEPTION) {
|
||||
log_debug(jfr, system)("Exception while starting Jfr startup recording");
|
||||
log_debug(jfr, system)("Exception while starting a recording");
|
||||
CLEAR_PENDING_EXCEPTION;
|
||||
return false;
|
||||
}
|
||||
log_trace(jfr, system)("Finished starting Jfr startup recording");
|
||||
log_trace(jfr, system)("Finished starting a recording");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool launch_recordings(const GrowableArray<const char*>* startup_options, TRAPS) {
|
||||
assert(startup_options != NULL, "invariant");
|
||||
const int length = startup_options->length();
|
||||
assert(length >= 1, "invariant");
|
||||
if (length == 1) {
|
||||
// already parsed and ready, launch it
|
||||
return launch_recording(THREAD);
|
||||
}
|
||||
for (int i = 0; i < length; ++i) {
|
||||
parse_recording_options(startup_options->at(i), THREAD);
|
||||
if (!launch_recording(THREAD)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool startup_recordings(TRAPS) {
|
||||
const GrowableArray<const char*>* startup_options = JfrOptionSet::startup_recordings();
|
||||
if (startup_options == NULL) {
|
||||
return true;
|
||||
}
|
||||
const bool ret = launch_recordings(startup_options, THREAD);
|
||||
teardown_startup_support();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void log_jdk_jfr_module_resolution_error(TRAPS) {
|
||||
LogTarget(Error, jfr, system) lt_error;
|
||||
LogTargetHandle handle(lt_error);
|
||||
@ -141,7 +189,7 @@ bool JfrRecorder::on_vm_start() {
|
||||
if (!register_jfr_dcmds()) {
|
||||
return false;
|
||||
}
|
||||
if (!initialize_startup_recording(thread)) {
|
||||
if (!validate_recording_options(thread)) {
|
||||
return false;
|
||||
}
|
||||
if (in_graph) {
|
||||
@ -159,7 +207,7 @@ bool JfrRecorder::on_vm_start() {
|
||||
log_jdk_jfr_module_resolution_error(thread);
|
||||
return false;
|
||||
}
|
||||
return startup_recording(thread);
|
||||
return startup_recordings(thread);
|
||||
}
|
||||
|
||||
static bool _created = false;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "runtime/thread.inline.hpp"
|
||||
#include "services/diagnosticArgument.hpp"
|
||||
#include "services/diagnosticFramework.hpp"
|
||||
#include "utilities/growableArray.hpp"
|
||||
#include "utilities/ostream.hpp"
|
||||
|
||||
struct ObsoleteOption {
|
||||
@ -664,42 +665,51 @@ bool JfrOptionSet::adjust_memory_options() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
to support starting multiple startup recordings
|
||||
|
||||
static const char* start_flight_recording_option_original = NULL;
|
||||
static const char* flight_recorder_option_original = NULL;
|
||||
|
||||
static void copy_option_string(const JavaVMOption* option, const char** addr) {
|
||||
assert(option != NULL, "invariant");
|
||||
assert(option->optionString != NULL, "invariant");
|
||||
const size_t length = strlen(option->optionString);
|
||||
*addr = JfrCHeapObj::new_array<char>(length + 1);
|
||||
assert(*addr != NULL, "invarinat");
|
||||
strncpy((char*)*addr, option->optionString, length + 1);
|
||||
assert(strncmp(*addr, option->optionString, length + 1) == 0, "invariant");
|
||||
}
|
||||
|
||||
copy_option_string(*option, &start_flight_recording_option_original);
|
||||
copy_option_string(*option, &flight_recorder_option_original);
|
||||
*/
|
||||
static GrowableArray<const char*>* startup_recording_array = NULL;
|
||||
|
||||
bool JfrOptionSet::parse_start_flight_recording_option(const JavaVMOption** option, char* tail) {
|
||||
assert(option != NULL, "invariant");
|
||||
assert(tail != NULL, "invariant");
|
||||
assert((*option)->optionString != NULL, "invariant");
|
||||
assert(strncmp((*option)->optionString, "-XX:StartFlightRecording", 24) == 0, "invariant");
|
||||
const char* param_string = NULL;
|
||||
if (*tail == '\0') {
|
||||
// Add dummy dumponexit=false so -XX:StartFlightRecording can be used without a parameter.
|
||||
// The existing option->optionString points to stack memory so no need to deallocate.
|
||||
const_cast<JavaVMOption*>(*option)->optionString = (char*)"-XX:StartFlightRecording=dumponexit=false";
|
||||
param_string = (*option)->optionString + 25;
|
||||
} else {
|
||||
*tail = '='; // ":" -> "="
|
||||
param_string = tail + 1;
|
||||
}
|
||||
assert(param_string != NULL, "invariant");
|
||||
const size_t param_length = strlen(param_string);
|
||||
|
||||
if (startup_recording_array == NULL) {
|
||||
startup_recording_array = new (ResourceObj::C_HEAP, mtTracing) GrowableArray<const char*>(8, true, mtTracing);
|
||||
}
|
||||
assert(startup_recording_array != NULL, "invariant");
|
||||
char* startup_options = NEW_C_HEAP_ARRAY(char, param_length + 1, mtTracing);
|
||||
strncpy(startup_options, param_string, strlen(param_string) + 1);
|
||||
assert(strncmp(param_string, startup_options, param_length) == 0, "invariant");
|
||||
startup_recording_array->append(startup_options);
|
||||
return false;
|
||||
}
|
||||
|
||||
const GrowableArray<const char*>* JfrOptionSet::startup_recordings() {
|
||||
return startup_recording_array;
|
||||
}
|
||||
|
||||
void JfrOptionSet::release_startup_recordings() {
|
||||
if (startup_recording_array != NULL) {
|
||||
for (int i = 0; i < startup_recording_array->length(); ++i) {
|
||||
FREE_C_HEAP_ARRAY(char, startup_recording_array->at(i));
|
||||
}
|
||||
}
|
||||
delete startup_recording_array;
|
||||
DEBUG_ONLY(startup_recording_array = NULL;)
|
||||
}
|
||||
|
||||
bool JfrOptionSet::parse_flight_recorder_option(const JavaVMOption** option, char* tail) {
|
||||
assert(option != NULL, "invariant");
|
||||
assert(tail != NULL, "invariant");
|
||||
|
@ -29,6 +29,9 @@
|
||||
#include "memory/allocation.hpp"
|
||||
#include "utilities/exceptions.hpp"
|
||||
|
||||
template <typename>
|
||||
class GrowableArray;
|
||||
|
||||
//
|
||||
// Command-line options and defaults
|
||||
//
|
||||
@ -78,6 +81,8 @@ class JfrOptionSet : public AllStatic {
|
||||
static bool parse_start_flight_recording_option(const JavaVMOption** option, char* tail);
|
||||
static bool parse_flight_recorder_option(const JavaVMOption** option, char* tail);
|
||||
|
||||
static const GrowableArray<const char*>* startup_recordings();
|
||||
static void release_startup_recordings();
|
||||
};
|
||||
|
||||
#endif // SHARE_VM_JFR_RECORDER_SERVICE_JFROPTIONSET_HPP
|
||||
|
@ -1116,7 +1116,7 @@ WB_ENTRY(jobject, WB_GetUint64VMFlag(JNIEnv* env, jobject o, jstring name))
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(jobject, WB_GetSizeTVMFlag(JNIEnv* env, jobject o, jstring name))
|
||||
uintx result;
|
||||
size_t result;
|
||||
if (GetVMFlag <size_t> (thread, env, name, &result, &JVMFlag::size_tAt)) {
|
||||
ThreadToNativeFromVM ttnfv(thread); // can't be in VM when we call JNI
|
||||
return longBox(thread, env, result);
|
||||
|
@ -540,6 +540,7 @@ static SpecialFlag const special_jvm_flags[] = {
|
||||
{ "PrintSafepointStatisticsCount",JDK_Version::jdk(11), JDK_Version::jdk(12), JDK_Version::jdk(13) },
|
||||
{ "AggressiveOpts", JDK_Version::jdk(11), JDK_Version::jdk(12), JDK_Version::jdk(13) },
|
||||
{ "AllowNonVirtualCalls", JDK_Version::jdk(11), JDK_Version::jdk(12), JDK_Version::jdk(13) },
|
||||
{ "UnlinkSymbolsALot", JDK_Version::jdk(11), JDK_Version::jdk(12), JDK_Version::jdk(13) },
|
||||
|
||||
// --- Deprecated alias flags (see also aliased_jvm_flags) - sorted by obsolete_in then expired_in:
|
||||
{ "DefaultMaxRAMFraction", JDK_Version::jdk(8), JDK_Version::undefined(), JDK_Version::undefined() },
|
||||
|
@ -333,7 +333,7 @@ class Arguments : AllStatic {
|
||||
// Value of the conservative maximum heap alignment needed
|
||||
static size_t _conservative_max_heap_alignment;
|
||||
|
||||
static uintx _min_heap_size;
|
||||
static size_t _min_heap_size;
|
||||
|
||||
// -Xrun arguments
|
||||
static AgentLibraryList _libraryList;
|
||||
|
@ -558,6 +558,9 @@ class os: AllStatic {
|
||||
static FILE* fopen(const char* path, const char* mode);
|
||||
static int close(int fd);
|
||||
static jlong lseek(int fd, jlong offset, int whence);
|
||||
// This function, on Windows, canonicalizes a given path (see os_windows.cpp for details).
|
||||
// On Posix, this function is a noop: it does not change anything and just returns
|
||||
// the input pointer.
|
||||
static char* native_path(char *path);
|
||||
static int ftruncate(int fd, jlong length);
|
||||
static int fsync(int fd);
|
||||
|
@ -1989,11 +1989,7 @@ JRT_END
|
||||
|
||||
// Handles the uncommon case in locking, i.e., contention or an inflated lock.
|
||||
JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* _obj, BasicLock* lock, JavaThread* thread))
|
||||
// Disable ObjectSynchronizer::quick_enter() in default config
|
||||
// on AARCH64 and ARM until JDK-8153107 is resolved.
|
||||
if (ARM_ONLY((SyncFlags & 256) != 0 &&)
|
||||
AARCH64_ONLY((SyncFlags & 256) != 0 &&)
|
||||
!SafepointSynchronize::is_synchronizing()) {
|
||||
if (!SafepointSynchronize::is_synchronizing()) {
|
||||
// Only try quick_enter() if we're not trying to reach a safepoint
|
||||
// so that the calling thread reaches the safepoint more quickly.
|
||||
if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;
|
||||
|
@ -115,7 +115,7 @@ const char* Abstract_VM_Version::vm_name() {
|
||||
|
||||
const char* Abstract_VM_Version::vm_vendor() {
|
||||
#ifdef VENDOR
|
||||
return XSTR(VENDOR);
|
||||
return VENDOR;
|
||||
#else
|
||||
return "Oracle Corporation";
|
||||
#endif
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package java.util;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
@ -276,8 +277,12 @@ public interface Collection<E> extends Iterable<E> {
|
||||
* allocate a new array even if this collection is backed by an array).
|
||||
* The caller is thus free to modify the returned array.
|
||||
*
|
||||
* <p>This method acts as bridge between array-based and collection-based
|
||||
* APIs.
|
||||
* @apiNote
|
||||
* This method acts as a bridge between array-based and collection-based APIs.
|
||||
* It returns an array whose runtime type is {@code Object[]}.
|
||||
* Use {@link #toArray(Object[]) toArray(T[])} to reuse an existing
|
||||
* array, or use {@link #toArray(IntFunction)} to control the runtime type
|
||||
* of the array.
|
||||
*
|
||||
* @return an array, whose {@linkplain Class#getComponentType runtime component
|
||||
* type} is {@code Object}, containing all of the elements in this collection
|
||||
@ -302,19 +307,27 @@ public interface Collection<E> extends Iterable<E> {
|
||||
* are returned by its iterator, this method must return the elements in
|
||||
* the same order.
|
||||
*
|
||||
* <p>Like the {@link #toArray()} method, this method acts as bridge between
|
||||
* array-based and collection-based APIs. Further, this method allows
|
||||
* precise control over the runtime type of the output array, and may,
|
||||
* under certain circumstances, be used to save allocation costs.
|
||||
* @apiNote
|
||||
* This method acts as a bridge between array-based and collection-based APIs.
|
||||
* It allows an existing array to be reused under certain circumstances.
|
||||
* Use {@link #toArray()} to create an array whose runtime type is {@code Object[]},
|
||||
* or use {@link #toArray(IntFunction)} to control the runtime type of
|
||||
* the array.
|
||||
*
|
||||
* <p>Suppose {@code x} is a collection known to contain only strings.
|
||||
* The following code can be used to dump the collection into a newly
|
||||
* allocated array of {@code String}:
|
||||
* The following code can be used to dump the collection into a previously
|
||||
* allocated {@code String} array:
|
||||
*
|
||||
* <pre>
|
||||
* String[] y = x.toArray(new String[0]);</pre>
|
||||
* String[] y = new String[SIZE];
|
||||
* ...
|
||||
* y = x.toArray(y);</pre>
|
||||
*
|
||||
* Note that {@code toArray(new Object[0])} is identical in function to
|
||||
* <p>The return value is reassigned to the variable {@code y}, because a
|
||||
* new array will be allocated and returned if the collection {@code x} has
|
||||
* too many elements to fit into the existing array {@code y}.
|
||||
*
|
||||
* <p>Note that {@code toArray(new Object[0])} is identical in function to
|
||||
* {@code toArray()}.
|
||||
*
|
||||
* @param <T> the component type of the array to contain the collection
|
||||
@ -329,6 +342,45 @@ public interface Collection<E> extends Iterable<E> {
|
||||
*/
|
||||
<T> T[] toArray(T[] a);
|
||||
|
||||
/**
|
||||
* Returns an array containing all of the elements in this collection,
|
||||
* using the provided {@code generator} function to allocate the returned array.
|
||||
*
|
||||
* <p>If this collection makes any guarantees as to what order its elements
|
||||
* are returned by its iterator, this method must return the elements in
|
||||
* the same order.
|
||||
*
|
||||
* @apiNote
|
||||
* This method acts as a bridge between array-based and collection-based APIs.
|
||||
* It allows creation of an array of a particular runtime type. Use
|
||||
* {@link #toArray()} to create an array whose runtime type is {@code Object[]},
|
||||
* or use {@link #toArray(Object[]) toArray(T[])} to reuse an existing array.
|
||||
*
|
||||
* <p>Suppose {@code x} is a collection known to contain only strings.
|
||||
* The following code can be used to dump the collection into a newly
|
||||
* allocated array of {@code String}:
|
||||
*
|
||||
* <pre>
|
||||
* String[] y = x.toArray(String[]::new);</pre>
|
||||
*
|
||||
* @implSpec
|
||||
* The default implementation calls the generator function with zero
|
||||
* and then passes the resulting array to {@link #toArray(Object[]) toArray(T[])}.
|
||||
*
|
||||
* @param <T> the component type of the array to contain the collection
|
||||
* @param generator a function which produces a new array of the desired
|
||||
* type and the provided length
|
||||
* @return an array containing all of the elements in this collection
|
||||
* @throws ArrayStoreException if the runtime type of any element in this
|
||||
* collection is not assignable to the {@linkplain Class#getComponentType
|
||||
* runtime component type} of the generated array
|
||||
* @throws NullPointerException if the generator function is null
|
||||
* @since 11
|
||||
*/
|
||||
default <T> T[] toArray(IntFunction<T[]> generator) {
|
||||
return toArray(generator.apply(0));
|
||||
}
|
||||
|
||||
// Modification Operations
|
||||
|
||||
/**
|
||||
|
@ -33,6 +33,7 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.IntStream;
|
||||
@ -1028,12 +1029,13 @@ public class Collections {
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public int size() {return c.size();}
|
||||
public boolean isEmpty() {return c.isEmpty();}
|
||||
public boolean contains(Object o) {return c.contains(o);}
|
||||
public Object[] toArray() {return c.toArray();}
|
||||
public <T> T[] toArray(T[] a) {return c.toArray(a);}
|
||||
public String toString() {return c.toString();}
|
||||
public int size() {return c.size();}
|
||||
public boolean isEmpty() {return c.isEmpty();}
|
||||
public boolean contains(Object o) {return c.contains(o);}
|
||||
public Object[] toArray() {return c.toArray();}
|
||||
public <T> T[] toArray(T[] a) {return c.toArray(a);}
|
||||
public <T> T[] toArray(IntFunction<T[]> f) {return c.toArray(f);}
|
||||
public String toString() {return c.toString();}
|
||||
|
||||
public Iterator<E> iterator() {
|
||||
return new Iterator<E>() {
|
||||
@ -2020,6 +2022,9 @@ public class Collections {
|
||||
public <T> T[] toArray(T[] a) {
|
||||
synchronized (mutex) {return c.toArray(a);}
|
||||
}
|
||||
public <T> T[] toArray(IntFunction<T[]> f) {
|
||||
synchronized (mutex) {return c.toArray(f);}
|
||||
}
|
||||
|
||||
public Iterator<E> iterator() {
|
||||
return c.iterator(); // Must be manually synched by user!
|
||||
@ -3049,14 +3054,15 @@ public class Collections {
|
||||
this.type = Objects.requireNonNull(type, "type");
|
||||
}
|
||||
|
||||
public int size() { return c.size(); }
|
||||
public boolean isEmpty() { return c.isEmpty(); }
|
||||
public boolean contains(Object o) { return c.contains(o); }
|
||||
public Object[] toArray() { return c.toArray(); }
|
||||
public <T> T[] toArray(T[] a) { return c.toArray(a); }
|
||||
public String toString() { return c.toString(); }
|
||||
public boolean remove(Object o) { return c.remove(o); }
|
||||
public void clear() { c.clear(); }
|
||||
public int size() { return c.size(); }
|
||||
public boolean isEmpty() { return c.isEmpty(); }
|
||||
public boolean contains(Object o) { return c.contains(o); }
|
||||
public Object[] toArray() { return c.toArray(); }
|
||||
public <T> T[] toArray(T[] a) { return c.toArray(a); }
|
||||
public <T> T[] toArray(IntFunction<T[]> f) { return c.toArray(f); }
|
||||
public String toString() { return c.toString(); }
|
||||
public boolean remove(Object o) { return c.remove(o); }
|
||||
public void clear() { c.clear(); }
|
||||
|
||||
public boolean containsAll(Collection<?> coll) {
|
||||
return c.containsAll(coll);
|
||||
@ -5559,25 +5565,26 @@ public class Collections {
|
||||
implements Queue<E>, Serializable {
|
||||
private static final long serialVersionUID = 1802017725587941708L;
|
||||
private final Deque<E> q;
|
||||
AsLIFOQueue(Deque<E> q) { this.q = q; }
|
||||
public boolean add(E e) { q.addFirst(e); return true; }
|
||||
public boolean offer(E e) { return q.offerFirst(e); }
|
||||
public E poll() { return q.pollFirst(); }
|
||||
public E remove() { return q.removeFirst(); }
|
||||
public E peek() { return q.peekFirst(); }
|
||||
public E element() { return q.getFirst(); }
|
||||
public void clear() { q.clear(); }
|
||||
public int size() { return q.size(); }
|
||||
public boolean isEmpty() { return q.isEmpty(); }
|
||||
public boolean contains(Object o) { return q.contains(o); }
|
||||
public boolean remove(Object o) { return q.remove(o); }
|
||||
public Iterator<E> iterator() { return q.iterator(); }
|
||||
public Object[] toArray() { return q.toArray(); }
|
||||
public <T> T[] toArray(T[] a) { return q.toArray(a); }
|
||||
public String toString() { return q.toString(); }
|
||||
public boolean containsAll(Collection<?> c) {return q.containsAll(c);}
|
||||
public boolean removeAll(Collection<?> c) {return q.removeAll(c);}
|
||||
public boolean retainAll(Collection<?> c) {return q.retainAll(c);}
|
||||
AsLIFOQueue(Deque<E> q) { this.q = q; }
|
||||
public boolean add(E e) { q.addFirst(e); return true; }
|
||||
public boolean offer(E e) { return q.offerFirst(e); }
|
||||
public E poll() { return q.pollFirst(); }
|
||||
public E remove() { return q.removeFirst(); }
|
||||
public E peek() { return q.peekFirst(); }
|
||||
public E element() { return q.getFirst(); }
|
||||
public void clear() { q.clear(); }
|
||||
public int size() { return q.size(); }
|
||||
public boolean isEmpty() { return q.isEmpty(); }
|
||||
public boolean contains(Object o) { return q.contains(o); }
|
||||
public boolean remove(Object o) { return q.remove(o); }
|
||||
public Iterator<E> iterator() { return q.iterator(); }
|
||||
public Object[] toArray() { return q.toArray(); }
|
||||
public <T> T[] toArray(T[] a) { return q.toArray(a); }
|
||||
public <T> T[] toArray(IntFunction<T[]> f) { return q.toArray(f); }
|
||||
public String toString() { return q.toString(); }
|
||||
public boolean containsAll(Collection<?> c) { return q.containsAll(c); }
|
||||
public boolean removeAll(Collection<?> c) { return q.removeAll(c); }
|
||||
public boolean retainAll(Collection<?> c) { return q.retainAll(c); }
|
||||
// We use inherited addAll; forwarding addAll would be wrong
|
||||
|
||||
// Override default methods in Collection
|
||||
|
@ -83,6 +83,16 @@ class ImmutableCollections {
|
||||
|
||||
// ---------- List Implementations ----------
|
||||
|
||||
// make a copy, short-circuiting based on implementation class
|
||||
@SuppressWarnings("unchecked")
|
||||
static <E> List<E> listCopy(Collection<? extends E> coll) {
|
||||
if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) {
|
||||
return (List<E>)coll;
|
||||
} else {
|
||||
return (List<E>)List.of(coll.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <E> List<E> emptyList() {
|
||||
return (List<E>) ListN.EMPTY_LIST;
|
||||
|
@ -1057,12 +1057,7 @@ public interface List<E> extends Collection<E> {
|
||||
* @throws NullPointerException if coll is null, or if it contains any nulls
|
||||
* @since 10
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <E> List<E> copyOf(Collection<? extends E> coll) {
|
||||
if (coll instanceof ImmutableCollections.AbstractImmutableList) {
|
||||
return (List<E>)coll;
|
||||
} else {
|
||||
return (List<E>)List.of(coll.toArray());
|
||||
}
|
||||
return ImmutableCollections.listCopy(coll);
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jdk.internal.misc.SharedSecrets;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.util.xml.PropertiesDefaultHandler;
|
||||
|
||||
/**
|
||||
@ -140,7 +141,9 @@ class Properties extends Hashtable<Object,Object> {
|
||||
/**
|
||||
* use serialVersionUID from JDK 1.1.X for interoperability
|
||||
*/
|
||||
private static final long serialVersionUID = 4112578634029874840L;
|
||||
private static final long serialVersionUID = 4112578634029874840L;
|
||||
|
||||
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
/**
|
||||
* A property list that contains default values for any keys not
|
||||
@ -148,7 +151,7 @@ class Properties extends Hashtable<Object,Object> {
|
||||
*
|
||||
* @serial
|
||||
*/
|
||||
protected Properties defaults;
|
||||
protected volatile Properties defaults;
|
||||
|
||||
/**
|
||||
* Properties does not store values in its inherited Hashtable, but instead
|
||||
@ -156,7 +159,7 @@ class Properties extends Hashtable<Object,Object> {
|
||||
* simple read operations. Writes and bulk operations remain synchronized,
|
||||
* as in Hashtable.
|
||||
*/
|
||||
private transient ConcurrentHashMap<Object, Object> map;
|
||||
private transient volatile ConcurrentHashMap<Object, Object> map;
|
||||
|
||||
/**
|
||||
* Creates an empty property list with no default values.
|
||||
@ -200,6 +203,9 @@ class Properties extends Hashtable<Object,Object> {
|
||||
super((Void) null);
|
||||
map = new ConcurrentHashMap<>(initialCapacity);
|
||||
this.defaults = defaults;
|
||||
|
||||
// Ensure writes can't be reordered
|
||||
UNSAFE.storeFence();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1097,7 +1103,8 @@ class Properties extends Hashtable<Object,Object> {
|
||||
public String getProperty(String key) {
|
||||
Object oval = map.get(key);
|
||||
String sval = (oval instanceof String) ? (String)oval : null;
|
||||
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
|
||||
Properties defaults;
|
||||
return ((sval == null) && ((defaults = this.defaults) != null)) ? defaults.getProperty(key) : sval;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1483,6 +1490,7 @@ class Properties extends Hashtable<Object,Object> {
|
||||
|
||||
@Override
|
||||
void writeHashtable(ObjectOutputStream s) throws IOException {
|
||||
var map = this.map;
|
||||
List<Object> entryStack = new ArrayList<>(map.size() * 2); // an estimate
|
||||
|
||||
for (Map.Entry<Object, Object> entry : map.entrySet()) {
|
||||
@ -1537,7 +1545,7 @@ class Properties extends Hashtable<Object,Object> {
|
||||
.checkArray(s, Map.Entry[].class, HashMap.tableSizeFor((int)(elements / 0.75)));
|
||||
|
||||
// create CHM of appropriate capacity
|
||||
map = new ConcurrentHashMap<>(elements);
|
||||
var map = new ConcurrentHashMap<>(elements);
|
||||
|
||||
// Read all the key/value objects
|
||||
for (; elements > 0; elements--) {
|
||||
@ -1545,5 +1553,6 @@ class Properties extends Hashtable<Object,Object> {
|
||||
Object value = s.readObject();
|
||||
map.put(key, value);
|
||||
}
|
||||
this.map = map;
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,9 @@ public class RSAKeyFactory extends KeyFactorySpi {
|
||||
* Used by RSASignature and RSACipher.
|
||||
*/
|
||||
public static RSAKey toRSAKey(Key key) throws InvalidKeyException {
|
||||
if (key == null) {
|
||||
throw new InvalidKeyException("Key must not be null");
|
||||
}
|
||||
if ((key instanceof RSAPrivateKeyImpl) ||
|
||||
(key instanceof RSAPrivateCrtKeyImpl) ||
|
||||
(key instanceof RSAPublicKeyImpl)) {
|
||||
|
@ -57,7 +57,7 @@ import jdk.internal.net.http.HttpClientBuilderImpl;
|
||||
* and can be used to send multiple requests.
|
||||
*
|
||||
* <p> An {@code HttpClient} provides configuration information, and resource
|
||||
* sharing, for all requests send through it.
|
||||
* sharing, for all requests sent through it.
|
||||
*
|
||||
* <p> A {@link BodyHandler BodyHandler} must be supplied for each {@link
|
||||
* HttpRequest} sent. The {@code BodyHandler} determines how to handle the
|
||||
@ -232,11 +232,10 @@ public abstract class HttpClient {
|
||||
*
|
||||
* <p> If this method is not invoked prior to {@linkplain #build()
|
||||
* building}, a default executor is created for each newly built {@code
|
||||
* HttpClient}. The default executor uses a {@linkplain
|
||||
* Executors#newCachedThreadPool(ThreadFactory) cached thread pool},
|
||||
* with a custom thread factory.
|
||||
* HttpClient}.
|
||||
*
|
||||
* @implNote If a security manager has been installed, the thread
|
||||
* @implNote The default executor uses a thread pool, with a custom
|
||||
* thread factory. If a security manager has been installed, the thread
|
||||
* factory creates threads that run with an access control context that
|
||||
* has no permissions.
|
||||
*
|
||||
@ -451,7 +450,7 @@ public abstract class HttpClient {
|
||||
* then the response, containing the {@code 3XX} response code, is returned,
|
||||
* where it can be handled manually.
|
||||
*
|
||||
* <p> {@code Redirect} policy is set via the {@linkplain
|
||||
* <p> {@code Redirect} policy is set through the {@linkplain
|
||||
* HttpClient.Builder#followRedirects(Redirect) Builder.followRedirects}
|
||||
* method.
|
||||
*
|
||||
|
@ -25,62 +25,68 @@
|
||||
|
||||
package java.net.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiPredicate;
|
||||
import static java.lang.String.CASE_INSENSITIVE_ORDER;
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* A read-only view of a set of HTTP headers.
|
||||
*
|
||||
* <p> An {@code HttpHeaders} is not created directly, but rather returned from
|
||||
* an {@link HttpResponse HttpResponse}. Specific HTTP headers can be set for
|
||||
* {@linkplain HttpRequest requests} through the one of the request builder's
|
||||
* {@link HttpRequest.Builder#header(String, String) headers} methods.
|
||||
* <p> An {@code HttpHeaders} is not typically created directly, but rather
|
||||
* returned from an {@link HttpRequest#headers() HttpRequest} or an
|
||||
* {@link HttpResponse#headers() HttpResponse}. Specific HTTP headers can be
|
||||
* set for a {@linkplain HttpRequest request} through one of the request
|
||||
* builder's {@link HttpRequest.Builder#header(String, String) headers} methods.
|
||||
*
|
||||
* <p> The methods of this class ( that accept a String header name ), and the
|
||||
* Map returned by the {@link #map() map} method, operate without regard to
|
||||
* case when retrieving the header value.
|
||||
* {@code Map} returned by the {@link #map() map} method, operate without regard
|
||||
* to case when retrieving the header value(s).
|
||||
*
|
||||
* <p> An HTTP header name may appear more than once in the HTTP protocol. As
|
||||
* such, headers are represented as a name and a list of values. Each occurrence
|
||||
* of a header value is added verbatim, to the appropriate header name list,
|
||||
* without interpreting its value. In particular, {@code HttpHeaders} does not
|
||||
* perform any splitting or joining of comma separated header value strings. The
|
||||
* order of elements in a header value list is preserved when {@link
|
||||
* HttpRequest.Builder#header(String, String) building} a request. For
|
||||
* responses, the order of elements in a header value list is the order in which
|
||||
* they were received. The {@code Map} returned by the {@code map} method,
|
||||
* however, does not provide any guarantee with regard to the ordering of its
|
||||
* entries.
|
||||
*
|
||||
* <p> {@code HttpHeaders} instances are immutable.
|
||||
*
|
||||
* @since 11
|
||||
*/
|
||||
public abstract class HttpHeaders {
|
||||
public final class HttpHeaders {
|
||||
|
||||
/**
|
||||
* Creates an HttpHeaders.
|
||||
*/
|
||||
protected HttpHeaders() {}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} containing the first value of the given named
|
||||
* (and possibly multi-valued) header. If the header is not present, then
|
||||
* the returned {@code Optional} is empty.
|
||||
*
|
||||
* @implSpec
|
||||
* The default implementation invokes
|
||||
* {@code allValues(name).stream().findFirst()}
|
||||
* Returns an {@link Optional} containing the first header string value of
|
||||
* the given named (and possibly multi-valued) header. If the header is not
|
||||
* present, then the returned {@code Optional} is empty.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return an {@code Optional<String>} for the first named value
|
||||
* @return an {@code Optional<String>} containing the first named header
|
||||
* string value, if present
|
||||
*/
|
||||
public Optional<String> firstValue(String name) {
|
||||
return allValues(name).stream().findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link OptionalLong} containing the first value of the
|
||||
* named header field. If the header is not present, then the Optional is
|
||||
* empty. If the header is present but contains a value that does not parse
|
||||
* as a {@code Long} value, then an exception is thrown.
|
||||
*
|
||||
* @implSpec
|
||||
* The default implementation invokes
|
||||
* {@code allValues(name).stream().mapToLong(Long::valueOf).findFirst()}
|
||||
* Returns an {@link OptionalLong} containing the first header string value
|
||||
* of the named header field. If the header is not present, then the
|
||||
* Optional is empty. If the header is present but contains a value that
|
||||
* does not parse as a {@code Long} value, then an exception is thrown.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return an {@code OptionalLong}
|
||||
@ -92,23 +98,19 @@ public abstract class HttpHeaders {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable List of all of the values of the given named
|
||||
* header. Always returns a List, which may be empty if the header is not
|
||||
* present.
|
||||
*
|
||||
* @implSpec
|
||||
* The default implementation invokes, among other things, the
|
||||
* {@code map().get(name)} to retrieve the list of header values.
|
||||
* Returns an unmodifiable List of all of the header string values of the
|
||||
* given named header. Always returns a List, which may be empty if the
|
||||
* header is not present.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return a List of String values
|
||||
* @return a List of headers string values
|
||||
*/
|
||||
public List<String> allValues(String name) {
|
||||
requireNonNull(name);
|
||||
List<String> values = map().get(name);
|
||||
// Making unmodifiable list out of empty in order to make a list which
|
||||
// throws UOE unconditionally
|
||||
return values != null ? values : unmodifiableList(emptyList());
|
||||
return values != null ? values : List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,7 +118,9 @@ public abstract class HttpHeaders {
|
||||
*
|
||||
* @return the Map
|
||||
*/
|
||||
public abstract Map<String, List<String>> map();
|
||||
public Map<String,List<String>> map() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests this HTTP headers instance for equality with the given object.
|
||||
@ -149,7 +153,11 @@ public abstract class HttpHeaders {
|
||||
* @return the hash-code value for this HTTP headers
|
||||
*/
|
||||
public final int hashCode() {
|
||||
return map().hashCode();
|
||||
int h = 0;
|
||||
for (Map.Entry<String, List<String>> e : map().entrySet()) {
|
||||
h += entryHash(e);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,4 +173,93 @@ public abstract class HttpHeaders {
|
||||
sb.append(" }");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTTP headers from the given map. The given map's key
|
||||
* represents the header name, and its value the list of string header
|
||||
* values for that header name.
|
||||
*
|
||||
* <p> An HTTP header name may appear more than once in the HTTP protocol.
|
||||
* Such, <i>multi-valued</i>, headers must be represented by a single entry
|
||||
* in the given map, whose entry value is a list that represents the
|
||||
* multiple header string values. Leading and trailing whitespaces are
|
||||
* removed from all string values retrieved from the given map and its lists
|
||||
* before processing. Only headers that, after filtering, contain at least
|
||||
* one, possibly empty string, value will be added to the HTTP headers.
|
||||
*
|
||||
* @apiNote The primary purpose of this method is for testing frameworks.
|
||||
* Per-request headers can be set through one of the {@code HttpRequest}
|
||||
* {@link HttpRequest.Builder#header(String, String) headers} methods.
|
||||
*
|
||||
* @param headerMap the map containing the header names and values
|
||||
* @param filter a filter that can be used to inspect each
|
||||
* header-name-and-value pair in the given map to determine if
|
||||
* it should, or should not, be added to the to the HTTP
|
||||
* headers
|
||||
* @return an HTTP headers instance containing the given headers
|
||||
* @throws NullPointerException if any of: {@code headerMap}, a key or value
|
||||
* in the given map, or an entry in the map's value list, or
|
||||
* {@code filter}, is {@code null}
|
||||
* @throws IllegalArgumentException if the given {@code headerMap} contains
|
||||
* any two keys that are equal ( without regard to case ); or if the
|
||||
* given map contains any key whose length, after trimming
|
||||
* whitespaces, is {@code 0}
|
||||
*/
|
||||
public static HttpHeaders of(Map<String,List<String>> headerMap,
|
||||
BiPredicate<String,String> filter) {
|
||||
requireNonNull(headerMap);
|
||||
requireNonNull(filter);
|
||||
return headersOf(headerMap, filter);
|
||||
}
|
||||
|
||||
// --
|
||||
|
||||
private static final HttpHeaders NO_HEADERS = new HttpHeaders(Map.of());
|
||||
|
||||
private final Map<String,List<String>> headers;
|
||||
|
||||
private HttpHeaders(Map<String,List<String>> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
private static final int entryHash(Map.Entry<String, List<String>> e) {
|
||||
String key = e.getKey();
|
||||
List<String> value = e.getValue();
|
||||
// we know that by construction key and values can't be null
|
||||
int keyHash = key.toLowerCase(Locale.ROOT).hashCode();
|
||||
int valueHash = value.hashCode();
|
||||
return keyHash ^ valueHash;
|
||||
}
|
||||
|
||||
// Returns a new HTTP headers after performing a structural copy and filtering.
|
||||
private static HttpHeaders headersOf(Map<String,List<String>> map,
|
||||
BiPredicate<String,String> filter) {
|
||||
TreeMap<String,List<String>> other = new TreeMap<>(CASE_INSENSITIVE_ORDER);
|
||||
TreeSet<String> notAdded = new TreeSet<>(CASE_INSENSITIVE_ORDER);
|
||||
ArrayList<String> tempList = new ArrayList<>();
|
||||
map.forEach((key, value) -> {
|
||||
String headerName = requireNonNull(key).trim();
|
||||
if (headerName.isEmpty()) {
|
||||
throw new IllegalArgumentException("empty key");
|
||||
}
|
||||
List<String> headerValues = requireNonNull(value);
|
||||
headerValues.forEach(headerValue -> {
|
||||
headerValue = requireNonNull(headerValue).trim();
|
||||
if (filter.test(headerName, headerValue)) {
|
||||
tempList.add(headerValue);
|
||||
}
|
||||
});
|
||||
|
||||
if (tempList.isEmpty()) {
|
||||
if (other.containsKey(headerName)
|
||||
|| notAdded.contains(headerName.toLowerCase(Locale.ROOT)))
|
||||
throw new IllegalArgumentException("duplicate key: " + headerName);
|
||||
notAdded.add(headerName.toLowerCase(Locale.ROOT));
|
||||
} else if (other.put(headerName, List.copyOf(tempList)) != null) {
|
||||
throw new IllegalArgumentException("duplicate key: " + headerName);
|
||||
}
|
||||
tempList.clear();
|
||||
});
|
||||
return other.isEmpty() ? NO_HEADERS : new HttpHeaders(unmodifiableMap(other));
|
||||
}
|
||||
}
|
||||
|
@ -89,10 +89,12 @@ public abstract class HttpRequest {
|
||||
* <p> Instances of {@code HttpRequest.Builder} are created by calling {@link
|
||||
* HttpRequest#newBuilder(URI)} or {@link HttpRequest#newBuilder()}.
|
||||
*
|
||||
* <p> Each of the setter methods modifies the state of the builder
|
||||
* and returns the same instance. The methods are not synchronized and
|
||||
* should not be called from multiple threads without external
|
||||
* synchronization. The {@link #build() build} method returns a new
|
||||
* <p> The builder can be used to configure per-request state, such as: the
|
||||
* request URI, the request method (default is GET unless explicitly set),
|
||||
* specific request headers, etc. Each of the setter methods modifies the
|
||||
* state of the builder and returns the same instance. The methods are not
|
||||
* synchronized and should not be called from multiple threads without
|
||||
* external synchronization. The {@link #build() build} method returns a new
|
||||
* {@code HttpRequest} each time it is invoked. Once built an {@code
|
||||
* HttpRequest} is immutable, and can be sent multiple times.
|
||||
*
|
||||
|
@ -830,13 +830,13 @@ public interface HttpResponse<T> {
|
||||
* BodySubscriber} provides implementations of many common body subscribers.
|
||||
*
|
||||
* <p> The object acts as a {@link Flow.Subscriber}<{@link List}<{@link
|
||||
* ByteBuffer}>> to the HTTP client implementation, which publishes
|
||||
* unmodifiable lists of read-only ByteBuffers containing the response body.
|
||||
* The Flow of data, as well as the order of ByteBuffers in the Flow lists,
|
||||
* is a strictly ordered representation of the response body. Both the Lists
|
||||
* and the ByteBuffers, once passed to the subscriber, are no longer used by
|
||||
* the HTTP client. The subscriber converts the incoming buffers of data to
|
||||
* some higher-level Java type {@code T}.
|
||||
* ByteBuffer}>> to the HTTP Client implementation, which publishes
|
||||
* lists of ByteBuffers containing the response body. The Flow of data, as
|
||||
* well as the order of ByteBuffers in the Flow lists, is a strictly ordered
|
||||
* representation of the response body. Both the Lists and the ByteBuffers,
|
||||
* once passed to the subscriber, are no longer used by the HTTP Client. The
|
||||
* subscriber converts the incoming buffers of data to some higher-level
|
||||
* Java type {@code T}.
|
||||
*
|
||||
* <p> The {@link #getBody()} method returns a
|
||||
* {@link CompletionStage}<{@code T}> that provides the response body
|
||||
@ -859,6 +859,9 @@ public interface HttpResponse<T> {
|
||||
* may cause the underlying HTTP connection to be closed and prevent it
|
||||
* from being reused for subsequent operations.
|
||||
*
|
||||
* @implNote The flow of data containing the response body is immutable.
|
||||
* Specifically, it is a flow of unmodifiable lists of read-only ByteBuffers.
|
||||
*
|
||||
* @param <T> the response body type
|
||||
* @see BodySubscribers
|
||||
* @since 11
|
||||
@ -888,20 +891,20 @@ public interface HttpResponse<T> {
|
||||
*
|
||||
* <pre>{@code // Streams the response body to a File
|
||||
* HttpResponse<byte[]> response = client
|
||||
* .send(request, (statusCode, responseHeaders) -> BodySubscribers.ofByteArray());
|
||||
* .send(request, responseInfo -> BodySubscribers.ofByteArray());
|
||||
*
|
||||
* // Accumulates the response body and returns it as a byte[]
|
||||
* HttpResponse<byte[]> response = client
|
||||
* .send(request, (statusCode, responseHeaders) -> BodySubscribers.ofByteArray());
|
||||
* .send(request, responseInfo -> BodySubscribers.ofByteArray());
|
||||
*
|
||||
* // Discards the response body
|
||||
* HttpResponse<Void> response = client
|
||||
* .send(request, (statusCode, responseHeaders) -> BodySubscribers.discarding());
|
||||
* .send(request, responseInfo -> BodySubscribers.discarding());
|
||||
*
|
||||
* // Accumulates the response body as a String then maps it to its bytes
|
||||
* HttpResponse<byte[]> response = client
|
||||
* .send(request, (sc, hdrs) ->
|
||||
* BodySubscribers.mapping(BodySubscribers.ofString(), String::getBytes));
|
||||
* .send(request, responseInfo ->
|
||||
* BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), String::getBytes));
|
||||
* }</pre>
|
||||
*
|
||||
* @since 11
|
||||
|
@ -35,9 +35,9 @@ import java.util.concurrent.CompletionStage;
|
||||
/**
|
||||
* A WebSocket Client.
|
||||
*
|
||||
* <p> {@code WebSocket} instances can be created via {@link WebSocket.Builder}.
|
||||
* <p> {@code WebSocket} instances are created through {@link WebSocket.Builder}.
|
||||
*
|
||||
* <p> WebSocket has an input and an output sides. These sides are independent
|
||||
* <p> WebSocket has an input and an output side. These sides are independent
|
||||
* from each other. A side can either be open or closed. Once closed, the side
|
||||
* remains closed. WebSocket messages are sent through a {@code WebSocket} and
|
||||
* received through a {@code WebSocket.Listener} associated with it. Messages
|
||||
@ -55,21 +55,22 @@ import java.util.concurrent.CompletionStage;
|
||||
*
|
||||
* <p> A receive method is any of the {@code onText}, {@code onBinary},
|
||||
* {@code onPing}, {@code onPong} and {@code onClose} methods of
|
||||
* {@code Listener}. A receive method initiates a receive operation and returns
|
||||
* a {@code CompletionStage} which completes once the operation has completed.
|
||||
* {@code Listener}. WebSocket initiates a receive operation by invoking a
|
||||
* receive method on the listener. The listener then must return a
|
||||
* {@code CompletionStage} which completes once the operation has completed.
|
||||
*
|
||||
* <p> A WebSocket maintains an <a id="counter">internal counter</a>.
|
||||
* This counter's value is a number of times the WebSocket has yet to invoke a
|
||||
* receive method. While this counter is zero the WebSocket does not invoke
|
||||
* receive methods. The counter is incremented by {@code n} when {@code
|
||||
* request(n)} is called. The counter is decremented by one when the WebSocket
|
||||
* invokes a receive method. {@code onOpen} and {@code onError} are not receive
|
||||
* methods. WebSocket invokes {@code onOpen} prior to any other methods on the
|
||||
* listener. WebSocket invokes {@code onOpen} at most once. WebSocket may invoke
|
||||
* {@code onError} at any given time. If the WebSocket invokes {@code onError}
|
||||
* or {@code onClose}, then no further listener's methods will be invoked, no
|
||||
* matter the value of the counter. For a newly built WebSocket the counter is
|
||||
* zero. A WebSocket invokes methods on the listener in a thread-safe manner.
|
||||
* <p> To control receiving of messages, a WebSocket maintains an
|
||||
* <a id="counter">internal counter</a>. This counter's value is a number of
|
||||
* times the WebSocket has yet to invoke a receive method. While this counter is
|
||||
* zero the WebSocket does not invoke receive methods. The counter is
|
||||
* incremented by {@code n} when {@code request(n)} is called. The counter is
|
||||
* decremented by one when the WebSocket invokes a receive method.
|
||||
* {@code onOpen} and {@code onError} are not receive methods. WebSocket invokes
|
||||
* {@code onOpen} prior to any other methods on the listener. WebSocket invokes
|
||||
* {@code onOpen} at most once. WebSocket may invoke {@code onError} at any
|
||||
* given time. If the WebSocket invokes {@code onError} or {@code onClose}, then
|
||||
* no further listener's methods will be invoked, no matter the value of the
|
||||
* counter. For a newly built WebSocket the counter is zero.
|
||||
*
|
||||
* <p> Unless otherwise stated, {@code null} arguments will cause methods
|
||||
* of {@code WebSocket} to throw {@code NullPointerException}, similarly,
|
||||
@ -105,13 +106,13 @@ public interface WebSocket {
|
||||
/**
|
||||
* A builder of {@linkplain WebSocket WebSocket Clients}.
|
||||
*
|
||||
* <p> A builder can be created by invoking the
|
||||
* {@link HttpClient#newWebSocketBuilder HttpClient.newWebSocketBuilder}
|
||||
* method. The intermediate (setter-like) methods change the state of the
|
||||
* builder and return the same builder they have been invoked on. If an
|
||||
* intermediate method is not invoked, an appropriate default value (or
|
||||
* behavior) will be assumed. A {@code Builder} is not safe for use by
|
||||
* multiple threads without external synchronization.
|
||||
* <p> Builders are created by invoking
|
||||
* {@link HttpClient#newWebSocketBuilder HttpClient.newWebSocketBuilder}.
|
||||
* The intermediate (setter-like) methods change the state of the builder
|
||||
* and return the same builder they have been invoked on. If an intermediate
|
||||
* method is not invoked, an appropriate default value (or behavior) will be
|
||||
* assumed. A {@code Builder} is not safe for use by multiple threads
|
||||
* without external synchronization.
|
||||
*
|
||||
* @since 11
|
||||
*/
|
||||
@ -155,7 +156,7 @@ public interface WebSocket {
|
||||
* Sets a request for the given subprotocols.
|
||||
*
|
||||
* <p> After the {@code WebSocket} has been built, the actual
|
||||
* subprotocol can be queried via
|
||||
* subprotocol can be queried through
|
||||
* {@link WebSocket#getSubprotocol WebSocket.getSubprotocol()}.
|
||||
*
|
||||
* <p> Subprotocols are specified in the order of preference. The most
|
||||
@ -218,11 +219,17 @@ public interface WebSocket {
|
||||
* The receiving interface of {@code WebSocket}.
|
||||
*
|
||||
* <p> A {@code WebSocket} invokes methods of the associated listener
|
||||
* passing itself as an argument. When data has been received, the
|
||||
* {@code WebSocket} invokes a receive method. Methods {@code onText},
|
||||
* {@code onBinary}, {@code onPing} and {@code onPong} must return a
|
||||
* {@code CompletionStage} that completes once the message has been received
|
||||
* by the listener.
|
||||
* passing itself as an argument. These methods are invoked in a thread-safe
|
||||
* manner, such that the next invocation may start only after the previous
|
||||
* one has finished.
|
||||
*
|
||||
* <p> When data has been received, the {@code WebSocket} invokes a receive
|
||||
* method. Methods {@code onText}, {@code onBinary}, {@code onPing} and
|
||||
* {@code onPong} must return a {@code CompletionStage} that completes once
|
||||
* the message has been received by the listener. If a listener's method
|
||||
* returns {@code null} rather than a {@code CompletionStage},
|
||||
* {@code WebSocket} will behave as if the listener returned a
|
||||
* {@code CompletionStage} that is already completed normally.
|
||||
*
|
||||
* <p> An {@code IOException} raised in {@code WebSocket} will result in an
|
||||
* invocation of {@code onError} with that exception (if the input is not
|
||||
@ -231,11 +238,14 @@ public interface WebSocket {
|
||||
* exceptionally, the WebSocket will invoke {@code onError} with this
|
||||
* exception.
|
||||
*
|
||||
* <p> If a listener's method returns {@code null} rather than a
|
||||
* {@code CompletionStage}, {@code WebSocket} will behave as if the listener
|
||||
* returned a {@code CompletionStage} that is already completed normally.
|
||||
* @apiNote The strict sequential order of invocations from
|
||||
* {@code WebSocket} to {@code Listener} means, in particular, that the
|
||||
* {@code Listener}'s methods are treated as non-reentrant. This means that
|
||||
* {@code Listener} implementations do not need to be concerned with
|
||||
* possible recursion or the order in which they invoke
|
||||
* {@code WebSocket.request} in relation to their processing logic.
|
||||
*
|
||||
* @apiNote Careful attention may be required if a listener is associated
|
||||
* <p> Careful attention may be required if a listener is associated
|
||||
* with more than a single {@code WebSocket}. In this case invocations
|
||||
* related to different instances of {@code WebSocket} may not be ordered
|
||||
* and may even happen concurrently.
|
||||
|
@ -28,7 +28,7 @@ package java.net.http;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An exception used to signal the opening handshake failed.
|
||||
* Thrown when the opening handshake has failed.
|
||||
*
|
||||
* @since 11
|
||||
*/
|
||||
@ -55,6 +55,10 @@ public final class WebSocketHandshakeException extends IOException {
|
||||
* <p> The value may be unavailable ({@code null}) if this exception has
|
||||
* been serialized and then deserialized.
|
||||
*
|
||||
* @apiNote The primary purpose of this method is to allow programmatic
|
||||
* examination of the reasons behind the failure of the opening handshake.
|
||||
* Some of these reasons might allow recovery.
|
||||
*
|
||||
* @return server response
|
||||
*/
|
||||
public HttpResponse<?> getResponse() {
|
||||
|
@ -42,20 +42,24 @@
|
||||
* Hypertext Transfer Protocol (HTTP/1.1)</a>, and
|
||||
* <a href="https://tools.ietf.org/html/rfc6455">The WebSocket Protocol</a>.
|
||||
*
|
||||
* <p> Asynchronous tasks and dependent actions of returned {@link
|
||||
* java.util.concurrent.CompletableFuture} instances are executed on the threads
|
||||
* supplied by the client's {@link java.util.concurrent.Executor}, where
|
||||
* practical.
|
||||
* <p> In general, asynchronous tasks execute in either the thread invoking
|
||||
* the operation, e.g. {@linkplain HttpClient#send(HttpRequest, BodyHandler)
|
||||
* sending} an HTTP request, or by the threads supplied by the client's {@link
|
||||
* HttpClient#executor() executor}. Dependent tasks, those that are triggered by
|
||||
* returned CompletionStages or CompletableFutures, that do not explicitly
|
||||
* specify an executor, execute in the same {@link
|
||||
* CompletableFuture#defaultExecutor() default executor} as that of {@code
|
||||
* CompletableFuture}, or the invoking thread if the operation completes before
|
||||
* the dependent task is registered.
|
||||
*
|
||||
* <p> {@code CompletableFuture}s returned by this API will throw {@link
|
||||
* java.lang.UnsupportedOperationException} for their {@link
|
||||
* java.util.concurrent.CompletableFuture#obtrudeValue(Object) obtrudeValue}
|
||||
* and {@link java.util.concurrent.CompletableFuture#obtrudeException(Throwable)
|
||||
* obtrudeException} methods. Invoking the {@link
|
||||
* java.util.concurrent.CompletableFuture#cancel cancel} method on a {@code
|
||||
* CompletableFuture} returned by this API will not interrupt the underlying
|
||||
* operation, but may be useful to complete, exceptionally, dependent stages
|
||||
* that have not already completed.
|
||||
* UnsupportedOperationException} for their {@link
|
||||
* CompletableFuture#obtrudeValue(Object) obtrudeValue}
|
||||
* and {@link CompletableFuture#obtrudeException(Throwable)
|
||||
* obtrudeException} methods. Invoking the {@link CompletableFuture#cancel
|
||||
* cancel} method on a {@code CompletableFuture} returned by this API may not
|
||||
* interrupt the underlying operation, but may be useful to complete,
|
||||
* exceptionally, dependent stages that have not already completed.
|
||||
*
|
||||
* <p> Unless otherwise stated, {@code null} parameter values will cause methods
|
||||
* of all classes in this package to throw {@code NullPointerException}.
|
||||
@ -63,3 +67,9 @@
|
||||
* @since 11
|
||||
*/
|
||||
package java.net.http;
|
||||
|
||||
import java.lang.UnsupportedOperationException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse.BodyHandler;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -34,7 +34,6 @@ import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.WeakHashMap;
|
||||
import java.net.http.HttpHeaders;
|
||||
@ -59,9 +58,9 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
static final int UNAUTHORIZED = 401;
|
||||
static final int PROXY_UNAUTHORIZED = 407;
|
||||
|
||||
private static final List<String> BASIC_DUMMY =
|
||||
List.of("Basic " + Base64.getEncoder()
|
||||
.encodeToString("o:o".getBytes(ISO_8859_1)));
|
||||
private static final String BASIC_DUMMY =
|
||||
"Basic " + Base64.getEncoder()
|
||||
.encodeToString("o:o".getBytes(ISO_8859_1));
|
||||
|
||||
// A public no-arg constructor is required by FilterFactory
|
||||
public AuthenticationFilter() {}
|
||||
@ -182,14 +181,12 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
String value = "Basic " + s;
|
||||
if (proxy) {
|
||||
if (r.isConnect()) {
|
||||
if (!Utils.PROXY_TUNNEL_FILTER
|
||||
.test(hdrname, List.of(value))) {
|
||||
if (!Utils.PROXY_TUNNEL_FILTER.test(hdrname, value)) {
|
||||
Log.logError("{0} disabled", hdrname);
|
||||
return;
|
||||
}
|
||||
} else if (r.proxy() != null) {
|
||||
if (!Utils.PROXY_FILTER
|
||||
.test(hdrname, List.of(value))) {
|
||||
if (!Utils.PROXY_FILTER.test(hdrname, value)) {
|
||||
Log.logError("{0} disabled", hdrname);
|
||||
return;
|
||||
}
|
||||
@ -261,9 +258,16 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
|
||||
boolean proxy = status == PROXY_UNAUTHORIZED;
|
||||
String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
||||
String authval = hdrs.firstValue(authname).orElseThrow(() -> {
|
||||
return new IOException("Invalid auth header");
|
||||
});
|
||||
String authval = hdrs.firstValue(authname).orElse(null);
|
||||
if (authval == null) {
|
||||
if (exchange.client().authenticator().isPresent()) {
|
||||
throw new IOException(authname + " header missing for response code " + status);
|
||||
} else {
|
||||
// No authenticator? let the caller deal with this.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
HeaderParser parser = new HeaderParser(authval);
|
||||
String scheme = parser.findKey(0);
|
||||
|
||||
|
@ -53,6 +53,8 @@ final class ConnectionPool {
|
||||
|
||||
static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
|
||||
"jdk.httpclient.keepalive.timeout", 1200); // seconds
|
||||
static final long MAX_POOL_SIZE = Utils.getIntegerNetProperty(
|
||||
"jdk.httpclient.connectionPoolSize", 0); // unbounded
|
||||
final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
|
||||
|
||||
// Pools of idle connections
|
||||
@ -160,6 +162,7 @@ final class ConnectionPool {
|
||||
CleanupTrigger cleanup = registerCleanupTrigger(conn);
|
||||
|
||||
// it's possible that cleanup may have been called.
|
||||
HttpConnection toClose = null;
|
||||
synchronized(this) {
|
||||
if (cleanup.isDone()) {
|
||||
return;
|
||||
@ -167,6 +170,10 @@ final class ConnectionPool {
|
||||
conn.close();
|
||||
return;
|
||||
}
|
||||
if (MAX_POOL_SIZE > 0 && expiryList.size() >= MAX_POOL_SIZE) {
|
||||
toClose = expiryList.removeOldest();
|
||||
if (toClose != null) removeFromPool(toClose);
|
||||
}
|
||||
if (conn instanceof PlainHttpConnection) {
|
||||
putConnection(conn, plainPool);
|
||||
} else {
|
||||
@ -175,6 +182,13 @@ final class ConnectionPool {
|
||||
}
|
||||
expiryList.add(conn, now, keepAlive);
|
||||
}
|
||||
if (toClose != null) {
|
||||
if (debug.on()) {
|
||||
debug.log("Maximum pool size reached: removing oldest connection %s",
|
||||
toClose.dbgString());
|
||||
}
|
||||
close(toClose);
|
||||
}
|
||||
//System.out.println("Return to pool: " + conn);
|
||||
}
|
||||
|
||||
@ -314,6 +328,8 @@ final class ConnectionPool {
|
||||
private final LinkedList<ExpiryEntry> list = new LinkedList<>();
|
||||
private volatile boolean mayContainEntries;
|
||||
|
||||
int size() { return list.size(); }
|
||||
|
||||
// A loosely accurate boolean whose value is computed
|
||||
// at the end of each operation performed on ExpiryList;
|
||||
// Does not require synchronizing on the ConnectionPool.
|
||||
@ -329,6 +345,13 @@ final class ConnectionPool {
|
||||
else return Optional.of(list.getLast().expiry);
|
||||
}
|
||||
|
||||
// should only be called while holding a synchronization
|
||||
// lock on the ConnectionPool
|
||||
HttpConnection removeOldest() {
|
||||
ExpiryEntry entry = list.pollLast();
|
||||
return entry == null ? null : entry.connection;
|
||||
}
|
||||
|
||||
// should only be called while holding a synchronization
|
||||
// lock on the ConnectionPool
|
||||
void add(HttpConnection conn) {
|
||||
@ -419,17 +442,38 @@ final class ConnectionPool {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a connection from the pool.
|
||||
// should only be called while holding a synchronization
|
||||
// lock on the ConnectionPool
|
||||
private void removeFromPool(HttpConnection c) {
|
||||
assert Thread.holdsLock(this);
|
||||
if (c instanceof PlainHttpConnection) {
|
||||
removeFromPool(c, plainPool);
|
||||
} else {
|
||||
assert c.isSecure();
|
||||
removeFromPool(c, sslPool);
|
||||
}
|
||||
}
|
||||
|
||||
// Used by tests
|
||||
synchronized boolean contains(HttpConnection c) {
|
||||
final CacheKey key = c.cacheKey();
|
||||
List<HttpConnection> list;
|
||||
if ((list = plainPool.get(key)) != null) {
|
||||
if (list.contains(c)) return true;
|
||||
}
|
||||
if ((list = sslPool.get(key)) != null) {
|
||||
if (list.contains(c)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cleanup(HttpConnection c, Throwable error) {
|
||||
if (debug.on())
|
||||
debug.log("%s : ConnectionPool.cleanup(%s)",
|
||||
String.valueOf(c.getConnectionFlow()), error);
|
||||
synchronized(this) {
|
||||
if (c instanceof PlainHttpConnection) {
|
||||
removeFromPool(c, plainPool);
|
||||
} else {
|
||||
assert c.isSecure();
|
||||
removeFromPool(c, sslPool);
|
||||
}
|
||||
removeFromPool(c);
|
||||
expiryList.remove(c);
|
||||
}
|
||||
c.close();
|
||||
|
@ -31,7 +31,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.net.http.HttpHeaders;
|
||||
import jdk.internal.net.http.common.HttpHeadersImpl;
|
||||
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||
import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
|
||||
@ -50,7 +50,7 @@ class CookieFilter implements HeaderFilter {
|
||||
Map<String,List<String>> cookies = cookieHandler.get(r.uri(), userheaders);
|
||||
|
||||
// add the returned cookies
|
||||
HttpHeadersImpl systemHeaders = r.getSystemHeaders();
|
||||
HttpHeadersBuilder systemHeadersBuilder = r.getSystemHeadersBuilder();
|
||||
if (cookies.isEmpty()) {
|
||||
Log.logTrace("Request: no cookie to add for {0}", r.uri());
|
||||
} else {
|
||||
@ -65,7 +65,7 @@ class CookieFilter implements HeaderFilter {
|
||||
if (values == null || values.isEmpty()) continue;
|
||||
for (String val : values) {
|
||||
if (Utils.isValidValue(val)) {
|
||||
systemHeaders.addHeader(hdrname, val);
|
||||
systemHeadersBuilder.addHeader(hdrname, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +307,7 @@ final class Exchange<T> {
|
||||
Function<ExchangeImpl<T>,CompletableFuture<Response>> andThen) {
|
||||
t = Utils.getCompletionCause(t);
|
||||
if (t instanceof ProxyAuthenticationRequired) {
|
||||
if (debug.on()) debug.log("checkFor407: ProxyAuthenticationRequired: building synthetic response");
|
||||
bodyIgnored = MinimalFuture.completedFuture(null);
|
||||
Response proxyResponse = ((ProxyAuthenticationRequired)t).proxyResponse;
|
||||
HttpConnection c = ex == null ? null : ex.connection();
|
||||
@ -315,8 +316,10 @@ final class Exchange<T> {
|
||||
proxyResponse.version, true);
|
||||
return MinimalFuture.completedFuture(syntheticResponse);
|
||||
} else if (t != null) {
|
||||
if (debug.on()) debug.log("checkFor407: no response - %s", t);
|
||||
return MinimalFuture.failedFuture(t);
|
||||
} else {
|
||||
if (debug.on()) debug.log("checkFor407: all clear");
|
||||
return andThen.apply(ex);
|
||||
}
|
||||
}
|
||||
@ -332,6 +335,7 @@ final class Exchange<T> {
|
||||
int rcode = r1.statusCode();
|
||||
if (rcode == 100) {
|
||||
Log.logTrace("Received 100-Continue: sending body");
|
||||
if (debug.on()) debug.log("Received 100-Continue for %s", r1);
|
||||
CompletableFuture<Response> cf =
|
||||
exchImpl.sendBodyAsync()
|
||||
.thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
||||
@ -341,6 +345,7 @@ final class Exchange<T> {
|
||||
} else {
|
||||
Log.logTrace("Expectation failed: Received {0}",
|
||||
rcode);
|
||||
if (debug.on()) debug.log("Expect-Continue failed (%d) for: %s", rcode, r1);
|
||||
if (upgrading && rcode == 101) {
|
||||
IOException failed = new IOException(
|
||||
"Unable to handle 101 while waiting for 100");
|
||||
@ -357,6 +362,7 @@ final class Exchange<T> {
|
||||
// send the request body and proceed.
|
||||
private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
|
||||
assert !request.expectContinue();
|
||||
if (debug.on()) debug.log("sendRequestBody");
|
||||
CompletableFuture<Response> cf = ex.sendBodyAsync()
|
||||
.thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
|
||||
cf = wrapForUpgrade(cf);
|
||||
|
@ -32,8 +32,8 @@ import java.util.function.Function;
|
||||
import java.net.http.HttpResponse;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.MinimalFuture;
|
||||
import static java.net.http.HttpClient.Version.HTTP_1_1;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import static java.net.http.HttpClient.Version.HTTP_1_1;
|
||||
|
||||
/**
|
||||
* Splits request so that headers and body can be sent separately with optional
|
||||
|
@ -27,7 +27,6 @@ package jdk.internal.net.http;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@ -42,7 +41,9 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import jdk.internal.net.http.common.Demand;
|
||||
import jdk.internal.net.http.common.FlowTube.TubeSubscriber;
|
||||
import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.MinimalFuture;
|
||||
import jdk.internal.net.http.common.SequentialScheduler;
|
||||
import jdk.internal.net.http.common.ConnectionExpiredException;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
@ -166,6 +167,7 @@ class Http1AsyncReceiver {
|
||||
= new ConcurrentLinkedDeque<>();
|
||||
private final SequentialScheduler scheduler =
|
||||
SequentialScheduler.synchronizedScheduler(this::flush);
|
||||
final MinimalFuture<Void> whenFinished;
|
||||
private final Executor executor;
|
||||
private final Http1TubeSubscriber subscriber = new Http1TubeSubscriber();
|
||||
private final AtomicReference<Http1AsyncDelegate> pendingDelegateRef;
|
||||
@ -184,6 +186,7 @@ class Http1AsyncReceiver {
|
||||
public Http1AsyncReceiver(Executor executor, Http1Exchange<?> owner) {
|
||||
this.pendingDelegateRef = new AtomicReference<>();
|
||||
this.executor = executor;
|
||||
this.whenFinished = new MinimalFuture<>();
|
||||
this.owner = owner;
|
||||
this.client = owner.client;
|
||||
}
|
||||
@ -261,6 +264,14 @@ class Http1AsyncReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
private String describe() {
|
||||
Http1Exchange<?> exchange = owner;
|
||||
if (exchange != null) {
|
||||
return String.valueOf(exchange.request());
|
||||
}
|
||||
return "<uri unavailable>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called from within the scheduler main loop.
|
||||
* Handles any pending errors by calling delegate.onReadError().
|
||||
@ -284,6 +295,10 @@ class Http1AsyncReceiver {
|
||||
+ "\t\t queue.isEmpty: " + queue.isEmpty());
|
||||
scheduler.stop();
|
||||
delegate.onReadError(x);
|
||||
whenFinished.completeExceptionally(x);
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("HTTP/1 read subscriber stopped for: {0}", describe());
|
||||
}
|
||||
if (stopRequested) {
|
||||
// This is the special case where the subscriber
|
||||
// has requested an illegal number of items.
|
||||
@ -464,27 +479,35 @@ class Http1AsyncReceiver {
|
||||
// throw ConnectionExpiredException
|
||||
// to try & force a retry of the request.
|
||||
retry = false;
|
||||
ex = new ConnectionExpiredException(
|
||||
"subscription is finished", ex);
|
||||
ex = new ConnectionExpiredException(ex);
|
||||
}
|
||||
}
|
||||
error = ex;
|
||||
}
|
||||
}
|
||||
final Throwable t = (recorded == null ? ex : recorded);
|
||||
if (debug.on())
|
||||
debug.log("recorded " + t + "\n\t delegate: " + delegate
|
||||
+ "\t\t queue.isEmpty: " + queue.isEmpty(), ex);
|
||||
if (Log.errors()) {
|
||||
Log.logError("HTTP/1 read subscriber recorded error: {0} - {1}", describe(), t);
|
||||
}
|
||||
if (queue.isEmpty() || pendingDelegateRef.get() != null || stopRequested) {
|
||||
// This callback is called from within the selector thread.
|
||||
// Use an executor here to avoid doing the heavy lifting in the
|
||||
// selector.
|
||||
if (Log.errors()) {
|
||||
Log.logError("HTTP/1 propagating recorded error: {0} - {1}", describe(), t);
|
||||
}
|
||||
scheduler.runOrSchedule(executor);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (debug.on()) debug.log("stopping");
|
||||
if (Log.channel() && !scheduler.isStopped()) {
|
||||
Log.logChannel("HTTP/1 read subscriber stopped for {0}", describe());
|
||||
}
|
||||
scheduler.stop();
|
||||
// make sure ref count is handled properly by
|
||||
// closing the delegate.
|
||||
@ -492,6 +515,7 @@ class Http1AsyncReceiver {
|
||||
if (previous != null) previous.close(error);
|
||||
delegate = null;
|
||||
owner = null;
|
||||
whenFinished.complete(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -514,12 +538,18 @@ class Http1AsyncReceiver {
|
||||
// supports being called multiple time.
|
||||
// doesn't cancel the previous subscription, since that is
|
||||
// most probably the same as the new subscription.
|
||||
if (debug.on()) debug.log("Received onSubscribed from upstream");
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("HTTP/1 read subscriber got subscription from {0}", describe());
|
||||
}
|
||||
assert this.subscription == null || dropped == false;
|
||||
this.subscription = subscription;
|
||||
dropped = false;
|
||||
canRequestMore.set(true);
|
||||
if (delegate != null) {
|
||||
scheduler.runOrSchedule(executor);
|
||||
} else {
|
||||
if (debug.on()) debug.log("onSubscribe: read delegate not present yet");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,6 @@
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.http.HttpResponse.BodyHandler;
|
||||
import java.net.http.HttpResponse.BodySubscriber;
|
||||
@ -46,6 +45,7 @@ import jdk.internal.net.http.common.SequentialScheduler;
|
||||
import jdk.internal.net.http.common.MinimalFuture;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import static java.net.http.HttpClient.Version.HTTP_1_1;
|
||||
import static jdk.internal.net.http.common.Utils.wrapWithExtraDetail;
|
||||
|
||||
/**
|
||||
* Encapsulates one HTTP/1.1 request/response exchange.
|
||||
@ -134,6 +134,9 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
subscription.request(n);
|
||||
}
|
||||
|
||||
/** A current-state message suitable for inclusion in an exception detail message. */
|
||||
abstract String currentStateMessage();
|
||||
|
||||
final boolean isSubscribed() {
|
||||
return subscription != null;
|
||||
}
|
||||
@ -159,6 +162,7 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
@Override public void onNext(ByteBuffer item) { error(); }
|
||||
@Override public void onError(Throwable throwable) { error(); }
|
||||
@Override public void onComplete() { error(); }
|
||||
@Override String currentStateMessage() { return null; }
|
||||
private void error() {
|
||||
throw new InternalError("should not reach here");
|
||||
}
|
||||
@ -193,35 +197,6 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
}
|
||||
this.requestAction = new Http1Request(request, this);
|
||||
this.asyncReceiver = new Http1AsyncReceiver(executor, this);
|
||||
asyncReceiver.subscribe(new InitialErrorReceiver());
|
||||
}
|
||||
|
||||
/** An initial receiver that handles no data, but cancels the request if
|
||||
* it receives an error. Will be replaced when reading response body. */
|
||||
final class InitialErrorReceiver implements Http1AsyncReceiver.Http1AsyncDelegate {
|
||||
volatile AbstractSubscription s;
|
||||
@Override
|
||||
public boolean tryAsyncReceive(ByteBuffer ref) {
|
||||
return false; // no data has been processed, leave it in the queue
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadError(Throwable ex) {
|
||||
cancelImpl(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(AbstractSubscription s) {
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSubscription subscription() {
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(Throwable error) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -244,16 +219,16 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
// create the response before sending the request headers, so that
|
||||
// the response can set the appropriate receivers.
|
||||
if (debug.on()) debug.log("Sending headers only");
|
||||
if (response == null) {
|
||||
response = new Http1Response<>(connection, this, asyncReceiver);
|
||||
}
|
||||
|
||||
if (debug.on()) debug.log("response created in advance");
|
||||
// If the first attempt to read something triggers EOF, or
|
||||
// IOException("channel reset by peer"), we're going to retry.
|
||||
// Instruct the asyncReceiver to throw ConnectionExpiredException
|
||||
// to force a retry.
|
||||
asyncReceiver.setRetryOnError(true);
|
||||
if (response == null) {
|
||||
response = new Http1Response<>(connection, this, asyncReceiver);
|
||||
}
|
||||
|
||||
if (debug.on()) debug.log("response created in advance");
|
||||
|
||||
CompletableFuture<Void> connectCF;
|
||||
if (!connection.connected()) {
|
||||
@ -296,6 +271,8 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
return cf;
|
||||
} catch (Throwable t) {
|
||||
if (debug.on()) debug.log("Failed to send headers: %s", t);
|
||||
headersSentCF.completeExceptionally(t);
|
||||
bodySentCF.completeExceptionally(t);
|
||||
connection.close();
|
||||
cf.completeExceptionally(t);
|
||||
return cf;
|
||||
@ -303,28 +280,52 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
.thenCompose(unused -> headersSentCF);
|
||||
}
|
||||
|
||||
private void cancelIfFailed(Flow.Subscription s) {
|
||||
asyncReceiver.whenFinished.whenCompleteAsync((r,t) -> {
|
||||
if (debug.on()) debug.log("asyncReceiver finished (failed=%s)", t);
|
||||
if (t != null) {
|
||||
s.cancel();
|
||||
// Don't complete exceptionally here as 't'
|
||||
// might not be the right exception: it will
|
||||
// not have been decorated yet.
|
||||
// t is an exception raised by the read side,
|
||||
// an EOFException or Broken Pipe...
|
||||
// We are cancelling the BodyPublisher subscription
|
||||
// and completing bodySentCF to allow the next step
|
||||
// to flow and call readHeaderAsync, which will
|
||||
// get the right exception from the asyncReceiver.
|
||||
bodySentCF.complete(this);
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
|
||||
assert headersSentCF.isDone();
|
||||
if (debug.on()) debug.log("sendBodyAsync");
|
||||
try {
|
||||
bodySubscriber = requestAction.continueRequest();
|
||||
if (debug.on()) debug.log("bodySubscriber is %s",
|
||||
bodySubscriber == null ? null : bodySubscriber.getClass());
|
||||
if (bodySubscriber == null) {
|
||||
bodySubscriber = Http1BodySubscriber.completeSubscriber(debug);
|
||||
appendToOutgoing(Http1BodySubscriber.COMPLETED);
|
||||
} else {
|
||||
// start
|
||||
bodySubscriber.whenSubscribed
|
||||
.thenAccept((s) -> cancelIfFailed(s))
|
||||
.thenAccept((s) -> requestMoreBody());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
cancelImpl(t);
|
||||
bodySentCF.completeExceptionally(t);
|
||||
}
|
||||
return bodySentCF;
|
||||
return Utils.wrapForDebug(debug, "sendBodyAsync", bodySentCF);
|
||||
}
|
||||
|
||||
@Override
|
||||
CompletableFuture<Response> getResponseAsync(Executor executor) {
|
||||
if (debug.on()) debug.log("reading headers");
|
||||
CompletableFuture<Response> cf = response.readHeadersAsync(executor);
|
||||
Throwable cause;
|
||||
synchronized (lock) {
|
||||
@ -348,7 +349,7 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
debug.log(acknowledged ? ("completed response with " + cause)
|
||||
: ("response already completed, ignoring " + cause));
|
||||
}
|
||||
return cf;
|
||||
return Utils.wrapForDebug(debug, "getResponseAsync", cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -421,7 +422,6 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
&& response != null && response.finished()) {
|
||||
return;
|
||||
}
|
||||
connection.close(); // TODO: ensure non-blocking if holding the lock
|
||||
writePublisher.writeScheduler.stop();
|
||||
if (operations.isEmpty()) {
|
||||
Log.logTrace("Http1Exchange: request [{0}/timeout={1}ms] no pending operation."
|
||||
@ -444,27 +444,31 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
operations.clear();
|
||||
}
|
||||
}
|
||||
Log.logError("Http1Exchange.cancel: count=" + count);
|
||||
if (toComplete != null) {
|
||||
// We might be in the selector thread in case of timeout, when
|
||||
// the SelectorManager calls purgeTimeoutsAndReturnNextDeadline()
|
||||
// There may or may not be other places that reach here
|
||||
// from the SelectorManager thread, so just make sure we
|
||||
// don't complete any CF from within the selector manager
|
||||
// thread.
|
||||
Executor exec = client.isSelectorThread()
|
||||
? executor
|
||||
: this::runInline;
|
||||
Throwable x = error;
|
||||
while (!toComplete.isEmpty()) {
|
||||
CompletableFuture<?> cf = toComplete.poll();
|
||||
exec.execute(() -> {
|
||||
if (cf.completeExceptionally(x)) {
|
||||
if (debug.on())
|
||||
debug.log("%s: completed cf with %s", request.uri(), x);
|
||||
}
|
||||
});
|
||||
try {
|
||||
Log.logError("Http1Exchange.cancel: count=" + count);
|
||||
if (toComplete != null) {
|
||||
// We might be in the selector thread in case of timeout, when
|
||||
// the SelectorManager calls purgeTimeoutsAndReturnNextDeadline()
|
||||
// There may or may not be other places that reach here
|
||||
// from the SelectorManager thread, so just make sure we
|
||||
// don't complete any CF from within the selector manager
|
||||
// thread.
|
||||
Executor exec = client.isSelectorThread()
|
||||
? executor
|
||||
: this::runInline;
|
||||
Throwable x = error;
|
||||
while (!toComplete.isEmpty()) {
|
||||
CompletableFuture<?> cf = toComplete.poll();
|
||||
exec.execute(() -> {
|
||||
if (cf.completeExceptionally(x)) {
|
||||
if (debug.on())
|
||||
debug.log("%s: completed cf with %s", request.uri(), x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,7 +515,7 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
|
||||
private void requestMoreBody() {
|
||||
try {
|
||||
if (debug.on()) debug.log("requesting more body from the subscriber");
|
||||
if (debug.on()) debug.log("requesting more request body from the subscriber");
|
||||
bodySubscriber.request(1);
|
||||
} catch (Throwable t) {
|
||||
if (debug.on()) debug.log("Subscription::request failed", t);
|
||||
@ -527,46 +531,62 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
final Executor exec = client.theExecutor();
|
||||
final DataPair dp = outgoing.pollFirst();
|
||||
|
||||
if (writePublisher.cancelled) {
|
||||
if (debug.on()) debug.log("cancelling upstream publisher");
|
||||
if (bodySubscriber != null) {
|
||||
exec.execute(bodySubscriber::cancelSubscription);
|
||||
} else if (debug.on()) {
|
||||
debug.log("bodySubscriber is null");
|
||||
}
|
||||
headersSentCF.completeAsync(() -> this, exec);
|
||||
bodySentCF.completeAsync(() -> this, exec);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dp == null) // publisher has not published anything yet
|
||||
return null;
|
||||
|
||||
synchronized (lock) {
|
||||
if (dp.throwable != null) {
|
||||
if (dp.throwable != null) {
|
||||
synchronized (lock) {
|
||||
state = State.ERROR;
|
||||
exec.execute(() -> {
|
||||
headersSentCF.completeExceptionally(dp.throwable);
|
||||
bodySentCF.completeExceptionally(dp.throwable);
|
||||
connection.close();
|
||||
});
|
||||
return dp;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case HEADERS:
|
||||
state = State.BODY;
|
||||
// completeAsync, since dependent tasks should run in another thread
|
||||
if (debug.on()) debug.log("initiating completion of headersSentCF");
|
||||
headersSentCF.completeAsync(() -> this, exec);
|
||||
break;
|
||||
case BODY:
|
||||
if (dp.data == Http1BodySubscriber.COMPLETED) {
|
||||
state = State.COMPLETING;
|
||||
if (debug.on()) debug.log("initiating completion of bodySentCF");
|
||||
bodySentCF.completeAsync(() -> this, exec);
|
||||
} else {
|
||||
exec.execute(this::requestMoreBody);
|
||||
}
|
||||
break;
|
||||
case INITIAL:
|
||||
case ERROR:
|
||||
case COMPLETING:
|
||||
case COMPLETED:
|
||||
default:
|
||||
assert false : "Unexpected state:" + state;
|
||||
}
|
||||
|
||||
exec.execute(() -> {
|
||||
headersSentCF.completeExceptionally(dp.throwable);
|
||||
bodySentCF.completeExceptionally(dp.throwable);
|
||||
connection.close();
|
||||
});
|
||||
return dp;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case HEADERS:
|
||||
synchronized (lock) {
|
||||
state = State.BODY;
|
||||
}
|
||||
// completeAsync, since dependent tasks should run in another thread
|
||||
if (debug.on()) debug.log("initiating completion of headersSentCF");
|
||||
headersSentCF.completeAsync(() -> this, exec);
|
||||
break;
|
||||
case BODY:
|
||||
if (dp.data == Http1BodySubscriber.COMPLETED) {
|
||||
synchronized (lock) {
|
||||
state = State.COMPLETING;
|
||||
}
|
||||
if (debug.on()) debug.log("initiating completion of bodySentCF");
|
||||
bodySentCF.completeAsync(() -> this, exec);
|
||||
} else {
|
||||
exec.execute(this::requestMoreBody);
|
||||
}
|
||||
break;
|
||||
case INITIAL:
|
||||
case ERROR:
|
||||
case COMPLETING:
|
||||
case COMPLETED:
|
||||
default:
|
||||
assert false : "Unexpected state:" + state;
|
||||
}
|
||||
|
||||
return dp;
|
||||
}
|
||||
|
||||
/** A Publisher of HTTP/1.1 headers and request body. */
|
||||
@ -608,6 +628,14 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
public void run() {
|
||||
assert state != State.COMPLETED : "Unexpected state:" + state;
|
||||
if (debug.on()) debug.log("WriteTask");
|
||||
|
||||
if (cancelled) {
|
||||
if (debug.on()) debug.log("handling cancellation");
|
||||
writeScheduler.stop();
|
||||
getOutgoing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscriber == null) {
|
||||
if (debug.on()) debug.log("no subscriber yet");
|
||||
return;
|
||||
@ -615,6 +643,8 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
if (debug.on()) debug.log(() -> "hasOutgoing = " + hasOutgoing());
|
||||
while (hasOutgoing() && demand.tryDecrement()) {
|
||||
DataPair dp = getOutgoing();
|
||||
if (dp == null)
|
||||
break;
|
||||
|
||||
if (dp.throwable != null) {
|
||||
if (debug.on()) debug.log("onError");
|
||||
@ -661,7 +691,7 @@ class Http1Exchange<T> extends ExchangeImpl<T> {
|
||||
if (cancelled)
|
||||
return; //no-op
|
||||
cancelled = true;
|
||||
writeScheduler.stop();
|
||||
writeScheduler.runOrSchedule(client.theExecutor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import java.util.Map;
|
||||
import java.net.http.HttpHeaders;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static jdk.internal.net.http.common.Utils.ACCEPT_ALL;
|
||||
|
||||
class Http1HeaderParser {
|
||||
|
||||
@ -49,10 +50,13 @@ class Http1HeaderParser {
|
||||
private HttpHeaders headers;
|
||||
private Map<String,List<String>> privateMap = new HashMap<>();
|
||||
|
||||
enum State { STATUS_LINE,
|
||||
enum State { INITIAL,
|
||||
STATUS_LINE,
|
||||
STATUS_LINE_FOUND_CR,
|
||||
STATUS_LINE_FOUND_LF,
|
||||
STATUS_LINE_END,
|
||||
STATUS_LINE_END_CR,
|
||||
STATUS_LINE_END_LF,
|
||||
HEADER,
|
||||
HEADER_FOUND_CR,
|
||||
HEADER_FOUND_LF,
|
||||
@ -60,7 +64,7 @@ class Http1HeaderParser {
|
||||
HEADER_FOUND_CR_LF_CR,
|
||||
FINISHED }
|
||||
|
||||
private State state = State.STATUS_LINE;
|
||||
private State state = State.INITIAL;
|
||||
|
||||
/** Returns the status-line. */
|
||||
String statusLine() { return statusLine; }
|
||||
@ -69,7 +73,29 @@ class Http1HeaderParser {
|
||||
int responseCode() { return responseCode; }
|
||||
|
||||
/** Returns the headers, possibly empty. */
|
||||
HttpHeaders headers() { assert state == State.FINISHED; return headers; }
|
||||
HttpHeaders headers() {
|
||||
assert state == State.FINISHED : "Unexpected state " + state;
|
||||
return headers;
|
||||
}
|
||||
|
||||
/** A current-state message suitable for inclusion in an exception detail message. */
|
||||
public String currentStateMessage() {
|
||||
String stateName = state.name();
|
||||
String msg;
|
||||
if (stateName.contains("INITIAL")) {
|
||||
return format("HTTP/1.1 header parser received no bytes");
|
||||
} else if (stateName.contains("STATUS")) {
|
||||
msg = format("parsing HTTP/1.1 status line, receiving [%s]", sb.toString());
|
||||
} else if (stateName.contains("HEADER")) {
|
||||
String headerName = sb.toString();
|
||||
if (headerName.indexOf(':') != -1)
|
||||
headerName = headerName.substring(0, headerName.indexOf(':')+1) + "...";
|
||||
msg = format("parsing HTTP/1.1 header, receiving [%s]", headerName);
|
||||
} else {
|
||||
msg =format("HTTP/1.1 parser receiving [%s]", state, sb.toString());
|
||||
}
|
||||
return format("%s, parser state [%s]", msg , state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses HTTP/1.X status-line and headers from the given bytes. Must be
|
||||
@ -84,18 +110,25 @@ class Http1HeaderParser {
|
||||
boolean parse(ByteBuffer input) throws ProtocolException {
|
||||
requireNonNull(input, "null input");
|
||||
|
||||
while (input.hasRemaining() && state != State.FINISHED) {
|
||||
while (canContinueParsing(input)) {
|
||||
switch (state) {
|
||||
case INITIAL:
|
||||
state = State.STATUS_LINE;
|
||||
break;
|
||||
case STATUS_LINE:
|
||||
readResumeStatusLine(input);
|
||||
break;
|
||||
// fallthrough
|
||||
case STATUS_LINE_FOUND_CR:
|
||||
case STATUS_LINE_FOUND_LF:
|
||||
readStatusLineFeed(input);
|
||||
break;
|
||||
case STATUS_LINE_END:
|
||||
maybeStartHeaders(input);
|
||||
break;
|
||||
// fallthrough
|
||||
case STATUS_LINE_END_CR:
|
||||
case STATUS_LINE_END_LF:
|
||||
maybeEndHeaders(input);
|
||||
break;
|
||||
case HEADER:
|
||||
@ -121,21 +154,35 @@ class Http1HeaderParser {
|
||||
return state == State.FINISHED;
|
||||
}
|
||||
|
||||
private boolean canContinueParsing(ByteBuffer buffer) {
|
||||
// some states don't require any input to transition
|
||||
// to the next state.
|
||||
switch (state) {
|
||||
case FINISHED: return false;
|
||||
case STATUS_LINE_FOUND_LF: return true;
|
||||
case STATUS_LINE_END_LF: return true;
|
||||
case HEADER_FOUND_LF: return true;
|
||||
default: return buffer.hasRemaining();
|
||||
}
|
||||
}
|
||||
|
||||
private void readResumeStatusLine(ByteBuffer input) {
|
||||
char c = 0;
|
||||
while (input.hasRemaining() && (c =(char)input.get()) != CR) {
|
||||
if (c == LF) break;
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
if (c == CR) {
|
||||
state = State.STATUS_LINE_FOUND_CR;
|
||||
} else if (c == LF) {
|
||||
state = State.STATUS_LINE_FOUND_LF;
|
||||
}
|
||||
}
|
||||
|
||||
private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
|
||||
char c = (char)input.get();
|
||||
char c = state == State.STATUS_LINE_FOUND_LF ? LF : (char)input.get();
|
||||
if (c != LF) {
|
||||
throw protocolException("Bad trailing char, \"%s\", when parsing status-line, \"%s\"",
|
||||
throw protocolException("Bad trailing char, \"%s\", when parsing status line, \"%s\"",
|
||||
c, sb.toString());
|
||||
}
|
||||
|
||||
@ -158,6 +205,8 @@ class Http1HeaderParser {
|
||||
char c = (char)input.get();
|
||||
if (c == CR) {
|
||||
state = State.STATUS_LINE_END_CR;
|
||||
} else if (c == LF) {
|
||||
state = State.STATUS_LINE_END_LF;
|
||||
} else {
|
||||
sb.append(c);
|
||||
state = State.HEADER;
|
||||
@ -165,15 +214,15 @@ class Http1HeaderParser {
|
||||
}
|
||||
|
||||
private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
|
||||
assert state == State.STATUS_LINE_END_CR;
|
||||
assert state == State.STATUS_LINE_END_CR || state == State.STATUS_LINE_END_LF;
|
||||
assert sb.length() == 0;
|
||||
char c = (char)input.get();
|
||||
char c = state == State.STATUS_LINE_END_LF ? LF : (char)input.get();
|
||||
if (c == LF) {
|
||||
headers = ImmutableHeaders.of(privateMap);
|
||||
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
|
||||
privateMap = null;
|
||||
state = State.FINISHED; // no headers
|
||||
} else {
|
||||
throw protocolException("Unexpected \"%s\", after status-line CR", c);
|
||||
throw protocolException("Unexpected \"%s\", after status line CR", c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,8 +261,8 @@ class Http1HeaderParser {
|
||||
|
||||
private void resumeOrLF(ByteBuffer input) {
|
||||
assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
|
||||
char c = (char)input.get();
|
||||
if (c == LF && state == State.HEADER_FOUND_CR) {
|
||||
char c = state == State.HEADER_FOUND_LF ? LF : (char)input.get();
|
||||
if (c == LF) {
|
||||
// header value will be flushed by
|
||||
// resumeOrSecondCR if next line does not
|
||||
// begin by SP or HT
|
||||
@ -231,7 +280,7 @@ class Http1HeaderParser {
|
||||
private void resumeOrSecondCR(ByteBuffer input) {
|
||||
assert state == State.HEADER_FOUND_CR_LF;
|
||||
char c = (char)input.get();
|
||||
if (c == CR) {
|
||||
if (c == CR || c == LF) {
|
||||
if (sb.length() > 0) {
|
||||
// no continuation line - flush
|
||||
// previous header value.
|
||||
@ -239,7 +288,13 @@ class Http1HeaderParser {
|
||||
sb = new StringBuilder();
|
||||
addHeaderFromString(headerString);
|
||||
}
|
||||
state = State.HEADER_FOUND_CR_LF_CR;
|
||||
if (c == CR) {
|
||||
state = State.HEADER_FOUND_CR_LF_CR;
|
||||
} else {
|
||||
state = State.FINISHED;
|
||||
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
|
||||
privateMap = null;
|
||||
}
|
||||
} else if (c == SP || c == HT) {
|
||||
assert sb.length() != 0;
|
||||
sb.append(SP); // continuation line
|
||||
@ -262,7 +317,7 @@ class Http1HeaderParser {
|
||||
char c = (char)input.get();
|
||||
if (c == LF) {
|
||||
state = State.FINISHED;
|
||||
headers = ImmutableHeaders.of(privateMap);
|
||||
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
|
||||
privateMap = null;
|
||||
} else {
|
||||
throw protocolException("Unexpected \"%s\", after CR LF CR", c);
|
||||
|
@ -26,7 +26,6 @@
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
@ -39,11 +38,12 @@ import java.util.function.BiPredicate;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpRequest;
|
||||
import jdk.internal.net.http.Http1Exchange.Http1BodySubscriber;
|
||||
import jdk.internal.net.http.common.HttpHeadersImpl;
|
||||
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||
import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
/**
|
||||
@ -52,7 +52,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
class Http1Request {
|
||||
|
||||
private static final String COOKIE_HEADER = "Cookie";
|
||||
private static final BiPredicate<String,List<String>> NOCOOKIES =
|
||||
private static final BiPredicate<String,String> NOCOOKIES =
|
||||
(k,v) -> !COOKIE_HEADER.equalsIgnoreCase(k);
|
||||
|
||||
private final HttpRequestImpl request;
|
||||
@ -60,7 +60,7 @@ class Http1Request {
|
||||
private final HttpConnection connection;
|
||||
private final HttpRequest.BodyPublisher requestPublisher;
|
||||
private final HttpHeaders userHeaders;
|
||||
private final HttpHeadersImpl systemHeaders;
|
||||
private final HttpHeadersBuilder systemHeadersBuilder;
|
||||
private volatile boolean streaming;
|
||||
private volatile long contentLength;
|
||||
|
||||
@ -73,7 +73,7 @@ class Http1Request {
|
||||
this.connection = http1Exchange.connection();
|
||||
this.requestPublisher = request.requestPublisher; // may be null
|
||||
this.userHeaders = request.getUserHeaders();
|
||||
this.systemHeaders = request.getSystemHeaders();
|
||||
this.systemHeadersBuilder = request.getSystemHeadersBuilder();
|
||||
}
|
||||
|
||||
private void logHeaders(String completeHeaders) {
|
||||
@ -92,12 +92,13 @@ class Http1Request {
|
||||
|
||||
|
||||
private void collectHeaders0(StringBuilder sb) {
|
||||
BiPredicate<String,List<String>> filter =
|
||||
BiPredicate<String,String> filter =
|
||||
connection.headerFilter(request);
|
||||
|
||||
// Filter out 'Cookie:' headers, we will collect them at the end.
|
||||
BiPredicate<String,List<String>> nocookies =
|
||||
NOCOOKIES.and(filter);
|
||||
BiPredicate<String,String> nocookies = NOCOOKIES.and(filter);
|
||||
|
||||
HttpHeaders systemHeaders = systemHeadersBuilder.build();
|
||||
|
||||
// If we're sending this request through a tunnel,
|
||||
// then don't send any preemptive proxy-* headers that
|
||||
@ -112,8 +113,7 @@ class Http1Request {
|
||||
|
||||
// Gather all 'Cookie:' headers and concatenate their
|
||||
// values in a single line.
|
||||
collectCookies(sb, COOKIE_HEADER,
|
||||
systemHeaders, userHeaders, filter);
|
||||
collectCookies(sb, systemHeaders, userHeaders);
|
||||
|
||||
// terminate headers
|
||||
sb.append('\r').append('\n');
|
||||
@ -142,20 +142,16 @@ class Http1Request {
|
||||
// any illegal character for header field values.
|
||||
//
|
||||
private void collectCookies(StringBuilder sb,
|
||||
String key,
|
||||
HttpHeaders system,
|
||||
HttpHeaders user,
|
||||
BiPredicate<String, List<String>> filter) {
|
||||
List<String> systemList = system.allValues(key);
|
||||
if (systemList != null && !filter.test(key, systemList)) systemList = null;
|
||||
List<String> userList = user.allValues(key);
|
||||
if (userList != null && !filter.test(key, userList)) userList = null;
|
||||
HttpHeaders user) {
|
||||
List<String> systemList = system.allValues(COOKIE_HEADER);
|
||||
List<String> userList = user.allValues(COOKIE_HEADER);
|
||||
boolean found = false;
|
||||
if (systemList != null) {
|
||||
for (String cookie : systemList) {
|
||||
if (!found) {
|
||||
found = true;
|
||||
sb.append(key).append(':').append(' ');
|
||||
sb.append(COOKIE_HEADER).append(':').append(' ');
|
||||
} else {
|
||||
sb.append(';').append(' ');
|
||||
}
|
||||
@ -166,7 +162,7 @@ class Http1Request {
|
||||
for (String cookie : userList) {
|
||||
if (!found) {
|
||||
found = true;
|
||||
sb.append(key).append(':').append(' ');
|
||||
sb.append(COOKIE_HEADER).append(':').append(' ');
|
||||
} else {
|
||||
sb.append(';').append(' ');
|
||||
}
|
||||
@ -176,13 +172,15 @@ class Http1Request {
|
||||
if (found) sb.append('\r').append('\n');
|
||||
}
|
||||
|
||||
private void collectHeaders1(StringBuilder sb, HttpHeaders headers,
|
||||
BiPredicate<String, List<String>> filter) {
|
||||
private void collectHeaders1(StringBuilder sb,
|
||||
HttpHeaders headers,
|
||||
BiPredicate<String,String> filter) {
|
||||
for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
List<String> values = entry.getValue();
|
||||
if (!filter.test(key, values)) continue;
|
||||
for (String value : values) {
|
||||
if (!filter.test(key, value))
|
||||
continue;
|
||||
sb.append(key).append(':').append(' ')
|
||||
.append(value)
|
||||
.append('\r').append('\n');
|
||||
@ -279,7 +277,7 @@ class Http1Request {
|
||||
|
||||
URI uri = request.uri();
|
||||
if (uri != null) {
|
||||
systemHeaders.setHeader("Host", hostString());
|
||||
systemHeadersBuilder.setHeader("Host", hostString());
|
||||
}
|
||||
if (requestPublisher == null) {
|
||||
// Not a user request, or maybe a method, e.g. GET, with no body.
|
||||
@ -289,13 +287,13 @@ class Http1Request {
|
||||
}
|
||||
|
||||
if (contentLength == 0) {
|
||||
systemHeaders.setHeader("Content-Length", "0");
|
||||
systemHeadersBuilder.setHeader("Content-Length", "0");
|
||||
} else if (contentLength > 0) {
|
||||
systemHeaders.setHeader("Content-Length", Long.toString(contentLength));
|
||||
systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));
|
||||
streaming = false;
|
||||
} else {
|
||||
streaming = true;
|
||||
systemHeaders.setHeader("Transfer-encoding", "chunked");
|
||||
systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");
|
||||
}
|
||||
collectHeaders0(sb);
|
||||
String hs = sb.toString();
|
||||
@ -349,6 +347,11 @@ class Http1Request {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String currentStateMessage() {
|
||||
return "streaming request body " + (complete ? "complete" : "incomplete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
if (complete)
|
||||
@ -416,6 +419,12 @@ class Http1Request {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String currentStateMessage() {
|
||||
return format("fixed content-length: %d, bytes sent: %d",
|
||||
contentLength, contentWritten);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
if (debug.on()) debug.log("onError");
|
||||
|
@ -44,9 +44,10 @@ import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.MinimalFuture;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
|
||||
import static java.net.http.HttpClient.Version.HTTP_1_1;
|
||||
import static java.net.http.HttpResponse.BodySubscribers.discarding;
|
||||
import static jdk.internal.net.http.common.Utils.wrapWithExtraDetail;
|
||||
import static jdk.internal.net.http.RedirectFilter.HTTP_NOT_MODIFIED;
|
||||
|
||||
/**
|
||||
* Handles a HTTP/1.1 response (headers + body).
|
||||
@ -76,6 +77,7 @@ class Http1Response<T> {
|
||||
final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
|
||||
final static AtomicLong responseCount = new AtomicLong();
|
||||
final long id = responseCount.incrementAndGet();
|
||||
private Http1HeaderParser hd;
|
||||
|
||||
Http1Response(HttpConnection conn,
|
||||
Http1Exchange<T> exchange,
|
||||
@ -87,6 +89,11 @@ class Http1Response<T> {
|
||||
this.asyncReceiver = asyncReceiver;
|
||||
headersReader = new HeadersReader(this::advance);
|
||||
bodyReader = new BodyReader(this::advance);
|
||||
|
||||
hd = new Http1HeaderParser();
|
||||
readProgress = State.READING_HEADERS;
|
||||
headersReader.start(hd);
|
||||
asyncReceiver.subscribe(headersReader);
|
||||
}
|
||||
|
||||
String dbgTag;
|
||||
@ -150,19 +157,36 @@ class Http1Response<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private volatile boolean firstTimeAround = true;
|
||||
|
||||
public CompletableFuture<Response> readHeadersAsync(Executor executor) {
|
||||
if (debug.on())
|
||||
debug.log("Reading Headers: (remaining: "
|
||||
+ asyncReceiver.remaining() +") " + readProgress);
|
||||
// with expect continue we will resume reading headers + body.
|
||||
asyncReceiver.unsubscribe(bodyReader);
|
||||
bodyReader.reset();
|
||||
Http1HeaderParser hd = new Http1HeaderParser();
|
||||
readProgress = State.READING_HEADERS;
|
||||
headersReader.start(hd);
|
||||
asyncReceiver.subscribe(headersReader);
|
||||
|
||||
if (firstTimeAround) {
|
||||
if (debug.on()) debug.log("First time around");
|
||||
firstTimeAround = false;
|
||||
} else {
|
||||
// with expect continue we will resume reading headers + body.
|
||||
asyncReceiver.unsubscribe(bodyReader);
|
||||
bodyReader.reset();
|
||||
|
||||
hd = new Http1HeaderParser();
|
||||
readProgress = State.READING_HEADERS;
|
||||
headersReader.reset();
|
||||
headersReader.start(hd);
|
||||
asyncReceiver.subscribe(headersReader);
|
||||
}
|
||||
|
||||
CompletableFuture<State> cf = headersReader.completion();
|
||||
assert cf != null : "parsing not started";
|
||||
if (debug.on()) {
|
||||
debug.log("headersReader is %s",
|
||||
cf == null ? "not yet started"
|
||||
: cf.isDone() ? "already completed"
|
||||
: "not yet completed");
|
||||
}
|
||||
|
||||
Function<State, Response> lambda = (State completed) -> {
|
||||
assert completed == State.READING_HEADERS;
|
||||
@ -207,7 +231,7 @@ class Http1Response<T> {
|
||||
}
|
||||
|
||||
int fixupContentLen(int clen) {
|
||||
if (request.method().equalsIgnoreCase("HEAD")) {
|
||||
if (request.method().equalsIgnoreCase("HEAD") || responseCode == HTTP_NOT_MODIFIED) {
|
||||
return 0;
|
||||
}
|
||||
if (clen == -1) {
|
||||
@ -289,13 +313,19 @@ class Http1Response<T> {
|
||||
try {
|
||||
userSubscriber.onComplete();
|
||||
} catch (Throwable x) {
|
||||
propagateError(t = withError = Utils.getCompletionCause(x));
|
||||
// rethrow and let the caller deal with it.
|
||||
// Simply propagate the error by calling
|
||||
// onError on the user subscriber, and let the
|
||||
// connection be reused since we should have received
|
||||
// and parsed all the bytes when we reach here.
|
||||
// If onError throws in turn, then we will simply
|
||||
// let that new exception flow up to the caller
|
||||
// and let it deal with it.
|
||||
// (i.e: log and close the connection)
|
||||
// arguably we could decide to not throw and let the
|
||||
// connection be reused since we should have received and
|
||||
// parsed all the bytes when we reach here.
|
||||
throw x;
|
||||
// Note that rethrowing here could introduce a
|
||||
// race that might cause the next send() operation to
|
||||
// fail as the connection has already been put back
|
||||
// into the cache when we reach here.
|
||||
propagateError(t = withError = Utils.getCompletionCause(x));
|
||||
}
|
||||
} else {
|
||||
propagateError(t);
|
||||
@ -613,6 +643,7 @@ class Http1Response<T> {
|
||||
|
||||
@Override
|
||||
public final void onReadError(Throwable t) {
|
||||
t = wrapWithExtraDetail(t, parser::currentStateMessage);
|
||||
Http1Response.this.onReadError(t);
|
||||
}
|
||||
|
||||
@ -692,6 +723,7 @@ class Http1Response<T> {
|
||||
|
||||
@Override
|
||||
public final void onReadError(Throwable t) {
|
||||
t = wrapWithExtraDetail(t, parser::currentStateMessage);
|
||||
Http1Response.this.onReadError(t);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,9 @@
|
||||
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Base64;
|
||||
@ -95,15 +98,20 @@ class Http2ClientImpl {
|
||||
synchronized (this) {
|
||||
Http2Connection connection = connections.get(key);
|
||||
if (connection != null) {
|
||||
if (connection.closed || !connection.reserveStream(true)) {
|
||||
if (debug.on())
|
||||
debug.log("removing found closed or closing connection: %s", connection);
|
||||
deleteConnection(connection);
|
||||
} else {
|
||||
// fast path if connection already exists
|
||||
if (debug.on())
|
||||
debug.log("found connection in the pool: %s", connection);
|
||||
return MinimalFuture.completedFuture(connection);
|
||||
try {
|
||||
if (connection.closed || !connection.reserveStream(true)) {
|
||||
if (debug.on())
|
||||
debug.log("removing found closed or closing connection: %s", connection);
|
||||
deleteConnection(connection);
|
||||
} else {
|
||||
// fast path if connection already exists
|
||||
if (debug.on())
|
||||
debug.log("found connection in the pool: %s", connection);
|
||||
return MinimalFuture.completedFuture(connection);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// thrown by connection.reserveStream()
|
||||
return MinimalFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,6 +127,11 @@ class Http2ClientImpl {
|
||||
.whenComplete((conn, t) -> {
|
||||
synchronized (Http2ClientImpl.this) {
|
||||
if (conn != null) {
|
||||
try {
|
||||
conn.reserveStream(true);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e); // shouldn't happen
|
||||
}
|
||||
offerConnection(conn);
|
||||
} else {
|
||||
Throwable cause = Utils.getCompletionCause(t);
|
||||
|
@ -28,7 +28,6 @@ package jdk.internal.net.http;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -54,7 +53,7 @@ import java.net.http.HttpHeaders;
|
||||
import jdk.internal.net.http.HttpConnection.HttpPublisher;
|
||||
import jdk.internal.net.http.common.FlowTube;
|
||||
import jdk.internal.net.http.common.FlowTube.TubeSubscriber;
|
||||
import jdk.internal.net.http.common.HttpHeadersImpl;
|
||||
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||
import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.MinimalFuture;
|
||||
@ -82,7 +81,6 @@ import jdk.internal.net.http.hpack.DecodingCallback;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static jdk.internal.net.http.frame.SettingsFrame.*;
|
||||
|
||||
|
||||
/**
|
||||
* An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used
|
||||
* over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.
|
||||
@ -259,6 +257,8 @@ class Http2Connection {
|
||||
// assigning a stream to a connection.
|
||||
private int lastReservedClientStreamid = 1;
|
||||
private int lastReservedServerStreamid = 0;
|
||||
private int numReservedClientStreams = 0; // count of current streams
|
||||
private int numReservedServerStreams = 0; // count of current streams
|
||||
private final Encoder hpackOut;
|
||||
private final Decoder hpackIn;
|
||||
final SettingsFrame clientSettings;
|
||||
@ -273,7 +273,7 @@ class Http2Connection {
|
||||
*/
|
||||
private final WindowController windowController = new WindowController();
|
||||
private final FramesController framesController = new FramesController();
|
||||
private final Http2TubeSubscriber subscriber = new Http2TubeSubscriber();
|
||||
private final Http2TubeSubscriber subscriber;
|
||||
final ConnectionWindowUpdateSender windowUpdater;
|
||||
private volatile Throwable cause;
|
||||
private volatile Supplier<ByteBuffer> initial;
|
||||
@ -290,6 +290,7 @@ class Http2Connection {
|
||||
String key) {
|
||||
this.connection = connection;
|
||||
this.client2 = client2;
|
||||
this.subscriber = new Http2TubeSubscriber(client2.client());
|
||||
this.nextstreamid = nextstreamid;
|
||||
this.key = key;
|
||||
this.clientSettings = this.client2.getClientSettings();
|
||||
@ -310,7 +311,7 @@ class Http2Connection {
|
||||
|
||||
/**
|
||||
* Case 1) Create from upgraded HTTP/1.1 connection.
|
||||
* Is ready to use. Can be SSL. exchange is the Exchange
|
||||
* Is ready to use. Can't be SSL. exchange is the Exchange
|
||||
* that initiated the connection, whose response will be delivered
|
||||
* on a Stream.
|
||||
*/
|
||||
@ -324,6 +325,7 @@ class Http2Connection {
|
||||
client2,
|
||||
3, // stream 1 is registered during the upgrade
|
||||
keyFor(connection));
|
||||
reserveStream(true);
|
||||
Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
|
||||
|
||||
Stream<?> initialStream = createStream(exchange);
|
||||
@ -407,7 +409,8 @@ class Http2Connection {
|
||||
// call these before assigning a request/stream to a connection
|
||||
// if false returned then a new Http2Connection is required
|
||||
// if true, the the stream may be assigned to this connection
|
||||
synchronized boolean reserveStream(boolean clientInitiated) {
|
||||
// for server push, if false returned, then the stream should be cancelled
|
||||
synchronized boolean reserveStream(boolean clientInitiated) throws IOException {
|
||||
if (finalStream) {
|
||||
return false;
|
||||
}
|
||||
@ -424,6 +427,19 @@ class Http2Connection {
|
||||
lastReservedClientStreamid+=2;
|
||||
else
|
||||
lastReservedServerStreamid+=2;
|
||||
|
||||
assert numReservedClientStreams >= 0;
|
||||
assert numReservedServerStreams >= 0;
|
||||
if (clientInitiated && numReservedClientStreams >= getMaxConcurrentClientStreams()) {
|
||||
throw new IOException("too many concurrent streams");
|
||||
} else if (clientInitiated) {
|
||||
numReservedClientStreams++;
|
||||
}
|
||||
if (!clientInitiated && numReservedServerStreams >= getMaxConcurrentServerStreams()) {
|
||||
return false;
|
||||
} else if (!clientInitiated) {
|
||||
numReservedServerStreams++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -564,6 +580,14 @@ class Http2Connection {
|
||||
return serverSettings.getParameter(INITIAL_WINDOW_SIZE);
|
||||
}
|
||||
|
||||
final int getMaxConcurrentClientStreams() {
|
||||
return serverSettings.getParameter(MAX_CONCURRENT_STREAMS);
|
||||
}
|
||||
|
||||
final int getMaxConcurrentServerStreams() {
|
||||
return clientSettings.getParameter(MAX_CONCURRENT_STREAMS);
|
||||
}
|
||||
|
||||
void close() {
|
||||
Log.logTrace("Closing HTTP/2 connection: to {0}", connection.address());
|
||||
GoAwayFrame f = new GoAwayFrame(0,
|
||||
@ -637,13 +661,19 @@ class Http2Connection {
|
||||
if (closed == true) return;
|
||||
closed = true;
|
||||
}
|
||||
Log.logError(t);
|
||||
if (Log.errors()) {
|
||||
if (!(t instanceof EOFException) || isActive()) {
|
||||
Log.logError(t);
|
||||
} else if (t != null) {
|
||||
Log.logError("Shutting down connection: {0}", t.getMessage());
|
||||
}
|
||||
}
|
||||
Throwable initialCause = this.cause;
|
||||
if (initialCause == null) this.cause = t;
|
||||
client2.deleteConnection(this);
|
||||
List<Stream<?>> c = new LinkedList<>(streams.values());
|
||||
for (Stream<?> s : c) {
|
||||
s.cancelImpl(t);
|
||||
s.connectionClosing(t);
|
||||
}
|
||||
connection.close();
|
||||
}
|
||||
@ -766,7 +796,7 @@ class Http2Connection {
|
||||
nextPushStream += 2;
|
||||
}
|
||||
|
||||
HttpHeadersImpl headers = decoder.headers();
|
||||
HttpHeaders headers = decoder.headers();
|
||||
HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers);
|
||||
Exchange<T> pushExch = new Exchange<>(pushReq, parent.exchange.multi);
|
||||
Stream.PushedStream<T> pushStream = createPushStream(parent, pushExch);
|
||||
@ -797,16 +827,44 @@ class Http2Connection {
|
||||
}
|
||||
|
||||
void resetStream(int streamid, int code) throws IOException {
|
||||
Log.logError(
|
||||
"Resetting stream {0,number,integer} with error code {1,number,integer}",
|
||||
streamid, code);
|
||||
ResetFrame frame = new ResetFrame(streamid, code);
|
||||
sendFrame(frame);
|
||||
closeStream(streamid);
|
||||
try {
|
||||
if (connection.channel().isOpen()) {
|
||||
// no need to try & send a reset frame if the
|
||||
// connection channel is already closed.
|
||||
Log.logError(
|
||||
"Resetting stream {0,number,integer} with error code {1,number,integer}",
|
||||
streamid, code);
|
||||
ResetFrame frame = new ResetFrame(streamid, code);
|
||||
sendFrame(frame);
|
||||
} else if (debug.on()) {
|
||||
debug.log("Channel already closed, no need to reset stream %d",
|
||||
streamid);
|
||||
}
|
||||
} finally {
|
||||
decrementStreamsCount(streamid);
|
||||
closeStream(streamid);
|
||||
}
|
||||
}
|
||||
|
||||
// reduce count of streams by 1 if stream still exists
|
||||
synchronized void decrementStreamsCount(int streamid) {
|
||||
Stream<?> s = streams.get(streamid);
|
||||
if (s == null || !s.deRegister())
|
||||
return;
|
||||
if (streamid % 2 == 1) {
|
||||
numReservedClientStreams--;
|
||||
assert numReservedClientStreams >= 0 :
|
||||
"negative client stream count for stream=" + streamid;
|
||||
} else {
|
||||
numReservedServerStreams--;
|
||||
assert numReservedServerStreams >= 0 :
|
||||
"negative server stream count for stream=" + streamid;
|
||||
}
|
||||
}
|
||||
|
||||
void closeStream(int streamid) {
|
||||
if (debug.on()) debug.log("Closed stream %d", streamid);
|
||||
boolean isClient = (streamid % 2) == 1;
|
||||
Stream<?> s = streams.remove(streamid);
|
||||
if (s != null) {
|
||||
// decrement the reference count on the HttpClientImpl
|
||||
@ -1148,14 +1206,19 @@ class Http2Connection {
|
||||
* A simple tube subscriber for reading from the connection flow.
|
||||
*/
|
||||
final class Http2TubeSubscriber implements TubeSubscriber {
|
||||
volatile Flow.Subscription subscription;
|
||||
volatile boolean completed;
|
||||
volatile boolean dropped;
|
||||
volatile Throwable error;
|
||||
final ConcurrentLinkedQueue<ByteBuffer> queue
|
||||
private volatile Flow.Subscription subscription;
|
||||
private volatile boolean completed;
|
||||
private volatile boolean dropped;
|
||||
private volatile Throwable error;
|
||||
private final ConcurrentLinkedQueue<ByteBuffer> queue
|
||||
= new ConcurrentLinkedQueue<>();
|
||||
final SequentialScheduler scheduler =
|
||||
private final SequentialScheduler scheduler =
|
||||
SequentialScheduler.synchronizedScheduler(this::processQueue);
|
||||
private final HttpClientImpl client;
|
||||
|
||||
Http2TubeSubscriber(HttpClientImpl client) {
|
||||
this.client = Objects.requireNonNull(client);
|
||||
}
|
||||
|
||||
final void processQueue() {
|
||||
try {
|
||||
@ -1179,6 +1242,12 @@ class Http2Connection {
|
||||
}
|
||||
}
|
||||
|
||||
private final void runOrSchedule() {
|
||||
if (client.isSelectorThread()) {
|
||||
scheduler.runOrSchedule(client.theExecutor());
|
||||
} else scheduler.runOrSchedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
// supports being called multiple time.
|
||||
@ -1202,7 +1271,7 @@ class Http2Connection {
|
||||
if (debug.on()) debug.log(() -> "onNext: got " + Utils.remaining(item)
|
||||
+ " bytes in " + item.size() + " buffers");
|
||||
queue.addAll(item);
|
||||
scheduler.runOrSchedule(client().theExecutor());
|
||||
runOrSchedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1210,15 +1279,18 @@ class Http2Connection {
|
||||
if (debug.on()) debug.log(() -> "onError: " + throwable);
|
||||
error = throwable;
|
||||
completed = true;
|
||||
scheduler.runOrSchedule(client().theExecutor());
|
||||
runOrSchedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
if (debug.on()) debug.log("EOF");
|
||||
error = new EOFException("EOF reached while reading");
|
||||
String msg = isActive()
|
||||
? "EOF reached while reading"
|
||||
: "Idle connection closed by HTTP/2 peer";
|
||||
if (debug.on()) debug.log(msg);
|
||||
error = new EOFException(msg);
|
||||
completed = true;
|
||||
scheduler.runOrSchedule(client().theExecutor());
|
||||
runOrSchedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1230,6 +1302,10 @@ class Http2Connection {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean isActive() {
|
||||
return numReservedClientStreams > 0 || numReservedServerStreams > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return dbgString();
|
||||
@ -1242,10 +1318,10 @@ class Http2Connection {
|
||||
|
||||
static class HeaderDecoder extends ValidatingHeadersConsumer {
|
||||
|
||||
HttpHeadersImpl headers;
|
||||
HttpHeadersBuilder headersBuilder;
|
||||
|
||||
HeaderDecoder() {
|
||||
this.headers = new HttpHeadersImpl();
|
||||
this.headersBuilder = new HttpHeadersBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1253,11 +1329,11 @@ class Http2Connection {
|
||||
String n = name.toString();
|
||||
String v = value.toString();
|
||||
super.onDecoded(n, v);
|
||||
headers.addHeader(n, v);
|
||||
headersBuilder.addHeader(n, v);
|
||||
}
|
||||
|
||||
HttpHeadersImpl headers() {
|
||||
return headers;
|
||||
HttpHeaders headers() {
|
||||
return headersBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,14 @@ package jdk.internal.net.http;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.Authenticator;
|
||||
import java.net.ConnectException;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.http.HttpTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
@ -56,13 +59,14 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
@ -121,6 +125,34 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A DelegatingExecutor is an executor that delegates tasks to
|
||||
* a wrapped executor when it detects that the current thread
|
||||
* is the SelectorManager thread. If the current thread is not
|
||||
* the selector manager thread the given task is executed inline.
|
||||
*/
|
||||
final static class DelegatingExecutor implements Executor {
|
||||
private final BooleanSupplier isInSelectorThread;
|
||||
private final Executor delegate;
|
||||
DelegatingExecutor(BooleanSupplier isInSelectorThread, Executor delegate) {
|
||||
this.isInSelectorThread = isInSelectorThread;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
Executor delegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
if (isInSelectorThread.getAsBoolean()) {
|
||||
delegate.execute(command);
|
||||
} else {
|
||||
command.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final CookieHandler cookieHandler;
|
||||
private final Redirect followRedirects;
|
||||
private final Optional<ProxySelector> userProxySelector;
|
||||
@ -128,7 +160,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
private final Authenticator authenticator;
|
||||
private final Version version;
|
||||
private final ConnectionPool connections;
|
||||
private final Executor executor;
|
||||
private final DelegatingExecutor delegatingExecutor;
|
||||
private final boolean isDefaultExecutor;
|
||||
// Security parameters
|
||||
private final SSLContext sslContext;
|
||||
@ -240,12 +272,11 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
|
||||
isDefaultExecutor = true;
|
||||
} else {
|
||||
ex = builder.executor;
|
||||
isDefaultExecutor = false;
|
||||
}
|
||||
delegatingExecutor = new DelegatingExecutor(this::isSelectorThread, ex);
|
||||
facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
|
||||
client2 = new Http2ClientImpl(this);
|
||||
executor = ex;
|
||||
cookieHandler = builder.cookieHandler;
|
||||
followRedirects = builder.followRedirects == null ?
|
||||
Redirect.NEVER : builder.followRedirects;
|
||||
@ -489,21 +520,38 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
send(HttpRequest req, BodyHandler<T> responseHandler)
|
||||
throws IOException, InterruptedException
|
||||
{
|
||||
CompletableFuture<HttpResponse<T>> cf = null;
|
||||
try {
|
||||
return sendAsync(req, responseHandler, null).get();
|
||||
cf = sendAsync(req, responseHandler, null, null);
|
||||
return cf.get();
|
||||
} catch (InterruptedException ie) {
|
||||
if (cf != null )
|
||||
cf.cancel(true);
|
||||
throw ie;
|
||||
} catch (ExecutionException e) {
|
||||
Throwable t = e.getCause();
|
||||
if (t instanceof Error)
|
||||
throw (Error)t;
|
||||
if (t instanceof RuntimeException)
|
||||
throw (RuntimeException)t;
|
||||
else if (t instanceof IOException)
|
||||
throw Utils.getIOException(t);
|
||||
else
|
||||
throw new InternalError("Unexpected exception", t);
|
||||
final Throwable throwable = e.getCause();
|
||||
final String msg = throwable.getMessage();
|
||||
|
||||
if (throwable instanceof IllegalArgumentException) {
|
||||
throw new IllegalArgumentException(msg, throwable);
|
||||
} else if (throwable instanceof SecurityException) {
|
||||
throw new SecurityException(msg, throwable);
|
||||
} else if (throwable instanceof HttpTimeoutException) {
|
||||
throw new HttpTimeoutException(msg);
|
||||
} else if (throwable instanceof ConnectException) {
|
||||
ConnectException ce = new ConnectException(msg);
|
||||
ce.initCause(throwable);
|
||||
throw ce;
|
||||
} else if (throwable instanceof IOException) {
|
||||
throw new IOException(msg, throwable);
|
||||
} else {
|
||||
throw new IOException(msg, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Executor ASYNC_POOL = new CompletableFuture<Void>().defaultExecutor();
|
||||
|
||||
@Override
|
||||
public <T> CompletableFuture<HttpResponse<T>>
|
||||
sendAsync(HttpRequest userRequest, BodyHandler<T> responseHandler)
|
||||
@ -511,13 +559,20 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
return sendAsync(userRequest, responseHandler, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> CompletableFuture<HttpResponse<T>>
|
||||
sendAsync(HttpRequest userRequest,
|
||||
BodyHandler<T> responseHandler,
|
||||
PushPromiseHandler<T> pushPromiseHandler)
|
||||
{
|
||||
PushPromiseHandler<T> pushPromiseHandler) {
|
||||
return sendAsync(userRequest, responseHandler, pushPromiseHandler, delegatingExecutor.delegate);
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<HttpResponse<T>>
|
||||
sendAsync(HttpRequest userRequest,
|
||||
BodyHandler<T> responseHandler,
|
||||
PushPromiseHandler<T> pushPromiseHandler,
|
||||
Executor exchangeExecutor) {
|
||||
|
||||
Objects.requireNonNull(userRequest);
|
||||
Objects.requireNonNull(responseHandler);
|
||||
|
||||
@ -536,9 +591,17 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
if (debugelapsed.on())
|
||||
debugelapsed.log("ClientImpl (async) send %s", userRequest);
|
||||
|
||||
Executor executor = acc == null
|
||||
? this.executor
|
||||
: new PrivilegedExecutor(this.executor, acc);
|
||||
// When using sendAsync(...) we explicitly pass the
|
||||
// executor's delegate as exchange executor to force
|
||||
// asynchronous scheduling of the exchange.
|
||||
// When using send(...) we don't specify any executor
|
||||
// and default to using the client's delegating executor
|
||||
// which only spawns asynchronous tasks if it detects
|
||||
// that the current thread is the selector manager
|
||||
// thread. This will cause everything to execute inline
|
||||
// until we need to schedule some event with the selector.
|
||||
Executor executor = exchangeExecutor == null
|
||||
? this.delegatingExecutor : exchangeExecutor;
|
||||
|
||||
MultiExchange<T> mex = new MultiExchange<>(userRequest,
|
||||
requestImpl,
|
||||
@ -547,15 +610,18 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
pushPromiseHandler,
|
||||
acc);
|
||||
CompletableFuture<HttpResponse<T>> res =
|
||||
mex.responseAsync().whenComplete((b,t) -> unreference());
|
||||
mex.responseAsync(executor).whenComplete((b,t) -> unreference());
|
||||
if (DEBUGELAPSED) {
|
||||
res = res.whenComplete(
|
||||
(b,t) -> debugCompleted("ClientImpl (async)", start, userRequest));
|
||||
}
|
||||
|
||||
// makes sure that any dependent actions happen in the executor
|
||||
res = res.whenCompleteAsync((r, t) -> { /* do nothing */}, executor);
|
||||
|
||||
// makes sure that any dependent actions happen in the CF default
|
||||
// executor. This is only needed for sendAsync(...), when
|
||||
// exchangeExecutor is non-null.
|
||||
if (exchangeExecutor != null) {
|
||||
res = res.whenCompleteAsync((r, t) -> { /* do nothing */}, ASYNC_POOL);
|
||||
}
|
||||
return res;
|
||||
} catch(Throwable t) {
|
||||
unreference();
|
||||
@ -654,6 +720,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
}
|
||||
|
||||
synchronized void shutdown() {
|
||||
Log.logTrace("{0}: shutting down", getName());
|
||||
if (debug.on()) debug.log("SelectorManager shutting down");
|
||||
closed = true;
|
||||
try {
|
||||
@ -670,6 +737,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
List<AsyncEvent> readyList = new ArrayList<>();
|
||||
List<Runnable> resetList = new ArrayList<>();
|
||||
try {
|
||||
if (Log.channel()) Log.logChannel(getName() + ": starting");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
synchronized (this) {
|
||||
assert errorList.isEmpty();
|
||||
@ -706,7 +774,7 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
throw new IOException("Channel closed");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.logTrace("HttpClientImpl: " + e);
|
||||
Log.logTrace("{0}: {1}", getName(), e);
|
||||
if (debug.on())
|
||||
debug.log("Got " + e.getClass().getName()
|
||||
+ " while handling registration events");
|
||||
@ -738,7 +806,9 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
// Check whether client is still alive, and if not,
|
||||
// gracefully stop this thread
|
||||
if (!owner.isReferenced()) {
|
||||
Log.logTrace("HttpClient no longer referenced. Exiting...");
|
||||
Log.logTrace("{0}: {1}",
|
||||
getName(),
|
||||
"HttpClient no longer referenced. Exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -780,7 +850,9 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
// Check whether client is still alive, and if not,
|
||||
// gracefully stop this thread
|
||||
if (!owner.isReferenced()) {
|
||||
Log.logTrace("HttpClient no longer referenced. Exiting...");
|
||||
Log.logTrace("{0}: {1}",
|
||||
getName(),
|
||||
"HttpClient no longer referenced. Exiting...");
|
||||
return;
|
||||
}
|
||||
owner.purgeTimeoutsAndReturnNextDeadline();
|
||||
@ -831,17 +903,18 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
//e.printStackTrace();
|
||||
if (!closed) {
|
||||
// This terminates thread. So, better just print stack trace
|
||||
String err = Utils.stackTrace(e);
|
||||
Log.logError("HttpClientImpl: fatal error: " + err);
|
||||
Log.logError("{0}: {1}: {2}", getName(),
|
||||
"HttpClientImpl shutting down due to fatal error", err);
|
||||
}
|
||||
if (debug.on()) debug.log("shutting down", e);
|
||||
if (Utils.ASSERTIONSENABLED && !debug.on()) {
|
||||
e.printStackTrace(System.err); // always print the stack
|
||||
}
|
||||
} finally {
|
||||
if (Log.channel()) Log.logChannel(getName() + ": stopping");
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
@ -1013,13 +1086,15 @@ final class HttpClientImpl extends HttpClient implements Trackable {
|
||||
return Optional.ofNullable(authenticator);
|
||||
}
|
||||
|
||||
/*package-private*/ final Executor theExecutor() {
|
||||
return executor;
|
||||
/*package-private*/ final DelegatingExecutor theExecutor() {
|
||||
return delegatingExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Optional<Executor> executor() {
|
||||
return isDefaultExecutor ? Optional.empty() : Optional.of(executor);
|
||||
return isDefaultExecutor
|
||||
? Optional.empty()
|
||||
: Optional.of(delegatingExecutor.delegate());
|
||||
}
|
||||
|
||||
ConnectionPool connectionPool() {
|
||||
|
@ -27,7 +27,6 @@ package jdk.internal.net.http;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
@ -250,7 +249,7 @@ abstract class HttpConnection implements Closeable {
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
BiPredicate<String,List<String>> headerFilter(HttpRequestImpl request) {
|
||||
BiPredicate<String,String> headerFilter(HttpRequestImpl request) {
|
||||
if (isTunnel()) {
|
||||
// talking to a server through a proxy tunnel
|
||||
// don't send proxy-* headers to a plain server
|
||||
@ -280,12 +279,12 @@ abstract class HttpConnection implements Closeable {
|
||||
// start with "proxy-"
|
||||
private static HttpHeaders proxyTunnelHeaders(HttpRequestImpl request) {
|
||||
Map<String, List<String>> combined = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
combined.putAll(request.getSystemHeaders().map());
|
||||
combined.putAll(request.getSystemHeadersBuilder().map());
|
||||
combined.putAll(request.headers().map()); // let user override system
|
||||
|
||||
// keep only proxy-* - and also strip authorization headers
|
||||
// for disabled schemes
|
||||
return ImmutableHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
|
||||
return HttpHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
|
||||
}
|
||||
|
||||
/* Returns either a plain HTTP connection or a plain tunnelling connection
|
||||
|
@ -32,9 +32,9 @@ import java.util.Optional;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublisher;
|
||||
import jdk.internal.net.http.common.HttpHeadersImpl;
|
||||
|
||||
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static jdk.internal.net.http.common.Utils.isValidName;
|
||||
import static jdk.internal.net.http.common.Utils.isValidValue;
|
||||
@ -42,7 +42,7 @@ import static jdk.internal.net.http.common.Utils.newIAE;
|
||||
|
||||
public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
|
||||
private HttpHeadersImpl userHeaders;
|
||||
private HttpHeadersBuilder headersBuilder;
|
||||
private URI uri;
|
||||
private String method;
|
||||
private boolean expectContinue;
|
||||
@ -54,13 +54,13 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
requireNonNull(uri, "uri must be non-null");
|
||||
checkURI(uri);
|
||||
this.uri = uri;
|
||||
this.userHeaders = new HttpHeadersImpl();
|
||||
this.headersBuilder = new HttpHeadersBuilder();
|
||||
this.method = "GET"; // default, as per spec
|
||||
this.version = Optional.empty();
|
||||
}
|
||||
|
||||
public HttpRequestBuilderImpl() {
|
||||
this.userHeaders = new HttpHeadersImpl();
|
||||
this.headersBuilder = new HttpHeadersBuilder();
|
||||
this.method = "GET"; // default, as per spec
|
||||
this.version = Optional.empty();
|
||||
}
|
||||
@ -90,7 +90,7 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
public HttpRequestBuilderImpl copy() {
|
||||
HttpRequestBuilderImpl b = new HttpRequestBuilderImpl();
|
||||
b.uri = this.uri;
|
||||
b.userHeaders = this.userHeaders.deepCopy();
|
||||
b.headersBuilder = this.headersBuilder.structuralCopy();
|
||||
b.method = this.method;
|
||||
b.expectContinue = this.expectContinue;
|
||||
b.bodyPublisher = bodyPublisher;
|
||||
@ -106,7 +106,7 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
if (!isValidName(name)) {
|
||||
throw newIAE("invalid header name: \"%s\"", name);
|
||||
}
|
||||
if (!Utils.ALLOWED_HEADERS.test(name)) {
|
||||
if (!Utils.ALLOWED_HEADERS.test(name, null)) {
|
||||
throw newIAE("restricted header name: \"%s\"", name);
|
||||
}
|
||||
if (!isValidValue(value)) {
|
||||
@ -117,14 +117,14 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
@Override
|
||||
public HttpRequestBuilderImpl setHeader(String name, String value) {
|
||||
checkNameAndValue(name, value);
|
||||
userHeaders.setHeader(name, value);
|
||||
headersBuilder.setHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequestBuilderImpl header(String name, String value) {
|
||||
checkNameAndValue(name, value);
|
||||
userHeaders.addHeader(name, value);
|
||||
headersBuilder.addHeader(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -155,7 +155,7 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
HttpHeadersImpl headers() { return userHeaders; }
|
||||
HttpHeadersBuilder headersBuilder() { return headersBuilder; }
|
||||
|
||||
URI uri() { return uri; }
|
||||
|
||||
@ -213,6 +213,13 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder {
|
||||
|
||||
@Override
|
||||
public HttpRequest build() {
|
||||
if (uri == null)
|
||||
throw new IllegalStateException("uri is null");
|
||||
assert method != null;
|
||||
return new ImmutableHttpRequest(this);
|
||||
}
|
||||
|
||||
public HttpRequestImpl buildForWebSocket() {
|
||||
if (uri == null)
|
||||
throw new IllegalStateException("uri is null");
|
||||
assert method != null;
|
||||
|
@ -41,16 +41,16 @@ import java.util.Optional;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpRequest;
|
||||
import jdk.internal.net.http.common.HttpHeadersImpl;
|
||||
import jdk.internal.net.http.common.HttpHeadersBuilder;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import jdk.internal.net.http.websocket.WebSocketRequest;
|
||||
|
||||
import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
|
||||
|
||||
class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
public class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
|
||||
private final HttpHeaders userHeaders;
|
||||
private final HttpHeadersImpl systemHeaders;
|
||||
private final HttpHeadersBuilder systemHeadersBuilder;
|
||||
private final URI uri;
|
||||
private volatile Proxy proxy; // ensure safe publishing
|
||||
private final InetSocketAddress authority; // only used when URI not specified
|
||||
@ -78,8 +78,8 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
public HttpRequestImpl(HttpRequestBuilderImpl builder) {
|
||||
String method = builder.method();
|
||||
this.method = method == null ? "GET" : method;
|
||||
this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS);
|
||||
this.systemHeaders = new HttpHeadersImpl();
|
||||
this.userHeaders = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);
|
||||
this.systemHeadersBuilder = new HttpHeadersBuilder();
|
||||
this.uri = builder.uri();
|
||||
assert uri != null;
|
||||
this.proxy = null;
|
||||
@ -106,21 +106,23 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
"uri must be non null");
|
||||
Duration timeout = request.timeout().orElse(null);
|
||||
this.method = method == null ? "GET" : method;
|
||||
this.userHeaders = ImmutableHeaders.validate(request.headers());
|
||||
this.userHeaders = HttpHeaders.of(request.headers().map(), Utils.VALIDATE_USER_HEADER);
|
||||
if (request instanceof HttpRequestImpl) {
|
||||
// all cases exception WebSocket should have a new system headers
|
||||
this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
|
||||
if (isWebSocket) {
|
||||
this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
|
||||
this.systemHeadersBuilder = ((HttpRequestImpl)request).systemHeadersBuilder;
|
||||
} else {
|
||||
this.systemHeaders = new HttpHeadersImpl();
|
||||
this.systemHeadersBuilder = new HttpHeadersBuilder();
|
||||
}
|
||||
} else {
|
||||
HttpRequestBuilderImpl.checkURI(requestURI);
|
||||
checkTimeout(timeout);
|
||||
this.systemHeaders = new HttpHeadersImpl();
|
||||
this.systemHeadersBuilder = new HttpHeadersBuilder();
|
||||
}
|
||||
if (!userHeaders.firstValue("User-Agent").isPresent()) {
|
||||
this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);
|
||||
}
|
||||
this.systemHeaders.setHeader("User-Agent", USER_AGENT);
|
||||
this.uri = requestURI;
|
||||
if (isWebSocket) {
|
||||
// WebSocket determines and sets the proxy itself
|
||||
@ -169,8 +171,10 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
this.method = method == null? "GET" : method;
|
||||
this.userHeaders = other.userHeaders;
|
||||
this.isWebSocket = other.isWebSocket;
|
||||
this.systemHeaders = new HttpHeadersImpl();
|
||||
this.systemHeaders.setHeader("User-Agent", USER_AGENT);
|
||||
this.systemHeadersBuilder = new HttpHeadersBuilder();
|
||||
if (!userHeaders.firstValue("User-Agent").isPresent()) {
|
||||
this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);
|
||||
}
|
||||
this.uri = uri;
|
||||
this.proxy = other.proxy;
|
||||
this.expectContinue = other.expectContinue;
|
||||
@ -189,8 +193,8 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
// to the connection pool (we might need to revisit this constructor later)
|
||||
assert "CONNECT".equalsIgnoreCase(method);
|
||||
this.method = method;
|
||||
this.systemHeaders = new HttpHeadersImpl();
|
||||
this.userHeaders = ImmutableHeaders.of(headers);
|
||||
this.systemHeadersBuilder = new HttpHeadersBuilder();
|
||||
this.userHeaders = headers;
|
||||
this.uri = URI.create("socket://" + authority.getHostString() + ":"
|
||||
+ Integer.toString(authority.getPort()) + "/");
|
||||
this.proxy = null;
|
||||
@ -218,14 +222,14 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
* parent.
|
||||
*/
|
||||
static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
|
||||
HttpHeadersImpl headers)
|
||||
HttpHeaders headers)
|
||||
throws IOException
|
||||
{
|
||||
return new HttpRequestImpl(parent, headers);
|
||||
}
|
||||
|
||||
// only used for push requests
|
||||
private HttpRequestImpl(HttpRequestImpl parent, HttpHeadersImpl headers)
|
||||
private HttpRequestImpl(HttpRequestImpl parent, HttpHeaders headers)
|
||||
throws IOException
|
||||
{
|
||||
this.method = headers.firstValue(":method")
|
||||
@ -240,8 +244,8 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
sb.append(scheme).append("://").append(authority).append(path);
|
||||
this.uri = URI.create(sb.toString());
|
||||
this.proxy = null;
|
||||
this.userHeaders = ImmutableHeaders.of(headers.map(), ALLOWED_HEADERS);
|
||||
this.systemHeaders = parent.systemHeaders;
|
||||
this.userHeaders = HttpHeaders.of(headers.map(), ALLOWED_HEADERS);
|
||||
this.systemHeadersBuilder = parent.systemHeadersBuilder;
|
||||
this.expectContinue = parent.expectContinue;
|
||||
this.secure = parent.secure;
|
||||
this.requestPublisher = parent.requestPublisher;
|
||||
@ -264,9 +268,9 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
InetSocketAddress authority() { return authority; }
|
||||
|
||||
void setH2Upgrade(Http2ClientImpl h2client) {
|
||||
systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
|
||||
systemHeaders.setHeader("Upgrade", "h2c");
|
||||
systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
|
||||
systemHeadersBuilder.setHeader("Connection", "Upgrade, HTTP2-Settings");
|
||||
systemHeadersBuilder.setHeader("Upgrade", "h2c");
|
||||
systemHeadersBuilder.setHeader("HTTP2-Settings", h2client.getSettingsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -332,18 +336,18 @@ class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
|
||||
|
||||
HttpHeaders getUserHeaders() { return userHeaders; }
|
||||
|
||||
HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
|
||||
HttpHeadersBuilder getSystemHeadersBuilder() { return systemHeadersBuilder; }
|
||||
|
||||
@Override
|
||||
public Optional<HttpClient.Version> version() { return version; }
|
||||
|
||||
void addSystemHeader(String name, String value) {
|
||||
systemHeaders.addHeader(name, value);
|
||||
systemHeadersBuilder.addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSystemHeader(String name, String value) {
|
||||
systemHeaders.setHeader(name, value);
|
||||
systemHeadersBuilder.setHeader(name, value);
|
||||
}
|
||||
|
||||
InetSocketAddress getAddress() {
|
||||
|
@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2018, 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
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
import java.net.http.HttpHeaders;
|
||||
import jdk.internal.net.http.common.HttpHeadersImpl;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
final class ImmutableHeaders extends HttpHeaders {
|
||||
|
||||
private final Map<String, List<String>> map;
|
||||
|
||||
public static ImmutableHeaders empty() {
|
||||
return of(emptyMap());
|
||||
}
|
||||
|
||||
public static ImmutableHeaders of(Map<String, List<String>> src) {
|
||||
return of(src, x -> true);
|
||||
}
|
||||
|
||||
public static ImmutableHeaders of(HttpHeaders headers) {
|
||||
return (headers instanceof ImmutableHeaders)
|
||||
? (ImmutableHeaders)headers
|
||||
: of(headers.map());
|
||||
}
|
||||
|
||||
static ImmutableHeaders validate(HttpHeaders headers) {
|
||||
if (headers instanceof ImmutableHeaders) {
|
||||
return of(headers);
|
||||
}
|
||||
if (headers instanceof HttpHeadersImpl) {
|
||||
return of(headers);
|
||||
}
|
||||
Map<String, List<String>> map = headers.map();
|
||||
return new ImmutableHeaders(map, Utils.VALIDATE_USER_HEADER);
|
||||
}
|
||||
|
||||
public static ImmutableHeaders of(Map<String, List<String>> src,
|
||||
Predicate<? super String> keyAllowed) {
|
||||
requireNonNull(src, "src");
|
||||
requireNonNull(keyAllowed, "keyAllowed");
|
||||
return new ImmutableHeaders(src, headerAllowed(keyAllowed));
|
||||
}
|
||||
|
||||
public static ImmutableHeaders of(Map<String, List<String>> src,
|
||||
BiPredicate<? super String, ? super List<String>> headerAllowed) {
|
||||
requireNonNull(src, "src");
|
||||
requireNonNull(headerAllowed, "headerAllowed");
|
||||
return new ImmutableHeaders(src, headerAllowed);
|
||||
}
|
||||
|
||||
private ImmutableHeaders(Map<String, List<String>> src,
|
||||
BiPredicate<? super String, ? super List<String>> headerAllowed) {
|
||||
Map<String, List<String>> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
src.entrySet().stream()
|
||||
.forEach(e -> addIfAllowed(e, headerAllowed, m));
|
||||
this.map = unmodifiableMap(m);
|
||||
}
|
||||
|
||||
private static void addIfAllowed(Map.Entry<String, List<String>> e,
|
||||
BiPredicate<? super String, ? super List<String>> headerAllowed,
|
||||
Map<String, List<String>> map) {
|
||||
String key = e.getKey();
|
||||
List<String> values = unmodifiableValues(e.getValue());
|
||||
if (headerAllowed.test(key, values)) {
|
||||
map.put(key, values);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> unmodifiableValues(List<String> values) {
|
||||
return unmodifiableList(new ArrayList<>(Objects.requireNonNull(values)));
|
||||
}
|
||||
|
||||
private static BiPredicate<String, List<String>> headerAllowed(Predicate<? super String> keyAllowed) {
|
||||
return (n,v) -> keyAllowed.test(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> map() {
|
||||
return map;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 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
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.net.http.HttpClient.Version;
|
||||
import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;
|
||||
|
||||
final class ImmutableHttpRequest extends HttpRequest {
|
||||
|
||||
private final String method;
|
||||
private final URI uri;
|
||||
private final HttpHeaders headers;
|
||||
private final Optional<BodyPublisher> requestPublisher;
|
||||
private final boolean expectContinue;
|
||||
private final Optional<Duration> timeout;
|
||||
private final Optional<Version> version;
|
||||
|
||||
/** Creates an ImmutableHttpRequest from the given builder. */
|
||||
ImmutableHttpRequest(HttpRequestBuilderImpl builder) {
|
||||
this.method = Objects.requireNonNull(builder.method());
|
||||
this.uri = Objects.requireNonNull(builder.uri());
|
||||
this.headers = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);
|
||||
this.requestPublisher = Optional.ofNullable(builder.bodyPublisher());
|
||||
this.expectContinue = builder.expectContinue();
|
||||
this.timeout = Optional.ofNullable(builder.timeout());
|
||||
this.version = Objects.requireNonNull(builder.version());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String method() { return method; }
|
||||
|
||||
@Override
|
||||
public URI uri() { return uri; }
|
||||
|
||||
@Override
|
||||
public HttpHeaders headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BodyPublisher> bodyPublisher() { return requestPublisher; }
|
||||
|
||||
@Override
|
||||
public boolean expectContinue() { return expectContinue; }
|
||||
|
||||
@Override
|
||||
public Optional<Duration> timeout() { return timeout; }
|
||||
|
||||
@Override
|
||||
public Optional<Version> version() { return version; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uri.toString() + " " + method;
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.ConnectException;
|
||||
import java.time.Duration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
@ -69,7 +69,7 @@ class MultiExchange<T> {
|
||||
final AccessControlContext acc;
|
||||
final HttpClientImpl client;
|
||||
final HttpResponse.BodyHandler<T> responseHandler;
|
||||
final Executor executor;
|
||||
final HttpClientImpl.DelegatingExecutor executor;
|
||||
final AtomicInteger attempts = new AtomicInteger();
|
||||
HttpRequestImpl currentreq; // used for retries & redirect
|
||||
HttpRequestImpl previousreq; // used for retries & redirect
|
||||
@ -124,8 +124,8 @@ class MultiExchange<T> {
|
||||
|
||||
if (pushPromiseHandler != null) {
|
||||
Executor executor = acc == null
|
||||
? this.executor
|
||||
: new PrivilegedExecutor(this.executor, acc);
|
||||
? this.executor.delegate()
|
||||
: new PrivilegedExecutor(this.executor.delegate(), acc);
|
||||
this.pushGroup = new PushGroup<>(pushPromiseHandler, request, executor);
|
||||
} else {
|
||||
pushGroup = null;
|
||||
@ -193,7 +193,7 @@ class MultiExchange<T> {
|
||||
getExchange().cancel(cause);
|
||||
}
|
||||
|
||||
public CompletableFuture<HttpResponse<T>> responseAsync() {
|
||||
public CompletableFuture<HttpResponse<T>> responseAsync(Executor executor) {
|
||||
CompletableFuture<Void> start = new MinimalFuture<>();
|
||||
CompletableFuture<HttpResponse<T>> cf = responseAsync0(start);
|
||||
start.completeAsync( () -> null, executor); // trigger execution
|
||||
@ -285,13 +285,22 @@ class MultiExchange<T> {
|
||||
|
||||
private static boolean retryPostValue() {
|
||||
String s = Utils.getNetProperty("jdk.httpclient.enableAllMethodRetry");
|
||||
if (s == "" || "true".equals(s))
|
||||
return true;
|
||||
return false;
|
||||
if (s == null)
|
||||
return false;
|
||||
return s.isEmpty() ? true : Boolean.parseBoolean(s);
|
||||
}
|
||||
|
||||
private static boolean retryConnect() {
|
||||
String s = Utils.getNetProperty("jdk.httpclient.disableRetryConnect");
|
||||
if (s == null)
|
||||
return false;
|
||||
return s.isEmpty() ? true : Boolean.parseBoolean(s);
|
||||
}
|
||||
|
||||
/** True if ALL ( even non-idempotent ) requests can be automatic retried. */
|
||||
private static final boolean RETRY_ALWAYS = retryPostValue();
|
||||
/** True if ConnectException should cause a retry. Enabled by default */
|
||||
private static final boolean RETRY_CONNECT = retryConnect();
|
||||
|
||||
/** Returns true is given request has an idempotent method. */
|
||||
private static boolean isIdempotentRequest(HttpRequest request) {
|
||||
@ -307,13 +316,23 @@ class MultiExchange<T> {
|
||||
|
||||
/** Returns true if the given request can be automatically retried. */
|
||||
private static boolean canRetryRequest(HttpRequest request) {
|
||||
if (isIdempotentRequest(request))
|
||||
return true;
|
||||
if (RETRY_ALWAYS)
|
||||
return true;
|
||||
if (isIdempotentRequest(request))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean retryOnFailure(Throwable t) {
|
||||
return t instanceof ConnectionExpiredException
|
||||
|| (RETRY_CONNECT && (t instanceof ConnectException));
|
||||
}
|
||||
|
||||
private Throwable retryCause(Throwable t) {
|
||||
Throwable cause = t instanceof ConnectionExpiredException ? t.getCause() : t;
|
||||
return cause == null ? t : cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a Throwable and returns a suitable CompletableFuture that is
|
||||
* completed exceptionally, or null.
|
||||
@ -326,21 +345,20 @@ class MultiExchange<T> {
|
||||
}
|
||||
if (cancelled && t instanceof IOException) {
|
||||
t = new HttpTimeoutException("request timed out");
|
||||
} else if (t instanceof ConnectionExpiredException) {
|
||||
Throwable cause = t;
|
||||
if (t.getCause() != null) {
|
||||
cause = t.getCause(); // unwrap the ConnectionExpiredException
|
||||
}
|
||||
} else if (retryOnFailure(t)) {
|
||||
Throwable cause = retryCause(t);
|
||||
|
||||
if (!canRetryRequest(currentreq)) {
|
||||
return failedFuture(cause); // fails with original cause
|
||||
if (!(t instanceof ConnectException)) {
|
||||
if (!canRetryRequest(currentreq)) {
|
||||
return failedFuture(cause); // fails with original cause
|
||||
}
|
||||
}
|
||||
|
||||
// allow the retry mechanism to do its work
|
||||
retryCause = cause;
|
||||
if (!expiredOnce) {
|
||||
if (debug.on())
|
||||
debug.log("ConnectionExpiredException (async): retrying...", t);
|
||||
debug.log(t.getClass().getSimpleName() + " (async): retrying...", t);
|
||||
expiredOnce = true;
|
||||
// The connection was abruptly closed.
|
||||
// We return null to retry the same request a second time.
|
||||
@ -350,9 +368,11 @@ class MultiExchange<T> {
|
||||
previousreq = currentreq;
|
||||
return null;
|
||||
} else {
|
||||
if (debug.on())
|
||||
debug.log("ConnectionExpiredException (async): already retried once.", t);
|
||||
if (t.getCause() != null) t = t.getCause();
|
||||
if (debug.on()) {
|
||||
debug.log(t.getClass().getSimpleName()
|
||||
+ " (async): already retried once.", t);
|
||||
}
|
||||
t = cause;
|
||||
}
|
||||
}
|
||||
return failedFuture(t);
|
||||
@ -364,9 +384,10 @@ class MultiExchange<T> {
|
||||
}
|
||||
@Override
|
||||
public void handle() {
|
||||
if (debug.on())
|
||||
if (debug.on()) {
|
||||
debug.log("Cancelling MultiExchange due to timeout for request %s",
|
||||
request);
|
||||
request);
|
||||
}
|
||||
cancel(new HttpTimeoutException("request timed out"));
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,8 @@
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.StandardSocketOptions;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
@ -91,14 +89,16 @@ class PlainHttpConnection extends HttpConnection {
|
||||
// complete async since the event runs on the SelectorManager thread
|
||||
cf.completeAsync(() -> null, client().theExecutor());
|
||||
} catch (Throwable e) {
|
||||
client().theExecutor().execute( () -> cf.completeExceptionally(e));
|
||||
Throwable t = Utils.toConnectException(e);
|
||||
client().theExecutor().execute( () -> cf.completeExceptionally(t));
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort(IOException ioe) {
|
||||
close();
|
||||
client().theExecutor().execute( () -> cf.completeExceptionally(ioe));
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ class PlainHttpConnection extends HttpConnection {
|
||||
try {
|
||||
finished = AccessController.doPrivileged(pa);
|
||||
} catch (PrivilegedActionException e) {
|
||||
cf.completeExceptionally(e.getCause());
|
||||
throw e.getCause();
|
||||
}
|
||||
if (finished) {
|
||||
if (debug.on()) debug.log("connect finished without blocking");
|
||||
@ -125,7 +125,13 @@ class PlainHttpConnection extends HttpConnection {
|
||||
client().registerEvent(new ConnectEvent(cf));
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
cf.completeExceptionally(throwable);
|
||||
cf.completeExceptionally(Utils.toConnectException(throwable));
|
||||
try {
|
||||
close();
|
||||
} catch (Exception x) {
|
||||
if (debug.on())
|
||||
debug.log("Failed to close channel after unsuccessful connect");
|
||||
}
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
|
@ -43,6 +43,13 @@ class RedirectFilter implements HeaderFilter {
|
||||
static final int DEFAULT_MAX_REDIRECTS = 5;
|
||||
URI uri;
|
||||
|
||||
/*
|
||||
* NOT_MODIFIED status code results from a conditional GET where
|
||||
* the server does not (must not) return a response body because
|
||||
* the condition specified in the request disallows it
|
||||
*/
|
||||
static final int HTTP_NOT_MODIFIED = 304;
|
||||
|
||||
static final int max_redirects = Utils.getIntegerNetProperty(
|
||||
"jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
|
||||
);
|
||||
@ -91,6 +98,10 @@ class RedirectFilter implements HeaderFilter {
|
||||
if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rcode == HTTP_NOT_MODIFIED)
|
||||
return null;
|
||||
|
||||
if (rcode >= 300 && rcode <= 399) {
|
||||
URI redir = getRedirectedURI(r.headers());
|
||||
String newMethod = redirectedMethod(rcode, method);
|
||||
|
@ -64,7 +64,7 @@ class Response {
|
||||
int statusCode,
|
||||
HttpClient.Version version,
|
||||
boolean isConnectResponse) {
|
||||
this.headers = ImmutableHeaders.of(headers);
|
||||
this.headers = headers;
|
||||
this.request = req;
|
||||
this.version = version;
|
||||
this.exchange = exchange;
|
||||
|
@ -26,7 +26,6 @@
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -34,9 +33,9 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* Implements chunked/fixed transfer encodings of HTTP/1.1 responses.
|
||||
@ -95,6 +94,9 @@ class ResponseContent {
|
||||
|
||||
interface BodyParser extends Consumer<ByteBuffer> {
|
||||
void onSubscribe(AbstractSubscription sub);
|
||||
// A current-state message suitable for inclusion in an exception
|
||||
// detail message.
|
||||
String currentStateMessage();
|
||||
}
|
||||
|
||||
// Returns a parser that will take care of parsing the received byte
|
||||
@ -144,6 +146,11 @@ class ResponseContent {
|
||||
pusher.onSubscribe(this.sub = sub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String currentStateMessage() {
|
||||
return format("chunked transfer encoding, state: %s", state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ByteBuffer b) {
|
||||
if (closedExceptionally != null) {
|
||||
@ -424,6 +431,12 @@ class ResponseContent {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String currentStateMessage() {
|
||||
return format("fixed content-length: %d, bytes received: %d",
|
||||
contentLength, contentLength - remaining);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ByteBuffer b) {
|
||||
if (closedExceptionally != null) {
|
||||
|
@ -41,6 +41,7 @@ import java.util.function.Supplier;
|
||||
import jdk.internal.net.http.common.BufferSupplier;
|
||||
import jdk.internal.net.http.common.Demand;
|
||||
import jdk.internal.net.http.common.FlowTube;
|
||||
import jdk.internal.net.http.common.Log;
|
||||
import jdk.internal.net.http.common.Logger;
|
||||
import jdk.internal.net.http.common.SequentialScheduler;
|
||||
import jdk.internal.net.http.common.SequentialScheduler.DeferredCompleter;
|
||||
@ -149,6 +150,10 @@ final class SocketTube implements FlowTube {
|
||||
void signalClosed() {
|
||||
// Ensures that the subscriber will be terminated and that future
|
||||
// subscribers will be notified when the connection is closed.
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Connection close signalled: connection closed locally ({0})",
|
||||
channelDescr());
|
||||
}
|
||||
readPublisher.subscriptionImpl.signalError(
|
||||
new IOException("connection closed locally"));
|
||||
}
|
||||
@ -364,6 +369,10 @@ final class SocketTube implements FlowTube {
|
||||
void startSubscription() {
|
||||
try {
|
||||
if (debug.on()) debug.log("write: starting subscription");
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Start requesting bytes for writing to channel: {0}",
|
||||
channelDescr());
|
||||
}
|
||||
assert client.isSelectorThread();
|
||||
// make sure read registrations are handled before;
|
||||
readPublisher.subscriptionImpl.handlePending();
|
||||
@ -409,6 +418,10 @@ final class SocketTube implements FlowTube {
|
||||
|
||||
void signalError(Throwable error) {
|
||||
debug.log(() -> "write error: " + error);
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Failed to write to channel ({0}: {1})",
|
||||
channelDescr(), error);
|
||||
}
|
||||
completed = true;
|
||||
readPublisher.signalError(error);
|
||||
}
|
||||
@ -455,6 +468,7 @@ final class SocketTube implements FlowTube {
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (debug.on()) debug.log("write: cancel");
|
||||
dropSubscription();
|
||||
upstreamSubscription.cancel();
|
||||
}
|
||||
@ -558,6 +572,10 @@ final class SocketTube implements FlowTube {
|
||||
if (!errorRef.compareAndSet(null, error)) {
|
||||
return;
|
||||
}
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Error signalled on channel {0}: {1}",
|
||||
channelDescr(), error);
|
||||
}
|
||||
subscriptionImpl.handleError();
|
||||
}
|
||||
|
||||
@ -665,6 +683,7 @@ final class SocketTube implements FlowTube {
|
||||
final void handleSubscribeEvent() {
|
||||
assert client.isSelectorThread();
|
||||
debug.log("subscribe event raised");
|
||||
if (Log.channel()) Log.logChannel("Start reading from {0}", channelDescr());
|
||||
readScheduler.runOrSchedule();
|
||||
if (readScheduler.isStopped() || completed) {
|
||||
// if already completed or stopped we can handle any
|
||||
@ -702,6 +721,10 @@ final class SocketTube implements FlowTube {
|
||||
@Override
|
||||
public final void cancel() {
|
||||
pauseReadEvent();
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Read subscription cancelled for channel {0}",
|
||||
channelDescr());
|
||||
}
|
||||
readScheduler.stop();
|
||||
}
|
||||
|
||||
@ -726,6 +749,10 @@ final class SocketTube implements FlowTube {
|
||||
return;
|
||||
}
|
||||
if (debug.on()) debug.log("got read error: " + error);
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Read error signalled on channel {0}: {1}",
|
||||
channelDescr(), error);
|
||||
}
|
||||
readScheduler.runOrSchedule();
|
||||
}
|
||||
|
||||
@ -772,6 +799,10 @@ final class SocketTube implements FlowTube {
|
||||
if (debug.on())
|
||||
debug.log("Sending error " + error
|
||||
+ " to subscriber " + subscriber);
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Raising error with subscriber for {0}: {1}",
|
||||
channelDescr(), error);
|
||||
}
|
||||
current.errorRef.compareAndSet(null, error);
|
||||
current.signalCompletion();
|
||||
readScheduler.stop();
|
||||
@ -788,6 +819,10 @@ final class SocketTube implements FlowTube {
|
||||
if (bytes == EOF) {
|
||||
if (!completed) {
|
||||
if (debug.on()) debug.log("got read EOF");
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("EOF read from channel: {0}",
|
||||
channelDescr());
|
||||
}
|
||||
completed = true;
|
||||
// safe to pause here because we're finished
|
||||
// anyway.
|
||||
@ -849,6 +884,12 @@ final class SocketTube implements FlowTube {
|
||||
if (debug.on()) debug.log("Unexpected exception in read loop", t);
|
||||
signalError(t);
|
||||
} finally {
|
||||
if (readScheduler.isStopped()) {
|
||||
if (debug.on()) debug.log("Read scheduler stopped");
|
||||
if (Log.channel()) {
|
||||
Log.logChannel("Stopped reading from channel {0}", channelDescr());
|
||||
}
|
||||
}
|
||||
handlePending();
|
||||
}
|
||||
}
|
||||
@ -1238,4 +1279,8 @@ final class SocketTube implements FlowTube {
|
||||
final String dbgString() {
|
||||
return "SocketTube("+id+")";
|
||||
}
|
||||
|
||||
final String channelDescr() {
|
||||
return String.valueOf(channel);
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,9 @@
|
||||
|
||||
package jdk.internal.net.http;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
@ -39,6 +39,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.concurrent.Flow.Subscription;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.net.http.HttpClient;
|
||||
@ -114,8 +115,8 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
final Http2Connection connection;
|
||||
final HttpRequestImpl request;
|
||||
final HeadersConsumer rspHeadersConsumer;
|
||||
final HttpHeadersImpl responseHeaders;
|
||||
final HttpHeadersImpl requestPseudoHeaders;
|
||||
final HttpHeadersBuilder responseHeadersBuilder;
|
||||
final HttpHeaders requestPseudoHeaders;
|
||||
volatile HttpResponse.BodySubscriber<T> responseSubscriber;
|
||||
final HttpRequest.BodyPublisher requestPublisher;
|
||||
volatile RequestSubscriber requestSubscriber;
|
||||
@ -133,6 +134,8 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
private volatile boolean closed;
|
||||
private volatile boolean endStreamSent;
|
||||
|
||||
final AtomicBoolean deRegistered = new AtomicBoolean(false);
|
||||
|
||||
// state flags
|
||||
private boolean requestSent, responseReceived;
|
||||
|
||||
@ -171,7 +174,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
Http2Frame frame = inputQ.peek();
|
||||
if (frame instanceof ResetFrame) {
|
||||
inputQ.remove();
|
||||
handleReset((ResetFrame)frame);
|
||||
handleReset((ResetFrame)frame, subscriber);
|
||||
return;
|
||||
}
|
||||
DataFrame df = (DataFrame)frame;
|
||||
@ -185,6 +188,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
Log.logTrace("responseSubscriber.onComplete");
|
||||
if (debug.on()) debug.log("incoming: onComplete");
|
||||
sched.stop();
|
||||
connection.decrementStreamsCount(streamid);
|
||||
subscriber.onComplete();
|
||||
onCompleteCalled = true;
|
||||
setEndStreamReceived();
|
||||
@ -198,6 +202,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
Log.logTrace("responseSubscriber.onComplete");
|
||||
if (debug.on()) debug.log("incoming: onComplete");
|
||||
sched.stop();
|
||||
connection.decrementStreamsCount(streamid);
|
||||
subscriber.onComplete();
|
||||
onCompleteCalled = true;
|
||||
setEndStreamReceived();
|
||||
@ -251,6 +256,10 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
return true; // end of stream
|
||||
}
|
||||
|
||||
boolean deRegister() {
|
||||
return deRegistered.compareAndSet(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
|
||||
boolean returnConnectionToPool,
|
||||
@ -258,6 +267,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
{
|
||||
try {
|
||||
Log.logTrace("Reading body on stream {0}", streamid);
|
||||
debug.log("Getting BodySubscriber for: " + response);
|
||||
BodySubscriber<T> bodySubscriber = handler.apply(new ResponseInfoImpl(response));
|
||||
CompletableFuture<T> cf = receiveData(bodySubscriber, executor);
|
||||
|
||||
@ -337,10 +347,9 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
this.windowController = windowController;
|
||||
this.request = e.request();
|
||||
this.requestPublisher = request.requestPublisher; // may be null
|
||||
responseHeaders = new HttpHeadersImpl();
|
||||
rspHeadersConsumer = new HeadersConsumer();
|
||||
this.requestPseudoHeaders = new HttpHeadersImpl();
|
||||
// NEW
|
||||
this.responseHeadersBuilder = new HttpHeadersBuilder();
|
||||
this.rspHeadersConsumer = new HeadersConsumer();
|
||||
this.requestPseudoHeaders = createPseudoHeaders(request);
|
||||
this.windowUpdater = new StreamWindowUpdateSender(connection);
|
||||
}
|
||||
|
||||
@ -391,6 +400,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
}
|
||||
|
||||
protected void handleResponse() throws IOException {
|
||||
HttpHeaders responseHeaders = responseHeadersBuilder.build();
|
||||
responseCode = (int)responseHeaders
|
||||
.firstValueAsLong(":status")
|
||||
.orElseThrow(() -> new IOException("no statuscode in response"));
|
||||
@ -424,25 +434,57 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
} else if (closed) {
|
||||
Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
|
||||
} else {
|
||||
// put it in the input queue in order to read all
|
||||
// pending data frames first. Indeed, a server may send
|
||||
// RST_STREAM after sending END_STREAM, in which case we should
|
||||
// ignore it. However, we won't know if we have received END_STREAM
|
||||
// or not until all pending data frames are read.
|
||||
receiveResetFrame(frame);
|
||||
// RST_STREAM was pushed to the queue. It will be handled by
|
||||
// asyncReceive after all pending data frames have been
|
||||
// processed.
|
||||
Log.logTrace("RST_STREAM pushed in queue for stream {0}", streamid);
|
||||
Flow.Subscriber<?> subscriber =
|
||||
responseSubscriber == null ? pendingResponseSubscriber : responseSubscriber;
|
||||
if (response == null && subscriber == null) {
|
||||
// we haven't receive the headers yet, and won't receive any!
|
||||
// handle reset now.
|
||||
handleReset(frame, subscriber);
|
||||
} else {
|
||||
// put it in the input queue in order to read all
|
||||
// pending data frames first. Indeed, a server may send
|
||||
// RST_STREAM after sending END_STREAM, in which case we should
|
||||
// ignore it. However, we won't know if we have received END_STREAM
|
||||
// or not until all pending data frames are read.
|
||||
receiveResetFrame(frame);
|
||||
// RST_STREAM was pushed to the queue. It will be handled by
|
||||
// asyncReceive after all pending data frames have been
|
||||
// processed.
|
||||
Log.logTrace("RST_STREAM pushed in queue for stream {0}", streamid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleReset(ResetFrame frame) {
|
||||
void handleReset(ResetFrame frame, Flow.Subscriber<?> subscriber) {
|
||||
Log.logTrace("Handling RST_STREAM on stream {0}", streamid);
|
||||
if (!closed) {
|
||||
close();
|
||||
int error = frame.getErrorCode();
|
||||
completeResponseExceptionally(new IOException(ErrorFrame.stringForCode(error)));
|
||||
synchronized (this) {
|
||||
if (closed) {
|
||||
if (debug.on()) debug.log("Stream already closed: ignoring RESET");
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
try {
|
||||
int error = frame.getErrorCode();
|
||||
IOException e = new IOException("Received RST_STREAM: "
|
||||
+ ErrorFrame.stringForCode(error));
|
||||
if (errorRef.compareAndSet(null, e)) {
|
||||
if (subscriber != null) {
|
||||
subscriber.onError(e);
|
||||
}
|
||||
}
|
||||
completeResponseExceptionally(e);
|
||||
if (!requestBodyCF.isDone()) {
|
||||
requestBodyCF.completeExceptionally(errorRef.get()); // we may be sending the body..
|
||||
}
|
||||
if (responseBodyCF != null) {
|
||||
responseBodyCF.completeExceptionally(errorRef.get());
|
||||
}
|
||||
} finally {
|
||||
connection.decrementStreamsCount(streamid);
|
||||
connection.closeStream(streamid);
|
||||
}
|
||||
} else {
|
||||
Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
|
||||
}
|
||||
@ -535,13 +577,12 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
}
|
||||
|
||||
private OutgoingHeaders<Stream<T>> headerFrame(long contentLength) {
|
||||
HttpHeadersImpl h = request.getSystemHeaders();
|
||||
HttpHeadersBuilder h = request.getSystemHeadersBuilder();
|
||||
if (contentLength > 0) {
|
||||
h.setHeader("content-length", Long.toString(contentLength));
|
||||
}
|
||||
setPseudoHeaderFields();
|
||||
HttpHeaders sysh = filter(h);
|
||||
HttpHeaders userh = filter(request.getUserHeaders());
|
||||
HttpHeaders sysh = filterHeaders(h.build());
|
||||
HttpHeaders userh = filterHeaders(request.getUserHeaders());
|
||||
OutgoingHeaders<Stream<T>> f = new OutgoingHeaders<>(sysh, userh, this);
|
||||
if (contentLength == 0) {
|
||||
f.setFlag(HeadersFrame.END_STREAM);
|
||||
@ -565,7 +606,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
// If nothing needs filtering then we can just use the
|
||||
// original headers.
|
||||
private boolean needsFiltering(HttpHeaders headers,
|
||||
BiPredicate<String, List<String>> filter) {
|
||||
BiPredicate<String, String> filter) {
|
||||
if (filter == Utils.PROXY_TUNNEL_FILTER || filter == Utils.PROXY_FILTER) {
|
||||
// we're either connecting or proxying
|
||||
// slight optimization: we only need to filter out
|
||||
@ -583,18 +624,17 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpHeaders filter(HttpHeaders headers) {
|
||||
private HttpHeaders filterHeaders(HttpHeaders headers) {
|
||||
HttpConnection conn = connection();
|
||||
BiPredicate<String, List<String>> filter =
|
||||
conn.headerFilter(request);
|
||||
BiPredicate<String, String> filter = conn.headerFilter(request);
|
||||
if (needsFiltering(headers, filter)) {
|
||||
return ImmutableHeaders.of(headers.map(), filter);
|
||||
return HttpHeaders.of(headers.map(), filter);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private void setPseudoHeaderFields() {
|
||||
HttpHeadersImpl hdrs = requestPseudoHeaders;
|
||||
private static HttpHeaders createPseudoHeaders(HttpRequest request) {
|
||||
HttpHeadersBuilder hdrs = new HttpHeadersBuilder();
|
||||
String method = request.method();
|
||||
hdrs.setHeader(":method", method);
|
||||
URI uri = request.uri();
|
||||
@ -615,9 +655,10 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
path += "?" + query;
|
||||
}
|
||||
hdrs.setHeader(":path", Utils.encode(path));
|
||||
return hdrs.build();
|
||||
}
|
||||
|
||||
HttpHeadersImpl getRequestPseudoHeaders() {
|
||||
HttpHeaders getRequestPseudoHeaders() {
|
||||
return requestPseudoHeaders;
|
||||
}
|
||||
|
||||
@ -657,6 +698,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
if (streamid > 0) {
|
||||
if (debug.on()) debug.log("Released stream %d", streamid);
|
||||
// remove this stream from the Http2Connection map.
|
||||
connection.decrementStreamsCount(streamid);
|
||||
connection.closeStream(streamid);
|
||||
} else {
|
||||
if (debug.on()) debug.log("Can't release stream %d", streamid);
|
||||
@ -945,14 +987,18 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
if (!cf.isDone()) {
|
||||
Log.logTrace("Completing response (streamid={0}): {1}",
|
||||
streamid, cf);
|
||||
cf.complete(resp);
|
||||
if (debug.on())
|
||||
debug.log("Completing responseCF(%d) with response headers", i);
|
||||
response_cfs.remove(cf);
|
||||
cf.complete(resp);
|
||||
return;
|
||||
} // else we found the previous response: just leave it alone.
|
||||
}
|
||||
cf = MinimalFuture.completedFuture(resp);
|
||||
Log.logTrace("Created completed future (streamid={0}): {1}",
|
||||
streamid, cf);
|
||||
if (debug.on())
|
||||
debug.log("Adding completed responseCF(0) with response headers");
|
||||
response_cfs.add(cf);
|
||||
}
|
||||
}
|
||||
@ -983,8 +1029,8 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
for (int i = 0; i < response_cfs.size(); i++) {
|
||||
CompletableFuture<Response> cf = response_cfs.get(i);
|
||||
if (!cf.isDone()) {
|
||||
cf.completeExceptionally(t);
|
||||
response_cfs.remove(i);
|
||||
cf.completeExceptionally(t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1033,6 +1079,15 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
cancelImpl(cause);
|
||||
}
|
||||
|
||||
void connectionClosing(Throwable cause) {
|
||||
Flow.Subscriber<?> subscriber =
|
||||
responseSubscriber == null ? pendingResponseSubscriber : responseSubscriber;
|
||||
errorRef.compareAndSet(null, cause);
|
||||
if (subscriber != null && !sched.isStopped() && !inputQ.isEmpty()) {
|
||||
sched.runOrSchedule();
|
||||
} else cancelImpl(cause);
|
||||
}
|
||||
|
||||
// This method sends a RST_STREAM frame
|
||||
void cancelImpl(Throwable e) {
|
||||
errorRef.compareAndSet(null, e);
|
||||
@ -1062,7 +1117,14 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
try {
|
||||
// will send a RST_STREAM frame
|
||||
if (streamid != 0) {
|
||||
connection.resetStream(streamid, ResetFrame.CANCEL);
|
||||
connection.decrementStreamsCount(streamid);
|
||||
e = Utils.getCompletionCause(e);
|
||||
if (e instanceof EOFException) {
|
||||
// read EOF: no need to try & send reset
|
||||
connection.closeStream(streamid);
|
||||
} else {
|
||||
connection.resetStream(streamid, ResetFrame.CANCEL);
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.logError(ex);
|
||||
@ -1184,6 +1246,7 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
// create and return the PushResponseImpl
|
||||
@Override
|
||||
protected void handleResponse() {
|
||||
HttpHeaders responseHeaders = responseHeadersBuilder.build();
|
||||
responseCode = (int)responseHeaders
|
||||
.firstValueAsLong(":status")
|
||||
.orElse(-1);
|
||||
@ -1252,17 +1315,18 @@ class Stream<T> extends ExchangeImpl<T> {
|
||||
|
||||
void reset() {
|
||||
super.reset();
|
||||
responseHeaders.clear();
|
||||
responseHeadersBuilder.clear();
|
||||
debug.log("Response builder cleared, ready to receive new headers.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecoded(CharSequence name, CharSequence value)
|
||||
throws UncheckedIOException
|
||||
throws UncheckedIOException
|
||||
{
|
||||
String n = name.toString();
|
||||
String v = value.toString();
|
||||
super.onDecoded(n, v);
|
||||
responseHeaders.addHeader(n, v);
|
||||
responseHeadersBuilder.addHeader(n, v);
|
||||
if (Log.headers() && Log.trace()) {
|
||||
Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}",
|
||||
streamid, n, v);
|
||||
|
@ -35,13 +35,12 @@ public final class ConnectionExpiredException extends IOException {
|
||||
private static final long serialVersionUID = 0;
|
||||
|
||||
/**
|
||||
* Constructs a {@code ConnectionExpiredException} with the specified detail
|
||||
* message and cause.
|
||||
* Constructs a {@code ConnectionExpiredException} with a detail message of
|
||||
* "subscription is finished" and the given cause.
|
||||
*
|
||||
* @param s the detail message
|
||||
* @param cause the throwable cause
|
||||
*/
|
||||
public ConnectionExpiredException(String s, Throwable cause) {
|
||||
super(s, cause);
|
||||
public ConnectionExpiredException(Throwable cause) {
|
||||
super("subscription is finished", cause);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 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
|
||||
@ -27,63 +27,59 @@ package jdk.internal.net.http.common;
|
||||
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import static jdk.internal.net.http.common.Utils.ACCEPT_ALL;
|
||||
|
||||
/**
|
||||
* Implementation of HttpHeaders.
|
||||
*
|
||||
* The public HttpHeaders API provides a read-only view, while the
|
||||
* non-HttpHeaders members allow for implementation specific mutation, e.g.
|
||||
* during creation, etc.
|
||||
*/
|
||||
public class HttpHeadersImpl extends HttpHeaders {
|
||||
/** A mutable builder for collecting and building HTTP headers. */
|
||||
public class HttpHeadersBuilder {
|
||||
|
||||
private final TreeMap<String, List<String>> headers;
|
||||
private final TreeMap<String, List<String>> headersMap;
|
||||
|
||||
public HttpHeadersImpl() {
|
||||
headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
public HttpHeadersBuilder() {
|
||||
headersMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> map() {
|
||||
return Collections.unmodifiableMap(headersMap());
|
||||
}
|
||||
|
||||
// non-HttpHeaders private mutators
|
||||
|
||||
public HttpHeadersImpl deepCopy() {
|
||||
HttpHeadersImpl h1 = newDeepCopy();
|
||||
for (Map.Entry<String, List<String>> entry : headersMap().entrySet()) {
|
||||
public HttpHeadersBuilder structuralCopy() {
|
||||
HttpHeadersBuilder builder = new HttpHeadersBuilder();
|
||||
for (Map.Entry<String, List<String>> entry : headersMap.entrySet()) {
|
||||
List<String> valuesCopy = new ArrayList<>(entry.getValue());
|
||||
h1.headersMap().put(entry.getKey(), valuesCopy);
|
||||
builder.headersMap.put(entry.getKey(), valuesCopy);
|
||||
}
|
||||
return h1;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public void addHeader(String name, String value) {
|
||||
headersMap().computeIfAbsent(name, k -> new ArrayList<>(1))
|
||||
.add(value);
|
||||
headersMap.computeIfAbsent(name, k -> new ArrayList<>(1))
|
||||
.add(value);
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value) {
|
||||
// headers typically have one value
|
||||
List<String> values = new ArrayList<>(1);
|
||||
values.add(value);
|
||||
headersMap().put(name, values);
|
||||
headersMap.put(name, values);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
headersMap().clear();
|
||||
headersMap.clear();
|
||||
}
|
||||
|
||||
protected HttpHeadersImpl newDeepCopy() {
|
||||
return new HttpHeadersImpl();
|
||||
public Map<String, List<String>> map() {
|
||||
return headersMap;
|
||||
}
|
||||
|
||||
protected Map<String, List<String>> headersMap() {
|
||||
return headers;
|
||||
public HttpHeaders build() {
|
||||
return HttpHeaders.of(headersMap, ACCEPT_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(super.toString()).append(" { ");
|
||||
sb.append(map());
|
||||
sb.append(" }");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ import javax.net.ssl.SSLParameters;
|
||||
/**
|
||||
* -Djava.net.HttpClient.log=
|
||||
* errors,requests,headers,
|
||||
* frames[:control:data:window:all..],content,ssl,trace
|
||||
* frames[:control:data:window:all..],content,ssl,trace,channel
|
||||
*
|
||||
* Any of errors, requests, headers or content are optional.
|
||||
*
|
||||
@ -65,6 +65,7 @@ public abstract class Log implements System.Logger {
|
||||
public static final int FRAMES = 0x10;
|
||||
public static final int SSL = 0x20;
|
||||
public static final int TRACE = 0x40;
|
||||
public static final int CHANNEL = 0x80;
|
||||
static int logging;
|
||||
|
||||
// Frame types: "control", "data", "window", "all"
|
||||
@ -99,11 +100,15 @@ public abstract class Log implements System.Logger {
|
||||
case "ssl":
|
||||
logging |= SSL;
|
||||
break;
|
||||
case "channel":
|
||||
logging |= CHANNEL;
|
||||
break;
|
||||
case "trace":
|
||||
logging |= TRACE;
|
||||
break;
|
||||
case "all":
|
||||
logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS|TRACE|SSL;
|
||||
logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS|TRACE|SSL| CHANNEL;
|
||||
frametypes |= ALL;
|
||||
break;
|
||||
default:
|
||||
// ignore bad values
|
||||
@ -166,6 +171,10 @@ public abstract class Log implements System.Logger {
|
||||
return (logging & FRAMES) != 0;
|
||||
}
|
||||
|
||||
public static boolean channel() {
|
||||
return (logging & CHANNEL) != 0;
|
||||
}
|
||||
|
||||
public static void logError(String s, Object... s1) {
|
||||
if (errors()) {
|
||||
logger.log(Level.INFO, "ERROR: " + s, s1);
|
||||
@ -191,9 +200,21 @@ public abstract class Log implements System.Logger {
|
||||
}
|
||||
}
|
||||
|
||||
public static void logChannel(String s, Object... s1) {
|
||||
if (channel()) {
|
||||
logger.log(Level.INFO, "CHANNEL: " + s, s1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logChannel(Supplier<String> msgSupplier) {
|
||||
if (channel()) {
|
||||
logger.log(Level.INFO, "CHANNEL: " + msgSupplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
public static void logTrace(String s, Object... s1) {
|
||||
if (trace()) {
|
||||
String format = "TRACE: " + s;
|
||||
String format = "MISC: " + s;
|
||||
logger.log(Level.INFO, format, s1);
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import java.util.concurrent.Flow;
|
||||
import java.util.concurrent.Flow.Subscriber;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntBinaryOperator;
|
||||
|
||||
/**
|
||||
* Implements SSL using two SubscriberWrappers.
|
||||
@ -87,13 +88,18 @@ public class SSLFlowDelegate {
|
||||
final Logger debug =
|
||||
Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
|
||||
|
||||
private static final ByteBuffer SENTINEL = Utils.EMPTY_BYTEBUFFER;
|
||||
private static final ByteBuffer HS_TRIGGER = ByteBuffer.allocate(0);
|
||||
// When handshake is in progress trying to wrap may produce no bytes.
|
||||
private static final ByteBuffer NOTHING = ByteBuffer.allocate(0);
|
||||
private static final String monProp = Utils.getProperty("jdk.internal.httpclient.monitorFlowDelegate");
|
||||
|
||||
final Executor exec;
|
||||
final Reader reader;
|
||||
final Writer writer;
|
||||
final SSLEngine engine;
|
||||
final String tubeName; // hack
|
||||
final CompletableFuture<String> alpnCF; // completes on initial handshake
|
||||
final static ByteBuffer SENTINEL = Utils.EMPTY_BYTEBUFFER;
|
||||
volatile boolean close_notify_received;
|
||||
final CompletableFuture<Void> readerCF;
|
||||
final CompletableFuture<Void> writerCF;
|
||||
@ -146,7 +152,8 @@ public class SSLFlowDelegate {
|
||||
// Writer to the downWriter.
|
||||
connect(downReader, downWriter);
|
||||
|
||||
//Monitor.add(this::monitor);
|
||||
if (monProp != null && (monProp.equals("") || monProp.equalsIgnoreCase("true")))
|
||||
Monitor.add(this::monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,13 +252,16 @@ public class SSLFlowDelegate {
|
||||
final Logger debugr = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
|
||||
|
||||
private final class ReaderDownstreamPusher implements Runnable {
|
||||
@Override public void run() { processData(); }
|
||||
@Override
|
||||
public void run() {
|
||||
processData();
|
||||
}
|
||||
}
|
||||
|
||||
Reader() {
|
||||
super();
|
||||
scheduler = SequentialScheduler.synchronizedScheduler(
|
||||
new ReaderDownstreamPusher());
|
||||
new ReaderDownstreamPusher());
|
||||
this.readBuf = ByteBuffer.allocate(1024);
|
||||
readBuf.limit(0); // keep in read mode
|
||||
}
|
||||
@ -276,7 +286,7 @@ public class SSLFlowDelegate {
|
||||
public void incoming(List<ByteBuffer> buffers, boolean complete) {
|
||||
if (debugr.on())
|
||||
debugr.log("Adding %d bytes to read buffer",
|
||||
Utils.remaining(buffers));
|
||||
Utils.remaining(buffers));
|
||||
addToReadBuf(buffers, complete);
|
||||
scheduler.runOrSchedule(exec);
|
||||
}
|
||||
@ -289,7 +299,7 @@ public class SSLFlowDelegate {
|
||||
|
||||
private void reallocReadBuf() {
|
||||
int sz = readBuf.capacity();
|
||||
ByteBuffer newb = ByteBuffer.allocate(sz*2);
|
||||
ByteBuffer newb = ByteBuffer.allocate(sz * 2);
|
||||
readBuf.flip();
|
||||
Utils.copy(readBuf, newb);
|
||||
readBuf = newb;
|
||||
@ -300,7 +310,7 @@ public class SSLFlowDelegate {
|
||||
if (readBuf.remaining() > TARGET_BUFSIZE) {
|
||||
if (debugr.on())
|
||||
debugr.log("readBuf has more than TARGET_BUFSIZE: %d",
|
||||
readBuf.remaining());
|
||||
readBuf.remaining());
|
||||
return 0;
|
||||
} else {
|
||||
return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
|
||||
@ -309,6 +319,7 @@ public class SSLFlowDelegate {
|
||||
|
||||
// readBuf is kept ready for reading outside of this method
|
||||
private void addToReadBuf(List<ByteBuffer> buffers, boolean complete) {
|
||||
assert Utils.remaining(buffers) > 0 || buffers.isEmpty();
|
||||
synchronized (readBufferLock) {
|
||||
for (ByteBuffer buf : buffers) {
|
||||
readBuf.compact();
|
||||
@ -344,14 +355,15 @@ public class SSLFlowDelegate {
|
||||
// In this case we need to wait for more bytes than what
|
||||
// we had before calling unwrap() again.
|
||||
volatile int minBytesRequired;
|
||||
|
||||
// work function where it all happens
|
||||
final void processData() {
|
||||
try {
|
||||
if (debugr.on())
|
||||
debugr.log("processData:"
|
||||
+ " readBuf remaining:" + readBuf.remaining()
|
||||
+ ", state:" + states(handshakeState)
|
||||
+ ", engine handshake status:" + engine.getHandshakeStatus());
|
||||
+ " readBuf remaining:" + readBuf.remaining()
|
||||
+ ", state:" + states(handshakeState)
|
||||
+ ", engine handshake status:" + engine.getHandshakeStatus());
|
||||
int len;
|
||||
boolean complete = false;
|
||||
while (readBuf.remaining() > (len = minBytesRequired)) {
|
||||
@ -400,14 +412,13 @@ public class SSLFlowDelegate {
|
||||
outgoing(Utils.EMPTY_BB_LIST, true);
|
||||
return;
|
||||
}
|
||||
if (result.handshaking() && !complete) {
|
||||
if (debugr.on()) debugr.log("handshaking");
|
||||
if (doHandshake(result, READER)) {
|
||||
resumeActivity();
|
||||
}
|
||||
if (result.handshaking()) {
|
||||
handshaking = true;
|
||||
if (debugr.on()) debugr.log("handshaking");
|
||||
if (doHandshake(result, READER)) continue; // need unwrap
|
||||
else break; // doHandshake will have triggered the write scheduler if necessary
|
||||
} else {
|
||||
if ((handshakeState.getAndSet(NOT_HANDSHAKING)& ~DOING_TASKS) == HANDSHAKING) {
|
||||
if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
|
||||
handshaking = false;
|
||||
applicationBufferSize = engine.getSession().getApplicationBufferSize();
|
||||
packetBufferSize = engine.getSession().getPacketBufferSize();
|
||||
@ -443,12 +454,19 @@ public class SSLFlowDelegate {
|
||||
|
||||
EngineResult unwrapBuffer(ByteBuffer src) throws IOException {
|
||||
ByteBuffer dst = getAppBuffer();
|
||||
int len = src.remaining();
|
||||
while (true) {
|
||||
SSLEngineResult sslResult = engine.unwrap(src, dst);
|
||||
switch (sslResult.getStatus()) {
|
||||
case BUFFER_OVERFLOW:
|
||||
// may happen only if app size buffer was changed.
|
||||
// get it again if app buffer size changed
|
||||
// may happen if app size buffer was changed, or if
|
||||
// our 'adaptiveBufferSize' guess was too small for
|
||||
// the current payload. In that case, update the
|
||||
// value of applicationBufferSize, and allocate a
|
||||
// buffer of that size, which we are sure will be
|
||||
// big enough to decode whatever needs to be
|
||||
// decoded. We will later update adaptiveBufferSize
|
||||
// in OK: below.
|
||||
int appSize = applicationBufferSize =
|
||||
engine.getSession().getApplicationBufferSize();
|
||||
ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
|
||||
@ -457,11 +475,26 @@ public class SSLFlowDelegate {
|
||||
dst = b;
|
||||
break;
|
||||
case CLOSED:
|
||||
assert dst.position() == 0;
|
||||
return doClosure(new EngineResult(sslResult));
|
||||
case BUFFER_UNDERFLOW:
|
||||
// handled implicitly by compaction/reallocation of readBuf
|
||||
assert dst.position() == 0;
|
||||
return new EngineResult(sslResult);
|
||||
case OK:
|
||||
int size = dst.position();
|
||||
if (debug.on()) {
|
||||
debugr.log("Decoded " + size + " bytes out of " + len
|
||||
+ " into buffer of " + dst.capacity()
|
||||
+ " remaining to decode: " + src.remaining());
|
||||
}
|
||||
// if the record payload was bigger than what was originally
|
||||
// allocated, then sets the adaptiveAppBufferSize to size
|
||||
// and we will use that new size as a guess for the next app
|
||||
// buffer.
|
||||
if (size > adaptiveAppBufferSize) {
|
||||
adaptiveAppBufferSize = ((size + 7) >>> 3) << 3;
|
||||
}
|
||||
dst.flip();
|
||||
return new EngineResult(sslResult, dst);
|
||||
}
|
||||
@ -662,8 +695,8 @@ public class SSLFlowDelegate {
|
||||
}
|
||||
cleanList(writeList); // tidy up the source list
|
||||
sendResultBytes(result);
|
||||
if (handshaking && !completing) {
|
||||
if (needWrap()) {
|
||||
if (handshaking) {
|
||||
if (!completing && needWrap()) {
|
||||
continue;
|
||||
} else {
|
||||
return;
|
||||
@ -687,11 +720,30 @@ public class SSLFlowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// The SSLEngine insists on being given a buffer that is at least
|
||||
// SSLSession.getPacketBufferSize() long (usually 16K). If given
|
||||
// a smaller buffer it will go in BUFFER_OVERFLOW, even if it only
|
||||
// has 6 bytes to wrap. Typical usage shows that for GET we
|
||||
// usually produce an average of ~ 100 bytes.
|
||||
// To avoid wasting space, and because allocating and zeroing
|
||||
// 16K buffers for encoding 6 bytes is costly, we are reusing the
|
||||
// same writeBuffer to interact with SSLEngine.wrap().
|
||||
// If the SSLEngine produces less than writeBuffer.capacity() / 2,
|
||||
// then we copy off the bytes to a smaller buffer that we send
|
||||
// downstream. Otherwise, we send the writeBuffer downstream
|
||||
// and will allocate a new one next time.
|
||||
volatile ByteBuffer writeBuffer;
|
||||
@SuppressWarnings("fallthrough")
|
||||
EngineResult wrapBuffers(ByteBuffer[] src) throws SSLException {
|
||||
long len = Utils.remaining(src);
|
||||
if (debugw.on())
|
||||
debugw.log("wrapping " + Utils.remaining(src) + " bytes");
|
||||
ByteBuffer dst = getNetBuffer();
|
||||
debugw.log("wrapping " + len + " bytes");
|
||||
|
||||
ByteBuffer dst = writeBuffer;
|
||||
if (dst == null) dst = writeBuffer = getNetBuffer();
|
||||
assert dst.position() == 0 : "buffer position is " + dst.position();
|
||||
assert dst.hasRemaining() : "buffer has no remaining space: capacity=" + dst.capacity();
|
||||
|
||||
while (true) {
|
||||
SSLEngineResult sslResult = engine.wrap(src, dst);
|
||||
if (debugw.on()) debugw.log("SSLResult: " + sslResult);
|
||||
@ -702,7 +754,7 @@ public class SSLFlowDelegate {
|
||||
if (debugw.on()) debugw.log("BUFFER_OVERFLOW");
|
||||
int netSize = packetBufferSize
|
||||
= engine.getSession().getPacketBufferSize();
|
||||
ByteBuffer b = ByteBuffer.allocate(netSize + dst.position());
|
||||
ByteBuffer b = writeBuffer = ByteBuffer.allocate(netSize + dst.position());
|
||||
dst.flip();
|
||||
b.put(dst);
|
||||
dst = b;
|
||||
@ -712,11 +764,27 @@ public class SSLFlowDelegate {
|
||||
// fallthrough. There could be some remaining data in dst.
|
||||
// CLOSED will be handled by the caller.
|
||||
case OK:
|
||||
dst.flip();
|
||||
final ByteBuffer dest = dst;
|
||||
final ByteBuffer dest;
|
||||
if (dst.position() == 0) {
|
||||
dest = NOTHING; // can happen if handshake is in progress
|
||||
} else if (dst.position() < dst.capacity() / 2) {
|
||||
// less than half the buffer was used.
|
||||
// copy off the bytes to a smaller buffer, and keep
|
||||
// the writeBuffer for next time.
|
||||
dst.flip();
|
||||
dest = Utils.copyAligned(dst);
|
||||
dst.clear();
|
||||
} else {
|
||||
// more than half the buffer was used.
|
||||
// just send that buffer downstream, and we will
|
||||
// get a new writeBuffer next time it is needed.
|
||||
dst.flip();
|
||||
dest = dst;
|
||||
writeBuffer = null;
|
||||
}
|
||||
if (debugw.on())
|
||||
debugw.log("OK => produced: %d, not wrapped: %d",
|
||||
dest.remaining(), Utils.remaining(src));
|
||||
debugw.log("OK => produced: %d bytes into %d, not wrapped: %d",
|
||||
dest.remaining(), dest.capacity(), Utils.remaining(src));
|
||||
return new EngineResult(sslResult, dest);
|
||||
case BUFFER_UNDERFLOW:
|
||||
// Shouldn't happen. Doesn't returns when wrap()
|
||||
@ -799,8 +867,12 @@ public class SSLFlowDelegate {
|
||||
private static final int NOT_HANDSHAKING = 0;
|
||||
private static final int HANDSHAKING = 1;
|
||||
|
||||
private static final int DOING_TASKS = 4; // bit added to above state
|
||||
private static final ByteBuffer HS_TRIGGER = ByteBuffer.allocate(0);
|
||||
// Bit flags
|
||||
// a thread is currently executing tasks
|
||||
private static final int DOING_TASKS = 4;
|
||||
// a thread wants to execute tasks, while another thread is executing
|
||||
private static final int REQUESTING_TASKS = 8;
|
||||
private static final int TASK_BITS = 12; // Both bits
|
||||
|
||||
private static final int READER = 1;
|
||||
private static final int WRITER = 2;
|
||||
@ -808,7 +880,7 @@ public class SSLFlowDelegate {
|
||||
private static String states(AtomicInteger state) {
|
||||
int s = state.get();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int x = s & ~DOING_TASKS;
|
||||
int x = s & ~TASK_BITS;
|
||||
switch (x) {
|
||||
case NOT_HANDSHAKING:
|
||||
sb.append(" NOT_HANDSHAKING ");
|
||||
@ -821,6 +893,8 @@ public class SSLFlowDelegate {
|
||||
}
|
||||
if ((s & DOING_TASKS) > 0)
|
||||
sb.append("|DOING_TASKS");
|
||||
if ((s & REQUESTING_TASKS) > 0)
|
||||
sb.append("|REQUESTING_TASKS");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@ -833,18 +907,37 @@ public class SSLFlowDelegate {
|
||||
final ConcurrentLinkedQueue<String> stateList =
|
||||
debug.on() ? new ConcurrentLinkedQueue<>() : null;
|
||||
|
||||
// Atomically executed to update task bits. Sets either DOING_TASKS or REQUESTING_TASKS
|
||||
// depending on previous value
|
||||
private static final IntBinaryOperator REQUEST_OR_DO_TASKS = (current, ignored) -> {
|
||||
if ((current & DOING_TASKS) == 0)
|
||||
return DOING_TASKS | (current & HANDSHAKING);
|
||||
else
|
||||
return DOING_TASKS | REQUESTING_TASKS | (current & HANDSHAKING);
|
||||
};
|
||||
|
||||
// Atomically executed to update task bits. Sets DOING_TASKS if REQUESTING was set
|
||||
// clears bits if not.
|
||||
private static final IntBinaryOperator FINISH_OR_DO_TASKS = (current, ignored) -> {
|
||||
if ((current & REQUESTING_TASKS) != 0)
|
||||
return DOING_TASKS | (current & HANDSHAKING);
|
||||
// clear both bits
|
||||
return (current & HANDSHAKING);
|
||||
};
|
||||
|
||||
private boolean doHandshake(EngineResult r, int caller) {
|
||||
// unconditionally sets the HANDSHAKING bit, while preserving DOING_TASKS
|
||||
handshakeState.getAndAccumulate(HANDSHAKING, (current, update) -> update | (current & DOING_TASKS));
|
||||
// unconditionally sets the HANDSHAKING bit, while preserving task bits
|
||||
handshakeState.getAndAccumulate(0, (current, unused) -> HANDSHAKING | (current & TASK_BITS));
|
||||
if (stateList != null && debug.on()) {
|
||||
stateList.add(r.handshakeStatus().toString());
|
||||
stateList.add(Integer.toString(caller));
|
||||
}
|
||||
switch (r.handshakeStatus()) {
|
||||
case NEED_TASK:
|
||||
int s = handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
|
||||
if ((s & DOING_TASKS) > 0) // someone else was doing tasks
|
||||
int s = handshakeState.accumulateAndGet(0, REQUEST_OR_DO_TASKS);
|
||||
if ((s & REQUESTING_TASKS) > 0) { // someone else is or will do tasks
|
||||
return false;
|
||||
}
|
||||
|
||||
if (debug.on()) debug.log("obtaining and initiating task execution");
|
||||
List<Runnable> tasks = obtainTasks();
|
||||
@ -878,20 +971,25 @@ public class SSLFlowDelegate {
|
||||
}
|
||||
|
||||
private void executeTasks(List<Runnable> tasks) {
|
||||
if (tasks.isEmpty())
|
||||
return;
|
||||
exec.execute(() -> {
|
||||
try {
|
||||
List<Runnable> nextTasks = tasks;
|
||||
if (debug.on()) debug.log("#tasks to execute: " + Integer.toString(nextTasks.size()));
|
||||
do {
|
||||
nextTasks.forEach(Runnable::run);
|
||||
if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
||||
nextTasks = obtainTasks();
|
||||
} else {
|
||||
int s = handshakeState.accumulateAndGet(0, FINISH_OR_DO_TASKS);
|
||||
if ((s & DOING_TASKS) != 0) {
|
||||
if (debug.on()) debug.log("re-running tasks (B)");
|
||||
nextTasks = obtainTasks();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
handshakeState.getAndUpdate((current) -> current & ~DOING_TASKS);
|
||||
if (debug.on()) debug.log("finished task execution");
|
||||
resumeActivity();
|
||||
} catch (Throwable t) {
|
||||
handleError(t);
|
||||
@ -997,6 +1095,8 @@ public class SSLFlowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// The maximum network buffer size negotiated during
|
||||
// the handshake. Usually 16K.
|
||||
volatile int packetBufferSize;
|
||||
final ByteBuffer getNetBuffer() {
|
||||
int netSize = packetBufferSize;
|
||||
@ -1006,13 +1106,32 @@ public class SSLFlowDelegate {
|
||||
return ByteBuffer.allocate(netSize);
|
||||
}
|
||||
|
||||
// The maximum application buffer size negotiated during
|
||||
// the handshake. Usually close to 16K.
|
||||
volatile int applicationBufferSize;
|
||||
// Despite of the maximum applicationBufferSize negotiated
|
||||
// above, TLS records usually have a much smaller payload.
|
||||
// The adaptativeAppBufferSize records the max payload
|
||||
// ever decoded, and we use that as a guess for how big
|
||||
// a buffer we will need for the next payload.
|
||||
// This avoids allocating and zeroing a 16K buffer for
|
||||
// nothing...
|
||||
volatile int adaptiveAppBufferSize;
|
||||
final ByteBuffer getAppBuffer() {
|
||||
int appSize = applicationBufferSize;
|
||||
if (appSize <= 0) {
|
||||
applicationBufferSize = appSize = engine.getSession().getApplicationBufferSize();
|
||||
applicationBufferSize = appSize
|
||||
= engine.getSession().getApplicationBufferSize();
|
||||
}
|
||||
return ByteBuffer.allocate(appSize);
|
||||
int size = adaptiveAppBufferSize;
|
||||
if (size <= 0) {
|
||||
size = 512; // start with 512 this is usually enough for handshaking / headers
|
||||
} else if (size > appSize) {
|
||||
size = appSize;
|
||||
}
|
||||
// will cause a BUFFER_OVERFLOW if not big enough, but
|
||||
// that's OK.
|
||||
return ByteBuffer.allocate(size);
|
||||
}
|
||||
|
||||
final String dbgString() {
|
||||
|
@ -309,7 +309,7 @@ public class SSLTube implements FlowTube {
|
||||
synchronized (this) {
|
||||
previous = pendingDelegate.getAndSet(delegateWrapper);
|
||||
subscription = readSubscription;
|
||||
handleNow = this.errorRef.get() != null || finished;
|
||||
handleNow = this.errorRef.get() != null || onCompleteReceived;
|
||||
}
|
||||
if (previous != null) {
|
||||
previous.dropSubscription();
|
||||
@ -424,12 +424,20 @@ public class SSLTube implements FlowTube {
|
||||
// if onError is invoked concurrently with setDelegate.
|
||||
synchronized (this) {
|
||||
failed = this.errorRef.get();
|
||||
completed = finished;
|
||||
completed = onCompleteReceived;
|
||||
subscribed = subscriberImpl;
|
||||
}
|
||||
|
||||
if (failed != null) {
|
||||
if (debug.on())
|
||||
debug.log("onNewSubscription: subscriberImpl:%s, invoking onError:%s",
|
||||
subscriberImpl, failed);
|
||||
subscriberImpl.onError(failed);
|
||||
} else if (completed) {
|
||||
if (debug.on())
|
||||
debug.log("onNewSubscription: subscriberImpl:%s, invoking onCompleted",
|
||||
subscriberImpl);
|
||||
finished = true;
|
||||
subscriberImpl.onComplete();
|
||||
}
|
||||
}
|
||||
@ -490,7 +498,6 @@ public class SSLTube implements FlowTube {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
assert !finished && !onCompleteReceived;
|
||||
onCompleteReceived = true;
|
||||
DelegateWrapper subscriberImpl;
|
||||
synchronized(this) {
|
||||
subscriberImpl = subscribed;
|
||||
@ -505,8 +512,10 @@ public class SSLTube implements FlowTube {
|
||||
onErrorImpl(new SSLHandshakeException(
|
||||
"Remote host terminated the handshake"));
|
||||
} else if (subscriberImpl != null) {
|
||||
finished = true;
|
||||
onCompleteReceived = finished = true;
|
||||
subscriberImpl.onComplete();
|
||||
} else {
|
||||
onCompleteReceived = true;
|
||||
}
|
||||
// now if we have any pending subscriber, we should complete
|
||||
// them immediately as the read scheduler will already be stopped.
|
||||
@ -528,12 +537,17 @@ public class SSLTube implements FlowTube {
|
||||
final class SSLSubscriptionWrapper implements Flow.Subscription {
|
||||
|
||||
volatile Flow.Subscription delegate;
|
||||
private volatile boolean cancelled;
|
||||
|
||||
void setSubscription(Flow.Subscription sub) {
|
||||
long demand = writeDemand.get(); // FIXME: isn't it a racy way of passing the demand?
|
||||
delegate = sub;
|
||||
if (debug.on()) debug.log("setSubscription: demand=%d", demand);
|
||||
if (demand > 0)
|
||||
if (debug.on())
|
||||
debug.log("setSubscription: demand=%d, cancelled:%s", demand, cancelled);
|
||||
|
||||
if (cancelled)
|
||||
delegate.cancel();
|
||||
else if (demand > 0)
|
||||
sub.request(demand);
|
||||
}
|
||||
|
||||
@ -549,7 +563,9 @@ public class SSLTube implements FlowTube {
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
// TODO: no-op or error?
|
||||
cancelled = true;
|
||||
if (delegate != null)
|
||||
delegate.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,6 +260,8 @@ public abstract class SubscriberWrapper
|
||||
try {
|
||||
run1();
|
||||
} catch (Throwable t) {
|
||||
if (debug.on())
|
||||
debug.log("DownstreamPusher threw: " + t);
|
||||
errorCommon(t);
|
||||
}
|
||||
}
|
||||
@ -292,6 +294,7 @@ public abstract class SubscriberWrapper
|
||||
pushScheduler.stop();
|
||||
outputQ.clear();
|
||||
downstreamSubscriber.onError(error);
|
||||
cf.completeExceptionally(error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -383,9 +386,8 @@ public abstract class SubscriberWrapper
|
||||
(throwable = new AssertionError("null throwable")) != null;
|
||||
if (errorRef.compareAndSet(null, throwable)) {
|
||||
if (debug.on()) debug.log("error", throwable);
|
||||
pushScheduler.runOrSchedule();
|
||||
upstreamCompleted = true;
|
||||
cf.completeExceptionally(throwable);
|
||||
pushScheduler.runOrSchedule();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -30,19 +30,23 @@ import sun.net.util.IPAddressUtil;
|
||||
import sun.net.www.HeaderParser;
|
||||
|
||||
import javax.net.ssl.ExtendedSSLSession;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URLPermission;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
@ -58,9 +62,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@ -119,6 +125,8 @@ public final class Utils {
|
||||
"jdk.httpclient.bufsize", DEFAULT_BUFSIZE
|
||||
);
|
||||
|
||||
public static final BiPredicate<String,String> ACCEPT_ALL = (x,y) -> true;
|
||||
|
||||
private static final Set<String> DISALLOWED_HEADERS_SET;
|
||||
|
||||
static {
|
||||
@ -131,24 +139,21 @@ public final class Utils {
|
||||
DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
|
||||
}
|
||||
|
||||
public static final Predicate<String>
|
||||
ALLOWED_HEADERS = header -> !DISALLOWED_HEADERS_SET.contains(header);
|
||||
public static final BiPredicate<String, String>
|
||||
ALLOWED_HEADERS = (header, unused) -> !DISALLOWED_HEADERS_SET.contains(header);
|
||||
|
||||
public static final BiPredicate<String, List<String>> VALIDATE_USER_HEADER =
|
||||
(name, lv) -> {
|
||||
requireNonNull(name, "header name");
|
||||
requireNonNull(lv, "header values");
|
||||
public static final BiPredicate<String, String> VALIDATE_USER_HEADER =
|
||||
(name, value) -> {
|
||||
assert name != null : "null header name";
|
||||
assert value != null : "null header value";
|
||||
if (!isValidName(name)) {
|
||||
throw newIAE("invalid header name: \"%s\"", name);
|
||||
}
|
||||
if (!Utils.ALLOWED_HEADERS.test(name)) {
|
||||
if (!Utils.ALLOWED_HEADERS.test(name, null)) {
|
||||
throw newIAE("restricted header name: \"%s\"", name);
|
||||
}
|
||||
for (String value : lv) {
|
||||
requireNonNull(value, "header value");
|
||||
if (!isValidValue(value)) {
|
||||
throw newIAE("invalid header value for %s: \"%s\"", name, value);
|
||||
}
|
||||
if (!isValidValue(value)) {
|
||||
throw newIAE("invalid header value for %s: \"%s\"", name, value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@ -180,9 +185,20 @@ public final class Utils {
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<T> wrapForDebug(Logger logger, String name, CompletableFuture<T> cf) {
|
||||
if (logger.on()) {
|
||||
return cf.handle((r,t) -> {
|
||||
logger.log("%s completed %s", name, t == null ? "successfully" : t );
|
||||
return cf;
|
||||
}).thenCompose(Function.identity());
|
||||
} else {
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String WSPACES = " \t\r\n";
|
||||
private static final boolean isAllowedForProxy(String name,
|
||||
List<String> value,
|
||||
String value,
|
||||
Set<String> disabledSchemes,
|
||||
Predicate<String> allowedKeys) {
|
||||
if (!allowedKeys.test(name)) return false;
|
||||
@ -191,21 +207,19 @@ public final class Utils {
|
||||
if (value.isEmpty()) return false;
|
||||
for (String scheme : disabledSchemes) {
|
||||
int slen = scheme.length();
|
||||
for (String v : value) {
|
||||
int vlen = v.length();
|
||||
if (vlen == slen) {
|
||||
if (v.equalsIgnoreCase(scheme)) {
|
||||
int vlen = value.length();
|
||||
if (vlen == slen) {
|
||||
if (value.equalsIgnoreCase(scheme)) {
|
||||
return false;
|
||||
}
|
||||
} else if (vlen > slen) {
|
||||
if (value.substring(0,slen).equalsIgnoreCase(scheme)) {
|
||||
int c = value.codePointAt(slen);
|
||||
if (WSPACES.indexOf(c) > -1
|
||||
|| Character.isSpaceChar(c)
|
||||
|| Character.isWhitespace(c)) {
|
||||
return false;
|
||||
}
|
||||
} else if (vlen > slen) {
|
||||
if (v.substring(0,slen).equalsIgnoreCase(scheme)) {
|
||||
int c = v.codePointAt(slen);
|
||||
if (WSPACES.indexOf(c) > -1
|
||||
|| Character.isSpaceChar(c)
|
||||
|| Character.isWhitespace(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -213,13 +227,13 @@ public final class Utils {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final BiPredicate<String, List<String>> PROXY_TUNNEL_FILTER =
|
||||
public static final BiPredicate<String, String> PROXY_TUNNEL_FILTER =
|
||||
(s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_TUNNEL_DISABLED_SCHEMES,
|
||||
IS_PROXY_HEADER);
|
||||
public static final BiPredicate<String, List<String>> PROXY_FILTER =
|
||||
public static final BiPredicate<String, String> PROXY_FILTER =
|
||||
(s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_DISABLED_SCHEMES,
|
||||
ALL_HEADERS);
|
||||
public static final BiPredicate<String, List<String>> NO_PROXY_HEADERS_FILTER =
|
||||
public static final BiPredicate<String, String> NO_PROXY_HEADERS_FILTER =
|
||||
(n,v) -> Utils.NO_PROXY_HEADER.test(n);
|
||||
|
||||
|
||||
@ -256,6 +270,35 @@ public final class Utils {
|
||||
return new IOException(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a more specific exception detail message, based on the given
|
||||
* exception type and the message supplier. This is primarily to present
|
||||
* more descriptive messages in IOExceptions that may be visible to calling
|
||||
* code.
|
||||
*
|
||||
* @return a possibly new exception that has as its detail message, the
|
||||
* message from the messageSupplier, and the given throwable as its
|
||||
* cause. Otherwise returns the given throwable
|
||||
*/
|
||||
public static Throwable wrapWithExtraDetail(Throwable t,
|
||||
Supplier<String> messageSupplier) {
|
||||
if (!(t instanceof IOException))
|
||||
return t;
|
||||
|
||||
String msg = messageSupplier.get();
|
||||
if (msg == null)
|
||||
return t;
|
||||
|
||||
if (t instanceof ConnectionExpiredException) {
|
||||
IOException ioe = new IOException(msg, t.getCause());
|
||||
t = new ConnectionExpiredException(ioe);
|
||||
} else {
|
||||
IOException ioe = new IOException(msg, t);
|
||||
t = ioe;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
private Utils() { }
|
||||
|
||||
/**
|
||||
@ -534,6 +577,16 @@ public final class Utils {
|
||||
return dst;
|
||||
}
|
||||
|
||||
public static ByteBuffer copyAligned(ByteBuffer src) {
|
||||
int len = src.remaining();
|
||||
int size = ((len + 7) >> 3) << 3;
|
||||
assert size >= len;
|
||||
ByteBuffer dst = ByteBuffer.allocate(size);
|
||||
dst.put(src);
|
||||
dst.flip();
|
||||
return dst;
|
||||
}
|
||||
|
||||
public static String dump(Object... objects) {
|
||||
return Arrays.toString(objects);
|
||||
}
|
||||
@ -901,6 +954,20 @@ public final class Utils {
|
||||
return address;
|
||||
}
|
||||
|
||||
public static Throwable toConnectException(Throwable e) {
|
||||
if (e == null) return null;
|
||||
e = getCompletionCause(e);
|
||||
if (e instanceof ConnectException) return e;
|
||||
if (e instanceof SecurityException) return e;
|
||||
if (e instanceof SSLException) return e;
|
||||
if (e instanceof Error) return e;
|
||||
if (e instanceof HttpTimeoutException) return e;
|
||||
Throwable cause = e;
|
||||
e = new ConnectException(e.getMessage());
|
||||
e.initCause(cause);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smallest (closest to zero) positive number {@code m} (which
|
||||
* is also a power of 2) such that {@code n <= m}.
|
||||
|
@ -110,14 +110,14 @@ public class SettingsFrame extends Http2Frame {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public int getParameter(int paramID) {
|
||||
public synchronized int getParameter(int paramID) {
|
||||
if (paramID > MAX_PARAM) {
|
||||
throw new IllegalArgumentException("illegal parameter");
|
||||
}
|
||||
return parameters[paramID - 1];
|
||||
}
|
||||
|
||||
public SettingsFrame setParameter(int paramID, int value) {
|
||||
public synchronized SettingsFrame setParameter(int paramID, int value) {
|
||||
if (paramID > MAX_PARAM) {
|
||||
throw new IllegalArgumentException("illegal parameter");
|
||||
}
|
||||
@ -166,7 +166,7 @@ public class SettingsFrame extends Http2Frame {
|
||||
// TODO: check these values
|
||||
f.setParameter(ENABLE_PUSH, 1);
|
||||
f.setParameter(HEADER_TABLE_SIZE, 4 * K);
|
||||
f.setParameter(MAX_CONCURRENT_STREAMS, 35);
|
||||
f.setParameter(MAX_CONCURRENT_STREAMS, 100);
|
||||
f.setParameter(INITIAL_WINDOW_SIZE, 64 * K - 1);
|
||||
f.setParameter(MAX_FRAME_SIZE, 16 * K);
|
||||
return f;
|
||||
|
@ -28,6 +28,7 @@ import jdk.internal.net.http.hpack.HPACK.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
|
||||
@ -66,7 +67,8 @@ public final class Decoder {
|
||||
private final Logger logger;
|
||||
private static final AtomicLong DECODERS_IDS = new AtomicLong();
|
||||
|
||||
private static final State[] states = new State[256];
|
||||
/* An immutable list of states */
|
||||
private static final List<State> states;
|
||||
|
||||
static {
|
||||
// To be able to do a quick lookup, each of 256 possibilities are mapped
|
||||
@ -78,21 +80,23 @@ public final class Decoder {
|
||||
// I do it mainly for better debugging (to not go each time step by step
|
||||
// through if...else tree). As for performance win for the decoding, I
|
||||
// believe is negligible.
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
State[] s = new State[256];
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
if ((i & 0b1000_0000) == 0b1000_0000) {
|
||||
states[i] = State.INDEXED;
|
||||
s[i] = State.INDEXED;
|
||||
} else if ((i & 0b1100_0000) == 0b0100_0000) {
|
||||
states[i] = State.LITERAL_WITH_INDEXING;
|
||||
s[i] = State.LITERAL_WITH_INDEXING;
|
||||
} else if ((i & 0b1110_0000) == 0b0010_0000) {
|
||||
states[i] = State.SIZE_UPDATE;
|
||||
s[i] = State.SIZE_UPDATE;
|
||||
} else if ((i & 0b1111_0000) == 0b0001_0000) {
|
||||
states[i] = State.LITERAL_NEVER_INDEXED;
|
||||
s[i] = State.LITERAL_NEVER_INDEXED;
|
||||
} else if ((i & 0b1111_0000) == 0b0000_0000) {
|
||||
states[i] = State.LITERAL;
|
||||
s[i] = State.LITERAL;
|
||||
} else {
|
||||
throw new InternalError(String.valueOf(i));
|
||||
}
|
||||
}
|
||||
states = List.of(s);
|
||||
}
|
||||
|
||||
private final long id;
|
||||
@ -278,7 +282,7 @@ public final class Decoder {
|
||||
|
||||
private void resumeReady(ByteBuffer input) {
|
||||
int b = input.get(input.position()) & 0xff; // absolute read
|
||||
State s = states[b];
|
||||
State s = states.get(b);
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format("next binary representation %s (first byte 0x%02x)",
|
||||
s, b));
|
||||
@ -388,15 +392,17 @@ public final class Decoder {
|
||||
try {
|
||||
if (firstValueIndex) {
|
||||
if (logger.isLoggable(NORMAL)) {
|
||||
logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
|
||||
intValue, value));
|
||||
logger.log(NORMAL, () -> format(
|
||||
"literal without indexing (%s, '%s', huffman=%b)",
|
||||
intValue, value, valueHuffmanEncoded));
|
||||
}
|
||||
SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue);
|
||||
action.onLiteral(intValue, f.name, value, valueHuffmanEncoded);
|
||||
} else {
|
||||
if (logger.isLoggable(NORMAL)) {
|
||||
logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
|
||||
name, value));
|
||||
logger.log(NORMAL, () -> format(
|
||||
"literal without indexing ('%s', huffman=%b, '%s', huffman=%b)",
|
||||
name, nameHuffmanEncoded, value, valueHuffmanEncoded));
|
||||
}
|
||||
action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
|
||||
}
|
||||
@ -445,8 +451,9 @@ public final class Decoder {
|
||||
String v = value.toString();
|
||||
if (firstValueIndex) {
|
||||
if (logger.isLoggable(NORMAL)) {
|
||||
logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
|
||||
intValue, value));
|
||||
logger.log(NORMAL, () -> format(
|
||||
"literal with incremental indexing (%s, '%s', huffman=%b)",
|
||||
intValue, value, valueHuffmanEncoded));
|
||||
}
|
||||
SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue);
|
||||
n = f.name;
|
||||
@ -454,8 +461,9 @@ public final class Decoder {
|
||||
} else {
|
||||
n = name.toString();
|
||||
if (logger.isLoggable(NORMAL)) {
|
||||
logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
|
||||
n, value));
|
||||
logger.log(NORMAL, () -> format(
|
||||
"literal with incremental indexing ('%s', huffman=%b, '%s', huffman=%b)",
|
||||
n, nameHuffmanEncoded, value, valueHuffmanEncoded));
|
||||
}
|
||||
action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded);
|
||||
}
|
||||
@ -496,15 +504,17 @@ public final class Decoder {
|
||||
try {
|
||||
if (firstValueIndex) {
|
||||
if (logger.isLoggable(NORMAL)) {
|
||||
logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
|
||||
intValue, value));
|
||||
logger.log(NORMAL, () -> format(
|
||||
"literal never indexed (%s, '%s', huffman=%b)",
|
||||
intValue, value, valueHuffmanEncoded));
|
||||
}
|
||||
SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue);
|
||||
action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded);
|
||||
} else {
|
||||
if (logger.isLoggable(NORMAL)) {
|
||||
logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
|
||||
name, value));
|
||||
logger.log(NORMAL, () -> format(
|
||||
"literal never indexed ('%s', huffman=%b, '%s', huffman=%b)",
|
||||
name, nameHuffmanEncoded, value, valueHuffmanEncoded));
|
||||
}
|
||||
action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
|
||||
}
|
||||
|
@ -105,8 +105,8 @@ public class Encoder {
|
||||
|
||||
private static final AtomicLong ENCODERS_IDS = new AtomicLong();
|
||||
|
||||
// TODO: enum: no huffman/smart huffman/always huffman
|
||||
private static final boolean DEFAULT_HUFFMAN = true;
|
||||
/* Used to calculate the number of bytes required for Huffman encoding */
|
||||
private final QuickHuffman.Writer huffmanWriter = new QuickHuffman.Writer();
|
||||
|
||||
private final Logger logger;
|
||||
private final long id;
|
||||
@ -241,21 +241,30 @@ public class Encoder {
|
||||
int index = t.indexOf(name, value);
|
||||
if (index > 0) {
|
||||
indexed(index);
|
||||
} else if (index < 0) {
|
||||
if (sensitive) {
|
||||
literalNeverIndexed(-index, value, DEFAULT_HUFFMAN);
|
||||
} else {
|
||||
literal(-index, value, DEFAULT_HUFFMAN);
|
||||
}
|
||||
} else {
|
||||
if (sensitive) {
|
||||
literalNeverIndexed(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
|
||||
boolean huffmanValue = isHuffmanBetterFor(value);
|
||||
if (index < 0) {
|
||||
if (sensitive) {
|
||||
literalNeverIndexed(-index, value, huffmanValue);
|
||||
} else {
|
||||
literal(-index, value, huffmanValue);
|
||||
}
|
||||
} else {
|
||||
literal(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
|
||||
boolean huffmanName = isHuffmanBetterFor(name);
|
||||
if (sensitive) {
|
||||
literalNeverIndexed(name, huffmanName, value, huffmanValue);
|
||||
} else {
|
||||
literal(name, huffmanName, value, huffmanValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHuffmanBetterFor(CharSequence value) {
|
||||
// prefer Huffman encoding only if it is strictly smaller than Latin-1
|
||||
return huffmanWriter.lengthOf(value) < value.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a maximum capacity of the header table.
|
||||
*
|
||||
@ -299,7 +308,7 @@ public class Encoder {
|
||||
if (calculated < 0 || calculated > capacity) {
|
||||
throw new IllegalArgumentException(
|
||||
format("0 <= calculated <= capacity: calculated=%s, capacity=%s",
|
||||
calculated, capacity));
|
||||
calculated, capacity));
|
||||
}
|
||||
capacityUpdate = true;
|
||||
// maxCapacity needs to be updated unconditionally, so the encoder
|
||||
@ -416,8 +425,9 @@ public class Encoder {
|
||||
boolean useHuffman)
|
||||
throws IndexOutOfBoundsException {
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format("literal without indexing ('%s', '%s')",
|
||||
index, value));
|
||||
logger.log(EXTRA, () -> format(
|
||||
"literal without indexing (%s, '%s', huffman=%b)",
|
||||
index, value, useHuffman));
|
||||
}
|
||||
checkEncoding();
|
||||
encoding = true;
|
||||
@ -428,10 +438,12 @@ public class Encoder {
|
||||
protected final void literal(CharSequence name,
|
||||
boolean nameHuffman,
|
||||
CharSequence value,
|
||||
boolean valueHuffman) {
|
||||
boolean valueHuffman)
|
||||
{
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format("literal without indexing ('%s', '%s')",
|
||||
name, value));
|
||||
logger.log(EXTRA, () -> format(
|
||||
"literal without indexing ('%s', huffman=%b, '%s', huffman=%b)",
|
||||
name, nameHuffman, value, valueHuffman));
|
||||
}
|
||||
checkEncoding();
|
||||
encoding = true;
|
||||
@ -442,10 +454,12 @@ public class Encoder {
|
||||
protected final void literalNeverIndexed(int index,
|
||||
CharSequence value,
|
||||
boolean valueHuffman)
|
||||
throws IndexOutOfBoundsException {
|
||||
throws IndexOutOfBoundsException
|
||||
{
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format("literal never indexed ('%s', '%s')",
|
||||
index, value));
|
||||
logger.log(EXTRA, () -> format(
|
||||
"literal never indexed (%s, '%s', huffman=%b)",
|
||||
index, value, valueHuffman));
|
||||
}
|
||||
checkEncoding();
|
||||
encoding = true;
|
||||
@ -458,8 +472,9 @@ public class Encoder {
|
||||
CharSequence value,
|
||||
boolean valueHuffman) {
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format("literal never indexed ('%s', '%s')",
|
||||
name, value));
|
||||
logger.log(EXTRA, () -> format(
|
||||
"literal never indexed ('%s', huffman=%b, '%s', huffman=%b)",
|
||||
name, nameHuffman, value, valueHuffman));
|
||||
}
|
||||
checkEncoding();
|
||||
encoding = true;
|
||||
@ -472,8 +487,9 @@ public class Encoder {
|
||||
boolean valueHuffman)
|
||||
throws IndexOutOfBoundsException {
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format("literal with incremental indexing ('%s', '%s')",
|
||||
index, value));
|
||||
logger.log(EXTRA, () -> format(
|
||||
"literal with incremental indexing (%s, '%s', huffman=%b)",
|
||||
index, value, valueHuffman));
|
||||
}
|
||||
checkEncoding();
|
||||
encoding = true;
|
||||
@ -485,9 +501,10 @@ public class Encoder {
|
||||
boolean nameHuffman,
|
||||
CharSequence value,
|
||||
boolean valueHuffman) {
|
||||
if (logger.isLoggable(EXTRA)) { // TODO: include huffman info?
|
||||
logger.log(EXTRA, () -> format("literal with incremental indexing ('%s', '%s')",
|
||||
name, value));
|
||||
if (logger.isLoggable(EXTRA)) {
|
||||
logger.log(EXTRA, () -> format(
|
||||
"literal with incremental indexing ('%s', huffman=%b, '%s', huffman=%b)",
|
||||
name, nameHuffman, value, valueHuffman));
|
||||
}
|
||||
checkEncoding();
|
||||
encoding = true;
|
||||
@ -506,7 +523,7 @@ public class Encoder {
|
||||
if (capacity > this.maxCapacity) {
|
||||
throw new IllegalArgumentException(
|
||||
format("capacity <= maxCapacity: capacity=%s, maxCapacity=%s",
|
||||
capacity, maxCapacity));
|
||||
capacity, maxCapacity));
|
||||
}
|
||||
writer = sizeUpdateWriter.maxHeaderTableSize(capacity);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ package jdk.internal.net.http.hpack;
|
||||
import jdk.internal.net.http.common.Utils;
|
||||
import jdk.internal.net.http.hpack.HPACK.Logger.Level;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Map;
|
||||
@ -171,4 +172,124 @@ public final class HPACK {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -- low-level utilities --
|
||||
|
||||
@FunctionalInterface
|
||||
interface BufferUpdateConsumer {
|
||||
void accept(long data, int len);
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
public static int read(ByteBuffer source,
|
||||
long buffer,
|
||||
int bufferLen,
|
||||
BufferUpdateConsumer consumer)
|
||||
{
|
||||
// read as much as possible (up to 8 bytes)
|
||||
int nBytes = Math.min((64 - bufferLen) >> 3, source.remaining());
|
||||
switch (nBytes) {
|
||||
case 0:
|
||||
break;
|
||||
case 3:
|
||||
buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
case 2:
|
||||
buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
case 1:
|
||||
buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
consumer.accept(buffer, bufferLen);
|
||||
break;
|
||||
case 7:
|
||||
buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
case 6:
|
||||
buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
case 5:
|
||||
buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
case 4:
|
||||
buffer |= ((source.getInt() & 0x00000000ffffffffL) << (32 - bufferLen));
|
||||
bufferLen += 32;
|
||||
consumer.accept(buffer, bufferLen);
|
||||
break;
|
||||
case 8:
|
||||
buffer = source.getLong();
|
||||
bufferLen = 64;
|
||||
consumer.accept(buffer, bufferLen);
|
||||
break;
|
||||
default:
|
||||
throw new InternalError(String.valueOf(nBytes));
|
||||
}
|
||||
return nBytes;
|
||||
}
|
||||
|
||||
// The number of bytes that can be written at once
|
||||
// (calculating in bytes, not bits, since
|
||||
// destination.remaining() * 8 might overflow)
|
||||
@SuppressWarnings("fallthrough")
|
||||
public static int write(long buffer,
|
||||
int bufferLen,
|
||||
BufferUpdateConsumer consumer,
|
||||
ByteBuffer destination)
|
||||
{
|
||||
int nBytes = Math.min(bufferLen >> 3, destination.remaining());
|
||||
switch (nBytes) {
|
||||
case 0:
|
||||
break;
|
||||
case 3:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
case 2:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
case 1:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
consumer.accept(buffer, bufferLen);
|
||||
break;
|
||||
case 7:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
case 6:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
case 5:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
case 4:
|
||||
destination.putInt((int) (buffer >>> 32));
|
||||
buffer <<= 32;
|
||||
bufferLen -= 32;
|
||||
consumer.accept(buffer, bufferLen);
|
||||
break;
|
||||
case 8:
|
||||
destination.putLong(buffer);
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
consumer.accept(buffer, bufferLen);
|
||||
break;
|
||||
default:
|
||||
throw new InternalError(String.valueOf(nBytes));
|
||||
}
|
||||
return nBytes;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the number of bytes the given number of bits constitute.
|
||||
*/
|
||||
static int bytesForBits(int n) {
|
||||
assert (n / 8 + (n % 8 != 0 ? 1 : 0)) == (n + 7) / 8
|
||||
&& (n + 7) / 8 == ((n + 7) >> 3) : n;
|
||||
return (n + 7) >> 3;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ import jdk.internal.net.http.hpack.HPACK.Logger;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
@ -104,16 +103,24 @@ final class HeaderTable extends SimpleHeaderTable {
|
||||
// Long.MAX_VALUE :-)
|
||||
//
|
||||
|
||||
private static final Map<String, LinkedHashMap<String, Integer>> staticIndexes;
|
||||
/* An immutable map of static header fields' indexes */
|
||||
private static final Map<String, Map<String, Integer>> staticIndexes;
|
||||
|
||||
static {
|
||||
staticIndexes = new HashMap<>(STATIC_TABLE_LENGTH); // TODO: Map.of
|
||||
Map<String, Map<String, Integer>> map
|
||||
= new HashMap<>(STATIC_TABLE_LENGTH);
|
||||
for (int i = 1; i <= STATIC_TABLE_LENGTH; i++) {
|
||||
HeaderField f = staticTable[i];
|
||||
Map<String, Integer> values = staticIndexes
|
||||
.computeIfAbsent(f.name, k -> new LinkedHashMap<>());
|
||||
HeaderField f = staticTable.get(i);
|
||||
Map<String, Integer> values
|
||||
= map.computeIfAbsent(f.name, k -> new HashMap<>());
|
||||
values.put(f.value, i);
|
||||
}
|
||||
// create an immutable deep copy
|
||||
Map<String, Map<String, Integer>> copy = new HashMap<>(map.size());
|
||||
for (Map.Entry<String, Map<String, Integer>> e : map.entrySet()) {
|
||||
copy.put(e.getKey(), Map.copyOf(e.getValue()));
|
||||
}
|
||||
staticIndexes = Map.copyOf(copy);
|
||||
}
|
||||
|
||||
// name -> (value -> [index])
|
||||
|
@ -46,29 +46,58 @@ final class ISO_8859_1 {
|
||||
|
||||
public static final class Reader {
|
||||
|
||||
private final HPACK.BufferUpdateConsumer UPDATER =
|
||||
(buf, bufLen) -> {
|
||||
buffer = buf;
|
||||
bufferLen = bufLen;
|
||||
};
|
||||
|
||||
private long buffer;
|
||||
private int bufferLen;
|
||||
|
||||
public void read(ByteBuffer source, Appendable destination)
|
||||
throws IOException {
|
||||
for (int i = 0, len = source.remaining(); i < len; i++) {
|
||||
char c = (char) (source.get() & 0xff);
|
||||
try {
|
||||
destination.append(c);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(
|
||||
"Error appending to the destination", e);
|
||||
throws IOException
|
||||
{
|
||||
while (true) {
|
||||
int nBytes = HPACK.read(source, buffer, bufferLen, UPDATER);
|
||||
if (nBytes == 0) {
|
||||
return;
|
||||
}
|
||||
assert bufferLen % 8 == 0 : bufferLen;
|
||||
while (bufferLen > 0) {
|
||||
char c = (char) (buffer >>> 56);
|
||||
try {
|
||||
destination.append(c);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(
|
||||
"Error appending to the destination", e);
|
||||
}
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Reader reset() {
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Writer {
|
||||
|
||||
private final HPACK.BufferUpdateConsumer UPDATER =
|
||||
(buf, bufLen) -> {
|
||||
buffer = buf;
|
||||
bufferLen = bufLen;
|
||||
};
|
||||
|
||||
private CharSequence source;
|
||||
private int pos;
|
||||
private int end;
|
||||
private long buffer;
|
||||
private int bufferLen;
|
||||
|
||||
public Writer configure(CharSequence source, int start, int end) {
|
||||
this.source = source;
|
||||
@ -78,25 +107,39 @@ final class ISO_8859_1 {
|
||||
}
|
||||
|
||||
public boolean write(ByteBuffer destination) {
|
||||
for (; pos < end; pos++) {
|
||||
char c = source.charAt(pos);
|
||||
if (c > '\u00FF') {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal ISO-8859-1 char: " + (int) c);
|
||||
while (true) {
|
||||
while (true) { // stuff codes into long
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
char c = source.charAt(pos);
|
||||
if (c > 255) {
|
||||
throw new IllegalArgumentException(Integer.toString((int) c));
|
||||
}
|
||||
if (bufferLen <= 56) {
|
||||
buffer |= (((long) c) << (56 - bufferLen)); // append
|
||||
bufferLen += 8;
|
||||
pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (destination.hasRemaining()) {
|
||||
destination.put((byte) c);
|
||||
} else {
|
||||
if (bufferLen == 0) {
|
||||
return true;
|
||||
}
|
||||
int nBytes = HPACK.write(buffer, bufferLen, UPDATER, destination);
|
||||
if (nBytes == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Writer reset() {
|
||||
source = null;
|
||||
pos = -1;
|
||||
end = -1;
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -25,19 +25,21 @@
|
||||
|
||||
package jdk.internal.net.http.hpack;
|
||||
|
||||
import jdk.internal.net.http.hpack.HPACK.BufferUpdateConsumer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static jdk.internal.net.http.hpack.HPACK.bytesForBits;
|
||||
|
||||
public final class QuickHuffman {
|
||||
|
||||
/*
|
||||
* Huffman codes for encoding.
|
||||
*
|
||||
* EOS will never be matched, since there is no symbol for it, thus no need
|
||||
* to store it in the array. Thus, the length of the array is 256, not 257.
|
||||
* Code information for each character is encoded as follows:
|
||||
* Mapping of characters to their code information. Code information
|
||||
* consists of a code value and a code length. Both components are packed in
|
||||
* a single long as follows:
|
||||
*
|
||||
* MSB LSB
|
||||
* +----------------+----------------+
|
||||
@ -46,9 +48,14 @@ public final class QuickHuffman {
|
||||
* |<----- 32 ----->|<----- 32 ----->|
|
||||
* |<------------- 64 -------------->|
|
||||
*
|
||||
* The leftmost 32 bits hold the code value. This value is aligned left
|
||||
* (or MSB). The rightmost 32 bits hold the length of the code value.
|
||||
* This length is aligned right (or LSB).
|
||||
* The leftmost 32 bits hold the code value. This value is aligned left (or
|
||||
* MSB). The rightmost 32 bits hold the code length. This length is aligned
|
||||
* right (or LSB). Such structure is possible thanks to codes being up to 30
|
||||
* bits long and their lengths being up to 5 bits long (length = 5..30).
|
||||
* This allows both components to fit into long and, thus, better locality.
|
||||
*
|
||||
* Input strings never contain EOS. Thus there's no need to provide EOS
|
||||
* mapping. Hence, the length of the array is 256, not 257.
|
||||
*/
|
||||
private static final long[] codes = new long[256];
|
||||
|
||||
@ -62,16 +69,19 @@ public final class QuickHuffman {
|
||||
|
||||
private static final int EOS_LENGTH = 30;
|
||||
private static final int EOS_LSB = 0x3fffffff;
|
||||
private static final long EOS_MSB = EOS_LSB << (64 - EOS_LENGTH);
|
||||
// EOS_LSB is casted to long before the shift, to allow long shift (6 bits)
|
||||
// instead of int shift (5 bits):
|
||||
private static final long EOS_MSB = ((long) EOS_LSB) << (64 - EOS_LENGTH);
|
||||
|
||||
/*
|
||||
* Huffman codes for decoding.
|
||||
* Huffman trie.
|
||||
*
|
||||
* Root node contains 257 descendant nodes, including EOS.
|
||||
* The root node leads to 257 descendant leaf nodes, of which 256 nodes
|
||||
* correspond to characters and 1 node correspond to EOS.
|
||||
*/
|
||||
private static final Node root;
|
||||
private static final Node root = buildTrie();
|
||||
|
||||
static {
|
||||
private static Node buildTrie() {
|
||||
TemporaryNode tmpRoot = new TemporaryNode();
|
||||
addChar(tmpRoot, 0, 0x1ff8, 13);
|
||||
addChar(tmpRoot, 1, 0x7fffd8, 23);
|
||||
@ -333,8 +343,8 @@ public final class QuickHuffman {
|
||||
|
||||
// The difference in performance can always be checked by not using
|
||||
// the immutable trie:
|
||||
// root = tmpRoot;
|
||||
root = ImmutableNode.copyOf(tmpRoot);
|
||||
// return tmpRoot;
|
||||
return ImmutableNode.copyOf(tmpRoot);
|
||||
}
|
||||
|
||||
private QuickHuffman() { }
|
||||
@ -605,6 +615,12 @@ public final class QuickHuffman {
|
||||
|
||||
static final class Reader implements Huffman.Reader {
|
||||
|
||||
private final BufferUpdateConsumer UPDATER =
|
||||
(buf, bufLen) -> {
|
||||
buffer = buf;
|
||||
bufferLen = bufLen;
|
||||
};
|
||||
|
||||
private Node curr = root; // current position in the trie
|
||||
private long buffer; // bits left from the previous match (aligned to the left, or MSB)
|
||||
private int bufferLen; // number of bits in the buffer
|
||||
@ -615,54 +631,10 @@ public final class QuickHuffman {
|
||||
public void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean isLast) throws IOException
|
||||
{
|
||||
read(source, destination, true, isLast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
curr = root;
|
||||
len = 0;
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
done = false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
void read(ByteBuffer source,
|
||||
Appendable destination,
|
||||
boolean reportEOS, /* reportEOS is exposed for tests */
|
||||
boolean isLast) throws IOException
|
||||
{
|
||||
while (!done) {
|
||||
// read as much as possible (up to 8 bytes)
|
||||
int remaining = source.remaining();
|
||||
int nBytes = Math.min((64 - bufferLen) >> 3, remaining);
|
||||
switch (nBytes) {
|
||||
case 0:
|
||||
break;
|
||||
case 3:
|
||||
readByte(source);
|
||||
case 2:
|
||||
readByte(source);
|
||||
case 1:
|
||||
readByte(source);
|
||||
break;
|
||||
case 7:
|
||||
readByte(source);
|
||||
case 6:
|
||||
readByte(source);
|
||||
case 5:
|
||||
readByte(source);
|
||||
case 4:
|
||||
readInt(source);
|
||||
break;
|
||||
case 8:
|
||||
readLong(source);
|
||||
break;
|
||||
default:
|
||||
throw new InternalError(String.valueOf(nBytes));
|
||||
}
|
||||
int nBytes = HPACK.read(source, buffer, bufferLen, UPDATER);
|
||||
// write as much as possible
|
||||
while (true) {
|
||||
if (bufferLen < 8) {
|
||||
@ -671,7 +643,7 @@ public final class QuickHuffman {
|
||||
} else if (!isLast) { // exit the method to accept more input
|
||||
return;
|
||||
} else if (bufferLen > 0) { // no more data is expected, pad
|
||||
// (this padding may be done more than once)
|
||||
// (this padding may be done more than once)
|
||||
buffer |= ((0xff00000000000000L >>> bufferLen)
|
||||
& 0xff00000000000000L);
|
||||
// do not update bufferLen, since all those ones are
|
||||
@ -692,7 +664,7 @@ public final class QuickHuffman {
|
||||
throw new IOException(
|
||||
"Not a EOS prefix padding or unexpected end of data");
|
||||
}
|
||||
if (reportEOS && node.isEOSPath()) {
|
||||
if (node.isEOSPath()) {
|
||||
throw new IOException("Encountered EOS");
|
||||
}
|
||||
destination.append(node.getSymbol());
|
||||
@ -715,27 +687,24 @@ public final class QuickHuffman {
|
||||
}
|
||||
}
|
||||
|
||||
private void readLong(ByteBuffer source) {
|
||||
buffer = source.getLong();
|
||||
bufferLen = 64;
|
||||
}
|
||||
|
||||
private void readInt(ByteBuffer source) {
|
||||
long b;
|
||||
b = source.getInt() & 0x00000000ffffffffL;
|
||||
buffer |= (b << (32 - bufferLen));
|
||||
bufferLen += 32;
|
||||
}
|
||||
|
||||
private void readByte(ByteBuffer source) {
|
||||
long b = source.get() & 0x00000000000000ffL;
|
||||
buffer |= (b << (56 - bufferLen));
|
||||
bufferLen += 8;
|
||||
@Override
|
||||
public void reset() {
|
||||
curr = root;
|
||||
len = 0;
|
||||
buffer = 0;
|
||||
bufferLen = 0;
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Writer implements Huffman.Writer {
|
||||
|
||||
private final BufferUpdateConsumer UPDATER =
|
||||
(buf, bufLen) -> {
|
||||
buffer = buf;
|
||||
bufferLen = bufLen;
|
||||
};
|
||||
|
||||
private CharSequence source;
|
||||
private boolean padded;
|
||||
private int pos;
|
||||
@ -752,43 +721,44 @@ public final class QuickHuffman {
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
@Override
|
||||
public boolean write(ByteBuffer destination) {
|
||||
while (true) {
|
||||
while (bufferLen < 32 && pos < end) {
|
||||
char c = source.charAt(pos++);
|
||||
buffer |= (codeValueOf(c) >>> bufferLen); // append
|
||||
bufferLen += codeLengthOf(c);
|
||||
while (true) { // stuff codes into long
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
char c = source.charAt(pos);
|
||||
if (c > 255) {
|
||||
throw new IllegalArgumentException("char=" + ((int) c));
|
||||
}
|
||||
long len = codeLengthOf(c);
|
||||
if (bufferLen + len <= 64) {
|
||||
buffer |= (codeValueOf(c) >>> bufferLen); // append
|
||||
bufferLen += len;
|
||||
pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bufferLen == 0) {
|
||||
return true;
|
||||
}
|
||||
if (pos >= end && !padded) { // no more chars, pad
|
||||
if (pos >= end && !padded) { // no more input chars are expected, pad
|
||||
padded = true;
|
||||
buffer |= (EOS_MSB >>> bufferLen);
|
||||
bufferLen = bytesForBits(bufferLen) << 3;
|
||||
// A long shift to 64 will result in the same long, not
|
||||
// necessarily 0L. In which case padding will be performed
|
||||
// incorrectly. If bufferLen is equal to 64, the shift (and
|
||||
// padding) in not performed.
|
||||
// (see https://docs.oracle.com/javase/specs/jls/se10/html/jls-15.html#jls-15.19)
|
||||
if (bufferLen != 64) {
|
||||
buffer |= (EOS_MSB >>> bufferLen);
|
||||
bufferLen = bytesForBits(bufferLen) << 3;
|
||||
}
|
||||
}
|
||||
// The number of bytes that can be written at once
|
||||
// (calculating in bytes, not bits, since
|
||||
// destination.remaining() * 8 might overflow)
|
||||
|
||||
int nBytes = Math.min(bytesForBits(bufferLen), destination.remaining()); // ceil?
|
||||
switch (nBytes) {
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
destination.put((byte) (buffer >>> 56));
|
||||
buffer <<= 8;
|
||||
bufferLen -= 8;
|
||||
break;
|
||||
default:
|
||||
destination.putInt((int) (buffer >>> 32));
|
||||
buffer <<= 32;
|
||||
bufferLen -= 32;
|
||||
break;
|
||||
int nBytes = HPACK.write(buffer, bufferLen, UPDATER, destination);
|
||||
if (nBytes == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -814,13 +784,4 @@ public final class QuickHuffman {
|
||||
return bytesForBits(len);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the number of bytes the given number of bits constitute.
|
||||
*/
|
||||
private static int bytesForBits(int n) {
|
||||
assert (n / 8 + (n % 8 != 0 ? 1 : 0)) == (n + 7) / 8
|
||||
&& (n + 7) / 8 == ((n + 7) >> 3) : n;
|
||||
return (n + 7) >> 3;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user