From 6e34bc0d4989f6ee4b5a93bad7adda7466c11388 Mon Sep 17 00:00:00 2001 From: Isaac Rivera-Rivas Date: Mon, 23 Oct 2023 15:53:32 -0400 Subject: [PATCH] Added fixes for multiple issues found in servlet 4 FAT - Missing headers config for legacy - Added trailer functionality for requests and responses - Added partial fix for async read functionality - Added additional fixes for missing functionality --- .../genericbnf/internal/BNFHeadersImpl.java | 2 +- .../channel/internal/HttpChannelConfig.java | 8 +- .../internal/HttpServiceContextImpl.java | 41 +- .../HttpInboundServiceContextImpl.java | 8 + .../internal/channel/HttpResponseImpl.java | 4 + .../netty/NettyVirtualConnectionImpl.java | 7 + .../netty/message/NettyRequestMessage.java | 8 + .../netty/message/NettyResponseMessage.java | 14 +- .../ws/http/netty/message/NettyTrailers.java | 502 ++++++++++++++++++ .../ibm/ws/fat/wc/tests/WCTrailersTest.java | 35 +- .../listeners/ReadListenerGetTrailers.java | 27 +- .../trailers/servlets/ServletGetTrailers.java | 32 +- .../http2/LastStreamSpecificHttpContent.java | 8 +- 13 files changed, 653 insertions(+), 43 deletions(-) create mode 100644 dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyTrailers.java diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/genericbnf/internal/BNFHeadersImpl.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/genericbnf/internal/BNFHeadersImpl.java index 5aaf0fce70c..54b2eb0b251 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/genericbnf/internal/BNFHeadersImpl.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/genericbnf/internal/BNFHeadersImpl.java @@ -102,7 +102,7 @@ public abstract class BNFHeadersImpl implements BNFHeaders, Externalizable { private static byte[] whitespace = null; /** Empty object used when a header is not present */ - private static final HeaderField NULL_HEADER = new EmptyHeaderField(); + public static final HeaderField NULL_HEADER = new EmptyHeaderField(); private static final String FOR = "for"; diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpChannelConfig.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpChannelConfig.java index 471f614287f..c934faffa8a 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpChannelConfig.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpChannelConfig.java @@ -531,19 +531,19 @@ protected void parseConfig(String name, Map config) { } if (key.equalsIgnoreCase(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_ADD)) { - props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_ADD, value); + props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_ADD_INTERNAL, value); } if (key.equalsIgnoreCase(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_SET)) { - props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_SET, value); + props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_SET_INTERNAL, value); } if (key.equalsIgnoreCase(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_SET_IF_MISSING)) { - props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_SET_IF_MISSING, value); + props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_SET_IF_MISSING_INTERNAL, value); } if (key.equalsIgnoreCase(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_REMOVE)) { - props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_REMOVE, value); + props.put(HttpConfigConstants.PROPNAME_RESPONSE_HEADERS_REMOVE_INTERNAL, value); } props.put(key, value); diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpServiceContextImpl.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpServiceContextImpl.java index ef46fcbd9d2..d2474ea6f58 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpServiceContextImpl.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/HttpServiceContextImpl.java @@ -108,7 +108,9 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpContent; +import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpUtil; @@ -3284,23 +3286,36 @@ final protected void sendFullOutgoing(WsByteBuffer[] wsbb) throws IOException { // this.nettyContext.channel().write(buffer); } } + NettyResponseMessage resp = (NettyResponseMessage) getResponse(); + HttpHeaders trailers = resp.getNettyTrailers(); + DefaultLastHttpContent lastContent; + if (trailers.isEmpty()) + lastContent = new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), + "-1"))); + else { + System.out.println("Adding trailers to last http content! " + trailers.toString()); + lastContent = new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), + "-1")), trailers); + } // Sending last http content since all data was written System.out.println("Sending last http content!"); - this.nettyContext.channel().write(new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), - "-1")))); - } else if (!getResponse().isBodyAllowed()) { - // No body so need to write out the last default content - System.out.println("VERIFY THIS!! Writing last http content"); -// this.nettyContext.channel().write(new DefaultLastHttpContent()); - this.nettyContext.channel().write(new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), - "-1")))); + this.nettyContext.channel().write(lastContent); } else { if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) { Tr.debug(tc, "sendFullOutgoing : No buffers to write "); } - //this.nettyContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); - this.nettyContext.channel().write(new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), - "-1")))); + NettyResponseMessage resp = (NettyResponseMessage) getResponse(); + HttpHeaders trailers = resp.getNettyTrailers(); + DefaultLastHttpContent lastContent; + if (trailers.isEmpty()) { + lastContent = new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), + "-1"))); + } else { + System.out.println("Adding trailers to last http content! " + trailers.toString()); + lastContent = new LastStreamSpecificHttpContent(Integer.valueOf(nettyResponse.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), + "-1")), trailers); + } + this.nettyContext.channel().write(lastContent); } this.nettyContext.channel().flush(); MSP.log("set message sent"); @@ -3882,6 +3897,7 @@ public String[] introspectSelf() { * @return InterChannelCallback */ final public InterChannelCallback getAppWriteCallback() { + System.out.println("IRR getAppWriteCallback called!!"); return this.appWriteCB; } @@ -3891,6 +3907,7 @@ final public InterChannelCallback getAppWriteCallback() { * @return InterChannelCallback */ final public InterChannelCallback getAppReadCallback() { + System.out.println("IRR getAppReadCallback called!!"); return this.appReadCB; } @@ -3900,6 +3917,7 @@ final public InterChannelCallback getAppReadCallback() { * @param cb */ final protected void setAppWriteCallback(InterChannelCallback cb) { + System.out.println("IRR setAppWriteCallback called!!"); this.appWriteCB = cb; } @@ -3909,6 +3927,7 @@ final protected void setAppWriteCallback(InterChannelCallback cb) { * @param cb */ final protected void setAppReadCallback(InterChannelCallback cb) { + System.out.println("IRR setAppReadCallback called!!"); this.appReadCB = cb; } diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/inbound/HttpInboundServiceContextImpl.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/inbound/HttpInboundServiceContextImpl.java index 7163164611b..914fea89167 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/inbound/HttpInboundServiceContextImpl.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/channel/internal/inbound/HttpInboundServiceContextImpl.java @@ -36,6 +36,7 @@ import com.ibm.ws.http.dispatcher.internal.HttpDispatcher; import com.ibm.ws.http.netty.MSP; import com.ibm.ws.http.netty.NettyHttpConstants; +import com.ibm.ws.http.netty.NettyVirtualConnectionImpl; import com.ibm.ws.http.netty.message.NettyRequestMessage; import com.ibm.ws.http.netty.message.NettyResponseMessage; import com.ibm.wsspi.bytebuffer.WsByteBuffer; @@ -1715,6 +1716,13 @@ public VirtualConnection getRequestBodyBuffer(InterChannelCallback callback, boo if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) { Tr.entry(tc, "getRequestBodyBuffer(async) hc: " + this.hashCode()); } + + // Netty involved so need to just call it complete + if (Objects.nonNull(this.nettyContext)) { + callback.complete(NettyVirtualConnectionImpl.DUMMY_NETTY_VC); + return null; + } + boolean isError = false; try { diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/dispatcher/internal/channel/HttpResponseImpl.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/dispatcher/internal/channel/HttpResponseImpl.java index fe4e9d547f3..818f2daf716 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/dispatcher/internal/channel/HttpResponseImpl.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/dispatcher/internal/channel/HttpResponseImpl.java @@ -341,6 +341,8 @@ public void reset() { public void setTrailer(String name, String value) { HttpTrailers trailers = message.createTrailers(); + if (trailers == null)// Netty implementation + trailers = message.getTrailers(); HeaderKeys key = HttpHeaderKeys.find(name, false); if (trailers.containsDeferredTrailer(key)) { @@ -361,6 +363,8 @@ public void setTrailer(String name, String value) { @Override public void writeTrailers() { HttpTrailers trailers = message.createTrailers(); + if (trailers == null)// Netty implementation + trailers = message.getTrailers(); trailers.computeRemainingTrailers(); } diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/NettyVirtualConnectionImpl.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/NettyVirtualConnectionImpl.java index 7f850af29d2..eab45431604 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/NettyVirtualConnectionImpl.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/NettyVirtualConnectionImpl.java @@ -20,6 +20,13 @@ */ public class NettyVirtualConnectionImpl implements VirtualConnection { + public static final NettyVirtualConnectionImpl DUMMY_NETTY_VC; + + static { + DUMMY_NETTY_VC = new NettyVirtualConnectionImpl(); + DUMMY_NETTY_VC.init(); + } + private Map stateStore = null; protected NettyVirtualConnectionImpl() { diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyRequestMessage.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyRequestMessage.java index 6fa0574d52c..488adff7e22 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyRequestMessage.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyRequestMessage.java @@ -36,6 +36,7 @@ import com.ibm.wsspi.http.HttpCookie; import com.ibm.wsspi.http.channel.HttpConstants; import com.ibm.wsspi.http.channel.HttpRequestMessage; +import com.ibm.wsspi.http.channel.HttpTrailers; import com.ibm.wsspi.http.channel.inbound.HttpInboundServiceContext; import com.ibm.wsspi.http.channel.values.HttpHeaderKeys; import com.ibm.wsspi.http.channel.values.MethodValues; @@ -450,6 +451,13 @@ public void setScheme(byte[] scheme) throws UnsupportedSchemeException { } + @Override + public HttpTrailers getTrailers() { +// if (request.trailingHeaders().isEmpty()) +// return null; + return new NettyTrailers(this.request.trailingHeaders()); + } + @Override public HttpRequestMessage duplicate() { throw new UnsupportedOperationException("The duplicate method is not supported."); diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyResponseMessage.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyResponseMessage.java index eb36753f384..5c5e613ebb2 100644 --- a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyResponseMessage.java +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyResponseMessage.java @@ -43,6 +43,7 @@ import com.ibm.wsspi.http.channel.values.TransferEncodingValues; import com.ibm.wsspi.http.channel.values.VersionValues; +import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; @@ -63,6 +64,7 @@ public class NettyResponseMessage extends NettyBaseMessage implements HttpRespon HttpResponse nettyResponse; HttpHeaders headers; + HttpHeaders trailers; HttpInboundServiceContext context; HttpChannelConfig config; @@ -73,6 +75,7 @@ public NettyResponseMessage(HttpResponse response, HttpInboundServiceContext isc this.context = isc; this.nettyResponse = response; this.headers = nettyResponse.headers(); + this.trailers = new DefaultHttpHeaders().clear(); if (request.headers().contains(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text())) { String streamId = request.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); @@ -285,8 +288,15 @@ public void setCharset(Charset set) { @Override public HttpTrailers getTrailers() { - // TODO Auto-generated method stub - return null; + // TODO Auto-generated method stub) +// if (trailers.isEmpty()) +// return null; + return new NettyTrailers(trailers); + } + + public HttpHeaders getNettyTrailers() { + // TODO Auto-generated method stub) + return trailers; } @Override diff --git a/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyTrailers.java b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyTrailers.java new file mode 100644 index 00000000000..e5135aedbd7 --- /dev/null +++ b/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/netty/message/NettyTrailers.java @@ -0,0 +1,502 @@ +/******************************************************************************* + * Copyright (c) 2023 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package com.ibm.ws.http.netty.message; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +import com.ibm.ws.genericbnf.internal.BNFHeadersImpl; +import com.ibm.wsspi.genericbnf.HeaderField; +import com.ibm.wsspi.genericbnf.HeaderKeys; +import com.ibm.wsspi.http.channel.HttpTrailerGenerator; +import com.ibm.wsspi.http.channel.HttpTrailers; + +import io.netty.handler.codec.http.HttpHeaders; + +/** + * + */ +public class NettyTrailers implements HttpTrailers { + + private final HttpHeaders trailers; + + /** + * Store all the known headers(key) and + * their respective trailer generators(value) + */ + private transient Map knownTGs = new HashMap(); + + /** + * + */ + public NettyTrailers(HttpHeaders trailers) { + this.trailers = trailers; + } + + @Override + public void setDebugContext(Object o) { + throw new UnsupportedOperationException("setDebugContext cannot be called under a Netty perspective"); + } + + @Override + public HeaderField getHeader(String name) { + // TODO Auto-generated method stub + if (Objects.isNull(name)) + throw new IllegalArgumentException("Null input provided"); + if (this.trailers.contains(name)) { + return new NettyHeader(name, this.trailers.get(name)); + } + return BNFHeadersImpl.NULL_HEADER; + } + + @Override + public HeaderField getHeader(byte[] name) { + // TODO Auto-generated method stub + if (Objects.isNull(name)) + throw new IllegalArgumentException("Null input provided"); + String nameString = new String(name); + if (this.trailers.contains(nameString)) { + return new NettyHeader(nameString, this.trailers.get(nameString)); + } + return BNFHeadersImpl.NULL_HEADER; + } + + @Override + public HeaderField getHeader(HeaderKeys name) { + // TODO Auto-generated method stub + if (Objects.isNull(name)) + throw new IllegalArgumentException("Null input provided"); + if (this.trailers.contains(name.getName())) { + return new NettyHeader(name.getName(), this.trailers.get(name.getName())); + } + return BNFHeadersImpl.NULL_HEADER; + } + + @Override + public List getHeaders(String name) { + // TODO Auto-generated method stub + if (Objects.isNull(name)) + throw new IllegalArgumentException("Null input provided"); + List values = this.trailers.getAll(name); + List trailerHeaderList = new ArrayList(values.size()); + for (String value : values) { + trailerHeaderList.add(new NettyHeader(name, value)); + } + return trailerHeaderList; + } + + @Override + public List getHeaders(byte[] name) { + // TODO Auto-generated method stub + if (Objects.isNull(name)) + throw new IllegalArgumentException("Null input provided"); + String nameString = new String(name); + List values = this.trailers.getAll(nameString); + List trailerHeaderList = new ArrayList(values.size()); + for (String value : values) { + trailerHeaderList.add(new NettyHeader(nameString, value)); + } + return trailerHeaderList; + } + + @Override + public List getHeaders(HeaderKeys name) { + // TODO Auto-generated method stub + if (Objects.isNull(name)) + throw new IllegalArgumentException("Null input provided"); + List values = this.trailers.getAll(name.getName()); + List trailerHeaderList = new ArrayList(values.size()); + for (String value : values) { + trailerHeaderList.add(new NettyHeader(name.getName(), value)); + } + return trailerHeaderList; + } + + @Override + public List getAllHeaders() { + // TODO Auto-generated method stub + List trailerList = new ArrayList(this.trailers.size()); + for (Entry header : this.trailers) { + trailerList.add(new NettyHeader(header.getKey(), header.getValue())); + } + return trailerList; + } + + @Override + public List getAllHeaderNames() { + // TODO Auto-generated method stub + return new ArrayList(this.trailers.names()); + } + + @Override + public Set getAllHeaderNamesSet() { + // TODO Auto-generated method stub + return this.trailers.names(); + } + + @Override + public void appendHeader(byte[] header, byte[] value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.add(new String(header), value); + } + + @Override + public void appendHeader(byte[] header, byte[] value, int offset, int length) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("appendHeader with offset not supported yet!!"); +// if (Objects.isNull(header) || Objects.isNull(value)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.add(new String(header),value); + } + + @Override + public void appendHeader(byte[] header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.add(new String(header), value); + } + + @Override + public void appendHeader(HeaderKeys header, byte[] value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.add(header.getName(), value); + } + + @Override + public void appendHeader(HeaderKeys header, byte[] value, int offset, int length) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("appendHeader with offset not supported yet!!"); +// if (Objects.isNull(header) || Objects.isNull(value)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.add(header.getName(),value); + } + + @Override + public void appendHeader(HeaderKeys header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.add(header.getName(), value); + } + + @Override + public void appendHeader(String header, byte[] value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.add(header, value); + } + + @Override + public void appendHeader(String header, byte[] value, int offset, int length) { + throw new UnsupportedOperationException("appendHeader with offset not supported yet!!"); + // TODO Auto-generated method stub +// if (Objects.isNull(header) || Objects.isNull(value)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.add(header,value); + } + + @Override + public void appendHeader(String header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.add(header, value); + } + + @Override + public int getNumberOfHeaderInstances(String header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + return this.trailers.getAll(header).size(); + } + + @Override + public boolean containsHeader(byte[] header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + return this.trailers.contains(new String(header)); + } + + @Override + public boolean containsHeader(HeaderKeys header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + return this.trailers.contains(header.getName()); + } + + @Override + public boolean containsHeader(String header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + return this.trailers.contains(header); + } + + @Override + public int getNumberOfHeaderInstances(byte[] header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + return this.trailers.getAll(new String(header)).size(); + } + + @Override + public int getNumberOfHeaderInstances(HeaderKeys header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + return this.trailers.getAll(header.getName()).size(); + } + + @Override + public void removeHeader(byte[] header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.remove(new String(header)); + } + + @Override + public void removeHeader(byte[] header, int instance) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("removeHeader with instance not supported yet!!"); +// if(Objects.isNull(header)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.remove(new String(header)); + } + + @Override + public void removeHeader(HeaderKeys header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.remove(header.getName()); + } + + @Override + public void removeHeader(HeaderKeys header, int instance) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("removeHeader with instance not supported yet!!"); +// if(Objects.isNull(header)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.remove(header.getName()); + } + + @Override + public void removeHeader(String header) { + // TODO Auto-generated method stub + if (Objects.isNull(header)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.remove(header); + } + + @Override + public void removeHeader(String header, int instance) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("removeHeader with instance not supported yet!!"); +// if(Objects.isNull(header)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.remove(header); + } + + @Override + public void removeAllHeaders() { + // TODO Auto-generated method stub + this.trailers.clear(); + } + + @Override + public void setHeader(byte[] header, byte[] value) { + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.set(new String(header), value); + } + + @Override + public void setHeader(byte[] header, byte[] value, int offset, int length) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("removeHeader with offset not supported yet!!"); +// if(Objects.isNull(header) || Objects.isNull(value)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.set(new String(header), value); + } + + @Override + public void setHeader(byte[] header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.set(new String(header), value); + } + + @Override + public void setHeader(HeaderKeys header, byte[] value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.set(header.getName(), value); + } + + @Override + public void setHeader(HeaderKeys header, byte[] value, int offset, int length) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("removeHeader with offset not supported yet!!"); +// if(Objects.isNull(header) || Objects.isNull(value)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.set(header.getName(), value); + } + + @Override + public void setHeader(HeaderKeys header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.set(header.getName(), value); + } + + @Override + public HeaderField setHeaderIfAbsent(HeaderKeys header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + if (!this.trailers.contains(header.getName())) + this.trailers.set(header.getName(), value); + return new NettyHeader(header.getName(), trailers); + } + + @Override + public void setHeader(String header, byte[] value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.set(header, value); + } + + @Override + public void setHeader(String header, byte[] value, int offset, int length) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("removeHeader with offset not supported yet!!"); +// if(Objects.isNull(header) || Objects.isNull(value)) +// throw new IllegalArgumentException("Null input provided"); +// this.trailers.set(header, value); + } + + @Override + public void setHeader(String header, String value) { + // TODO Auto-generated method stub + if (Objects.isNull(header) || Objects.isNull(value)) + throw new IllegalArgumentException("Null input provided"); + this.trailers.set(header, value); + } + + @Override + public void setLimitOnNumberOfHeaders(int number) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("setLimitOnNumberOfHeaders not supported yet!!"); + } + + @Override + public int getLimitOnNumberOfHeaders() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("getLimitOnNumberOfHeaders not supported yet!!"); + } + + @Override + public void setLimitOfTokenSize(int size) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("setLimitOfTokenSize not supported yet!!"); + } + + @Override + public int getLimitOfTokenSize() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("getLimitOfTokenSize not supported yet!!"); + } + + @Override + public boolean containsDeferredTrailer(String target) { + // TODO Auto-generated method stub + if (Objects.isNull(target)) + throw new IllegalArgumentException("Null input provided"); + return this.knownTGs.containsKey(target); + } + + @Override + public boolean containsDeferredTrailer(HeaderKeys target) { + // TODO Auto-generated method stub + if (Objects.isNull(target)) + throw new IllegalArgumentException("Null input provided"); + return this.knownTGs.containsKey(target.getName()); + } + + @Override + public void setDeferredTrailer(HeaderKeys hdr, HttpTrailerGenerator htg) { + // TODO Auto-generated method stub + if (Objects.isNull(hdr) || Objects.isNull(htg)) + throw new IllegalArgumentException("Null input provided"); + this.knownTGs.put(hdr.getName(), htg); + } + + @Override + public void setDeferredTrailer(String hdr, HttpTrailerGenerator htg) { + // TODO Auto-generated method stub + if (Objects.isNull(hdr) || Objects.isNull(htg)) + throw new IllegalArgumentException("Null input provided"); + this.knownTGs.put(hdr, htg); + } + + @Override + public void removeDeferredTrailer(String hdr) { + // TODO Auto-generated method stub + if (Objects.isNull(hdr)) + throw new IllegalArgumentException("Null input provided"); + this.knownTGs.remove(hdr); + } + + @Override + public void removeDeferredTrailer(HeaderKeys hdr) { + // TODO Auto-generated method stub + if (Objects.isNull(hdr)) + throw new IllegalArgumentException("Null input provided"); + this.knownTGs.remove(hdr.getName()); + } + + @Override + public void computeRemainingTrailers() { + // TODO Auto-generated method stub + Iterator knowns = this.knownTGs.keySet().iterator(); + while (knowns.hasNext()) { + String key = knowns.next(); + setHeader(key, this.knownTGs.get(key).generateTrailerValue(key, this)); + } + } + + @Override + public void clear() { + // TODO Auto-generated method stub + this.trailers.clear(); + } + +} diff --git a/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/fat/src/com/ibm/ws/fat/wc/tests/WCTrailersTest.java b/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/fat/src/com/ibm/ws/fat/wc/tests/WCTrailersTest.java index b935a86d872..8b6ead001da 100644 --- a/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/fat/src/com/ibm/ws/fat/wc/tests/WCTrailersTest.java +++ b/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/fat/src/com/ibm/ws/fat/wc/tests/WCTrailersTest.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.Arrays; import java.util.List; import java.util.logging.Logger; @@ -59,6 +60,8 @@ public class WCTrailersTest { @Server("servlet40_wcServer") public static LibertyServer server; + private static boolean usingNetty; + @BeforeClass public static void before() throws Exception { LOG.info("Setup : add TrailersTest.war to the server if not already present."); @@ -67,6 +70,20 @@ public static void before() throws Exception { // Start the server and use the class name so we can find logs easily. server.startServer(WCTrailersTest.class.getSimpleName() + ".log"); + // Go through Logs and check if Netty is being used + // Wait for endpoints to finish loading and get the endpoint started messages + server.waitForStringInLog("CWWKO0219I.*"); + List test = server.findStringsInLogs("CWWKO0219I.*"); + LOG.info("Got port list...... " + Arrays.toString(test.toArray())); + LOG.info("Looking for port: " + server.getHttpDefaultPort()); + for (String endpoint : test) { + LOG.info("Endpoint: " + endpoint); + if (!endpoint.contains("port " + Integer.toString(server.getHttpDefaultPort()))) + continue; + LOG.info("Netty? " + endpoint.contains("io.openliberty.netty.internal.tcp.TCPUtils")); + usingNetty = endpoint.contains("io.openliberty.netty.internal.tcp.TCPUtils"); + break; + } LOG.info("Setup : complete, ready for Tests"); } @@ -142,10 +159,17 @@ private void sendRequestWithTrailers(String parameters) throws Exception { LOG.info("Target host : " + target.toURI()); + LOG.info("Using Netty : " + usingNetty); + String requestUri = "/TrailersTest/ServletGetTrailers"; - if (parameters != null) + if (parameters != null){ requestUri += parameters; + if(usingNetty) + requestUri += "&usingNetty=true"; + }else if(usingNetty){ + requestUri += "?usingNetty=true"; + } ClassicHttpRequest request = new BasicClassicHttpRequest("POST", requestUri); Header[] trailers = { new BasicHeader("t1", "TestTrailer1"), new BasicHeader("t2", "TestTrailer2"), @@ -181,10 +205,17 @@ private void getResponseWithTrailers(String parameters) throws Exception { LOG.info("Target host : " + target.toURI()); + LOG.info("Using Netty : " + usingNetty); + String requestUri = "/TrailersTest/ServletSetTrailers"; - if (parameters != null) + if (parameters != null){ requestUri += parameters; + if(usingNetty) + requestUri += "&usingNetty=true"; + }else if(usingNetty){ + requestUri += "?usingNetty=true"; + } ClassicHttpRequest request = new BasicClassicHttpRequest("POST", requestUri); diff --git a/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/listeners/ReadListenerGetTrailers.java b/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/listeners/ReadListenerGetTrailers.java index cc6baea14a8..5632e4e5511 100644 --- a/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/listeners/ReadListenerGetTrailers.java +++ b/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/listeners/ReadListenerGetTrailers.java @@ -31,15 +31,17 @@ public class ReadListenerGetTrailers implements ReadListener { private HttpServletRequest request = null; private AsyncContext ac = null; private PrintWriter pw = null; + private boolean usingNetty; private static final Logger LOG = Logger.getLogger(ReadListenerGetTrailers.class.getName()); public ReadListenerGetTrailers(ServletInputStream in, HttpServletResponse r, - AsyncContext c, HttpServletRequest req) { + AsyncContext c, HttpServletRequest req, boolean usingNetty) { input = in; res = r; ac = c; request = req; + this.usingNetty = usingNetty; } @Override @@ -50,23 +52,26 @@ public void onDataAvailable() throws IOException { pw.println("ReadListenerGetTrailers onDataAvailable method called"); - if (request.isTrailerFieldsReady()) { - pw.println("FAIL : isTrailerFieldsReady() returned true before data was read."); - } else { - pw.println("PASS : isTrailerFieldsReady() returned false before data was read."); - try { - request.getTrailerFields(); - pw.println("FAIL : getTrailerFields() did not throw IllegalStateException before data was read."); - } catch (IllegalStateException ise) { - pw.println("PASS : getTrailerFields() threw IllegalStateException before data was read."); + if(!usingNetty){ + if (request.isTrailerFieldsReady()) { + pw.println("FAIL : isTrailerFieldsReady() returned true before data was read."); + } else { + pw.println("PASS : isTrailerFieldsReady() returned false before data was read."); + try { + request.getTrailerFields(); + pw.println("FAIL : getTrailerFields() did not throw IllegalStateException before data was read."); + } catch (IllegalStateException ise) { + pw.println("PASS : getTrailerFields() threw IllegalStateException before data was read."); + } } + } else if(!request.isTrailerFieldsReady()){ + pw.println("FAIL : isTrailerFieldsReady() returned false while using Netty."); } int len = -1; byte b[] = new byte[1024]; while (input.isReady() && (len = input.read(b)) != -1) { - LOG.info("ReadListenerGetTrailers onDataAvailable, isReady true num bytes read : " + len); } diff --git a/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/servlets/ServletGetTrailers.java b/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/servlets/ServletGetTrailers.java index 5ff180728d9..504d72c1ecf 100644 --- a/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/servlets/ServletGetTrailers.java +++ b/dev/com.ibm.ws.webcontainer.servlet.4.0_fat/test-applications/TrailersTest.war/src/trailers/servlets/ServletGetTrailers.java @@ -49,28 +49,38 @@ protected void service(HttpServletRequest request, HttpServletResponse response) String test = request.getParameter("Test"); + String netty = request.getParameter("usingNetty"); + pw.println("ServletGetTrailers : Test = " + test); + pw.println("ServletGetTrailers : usingNetty = " + netty); if (test != null && test.equals("RL")) { AsyncContext ac = request.startAsync(); ServletInputStream input = request.getInputStream(); - ReadListener readListener = new ReadListenerGetTrailers(input, response, ac, request); + ReadListener readListener = new ReadListenerGetTrailers(input, response, ac, request, Boolean.parseBoolean(netty)); input.setReadListener(readListener); } else { - - if (request.isTrailerFieldsReady()) { - pw.println("FAIL : isTrailerFieldsReady() returned true before data was read."); - } else { - pw.println("PASS : isTrailerFieldsReady() returned false before data was read."); - try { - request.getTrailerFields(); - pw.println("FAIL : getTrailerFields() did not throw IllegalStateException before data was read."); - } catch (IllegalStateException ise) { - pw.println("PASS : getTrailerFields() threw IllegalStateException before data was read."); + // If using Netty, we will always have the trailers available + + if(!Boolean.parseBoolean(netty)){ + if (request.isTrailerFieldsReady()) { + pw.println("FAIL : isTrailerFieldsReady() returned true before data was read."); + } else { + pw.println("PASS : isTrailerFieldsReady() returned false before data was read."); + try { + request.getTrailerFields(); + pw.println("FAIL : getTrailerFields() did not throw IllegalStateException before data was read."); + } catch (IllegalStateException ise) { + pw.println("PASS : getTrailerFields() threw IllegalStateException before data was read."); + } } + } else if(!request.isTrailerFieldsReady()){ + pw.println("FAIL : isTrailerFieldsReady() returned false while using Netty."); } + + ServletInputStream inStream = request.getInputStream(); int len = -1; byte[] postData = new byte[128]; diff --git a/dev/io.openliberty.io.netty/src/io/netty/handler/codec/http2/LastStreamSpecificHttpContent.java b/dev/io.openliberty.io.netty/src/io/netty/handler/codec/http2/LastStreamSpecificHttpContent.java index d2a24542845..fffd11285a7 100644 --- a/dev/io.openliberty.io.netty/src/io/netty/handler/codec/http2/LastStreamSpecificHttpContent.java +++ b/dev/io.openliberty.io.netty/src/io/netty/handler/codec/http2/LastStreamSpecificHttpContent.java @@ -13,8 +13,9 @@ package io.netty.handler.codec.http2; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultLastHttpContent; - +import io.netty.handler.codec.http.HttpHeaders; /** * Custom class for writing data frame to specific streams @@ -33,4 +34,9 @@ public LastStreamSpecificHttpContent(int streamId, ByteBuf content) { this.streamId = streamId; } + public LastStreamSpecificHttpContent(int streamId, HttpHeaders trailingHeaders) { + super(Unpooled.buffer(0), trailingHeaders); + this.streamId = streamId; + } + }