/*
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.transport.http.netty.message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.LastHttpContent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.messaging.CarbonMessage;
import org.wso2.carbon.transport.http.netty.common.Constants;
import org.wso2.carbon.transport.http.netty.listener.ServerBootstrapConfiguration;
import org.wso2.carbon.transport.http.netty.sender.channel.BootstrapConfiguration;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
/**
* HTTP based representation for CarbonMessage.
*/
public class HTTPCarbonMessage extends CarbonMessage {
private static final Logger LOG = LoggerFactory.getLogger(HTTPCarbonMessage.class);
private BlockingQueue<HttpContent> httpContentQueue = new LinkedBlockingQueue<>();
private BlockingQueue<HttpContent> outContentQueue = new LinkedBlockingQueue<>();
private BlockingQueue<HttpContent> garbageCollected = new LinkedBlockingQueue<>();
// Variable to keep the status on whether the last content was added during the clone
private boolean isEndMarked = false;
private int soTimeOut = 60;
public HTTPCarbonMessage() {
BootstrapConfiguration clientBootstrapConfig = BootstrapConfiguration.getInstance();
if (clientBootstrapConfig != null) {
soTimeOut = clientBootstrapConfig.getSocketTimeout();
return;
}
ServerBootstrapConfiguration serverBootstrapConfiguration = ServerBootstrapConfiguration.getInstance();
if (serverBootstrapConfiguration != null) {
soTimeOut = serverBootstrapConfiguration.getSoTimeOut();
}
}
public void addHttpContent(HttpContent httpContent) {
try {
httpContentQueue.put(httpContent);
} catch (InterruptedException e) {
LOG.error("Cannot put content to queue", e);
}
}
public HttpContent getHttpContent() {
try {
return httpContentQueue.poll(soTimeOut, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOG.error("Error while retrieving http content from queue.", e);
return null;
}
}
@Override
public ByteBuffer getMessageBody() {
try {
HttpContent httpContent = httpContentQueue.poll(soTimeOut, TimeUnit.SECONDS);
if (httpContent instanceof LastHttpContent) {
super.setEndOfMsgAdded(true);
}
ByteBuf buf = httpContent.content();
garbageCollected.add(httpContent);
return buf.nioBuffer();
} catch (InterruptedException e) {
LOG.error("Error while retrieving message body from queue.", e);
return null;
}
}
@Override
public List<ByteBuffer> getFullMessageBody() {
List<ByteBuffer> byteBufferList = new ArrayList<>();
boolean isEndOfMessageProcessed = false;
while (!isEndOfMessageProcessed) {
try {
HttpContent httpContent = httpContentQueue.poll(soTimeOut, TimeUnit.SECONDS);
// This check is to make sure we add the last http content after getClone and avoid adding
// empty content to bytebuf list again and again
if (httpContent instanceof EmptyLastHttpContent) {
break;
}
if (httpContent instanceof LastHttpContent) {
isEndOfMessageProcessed = true;
}
ByteBuf buf = httpContent.content();
garbageCollected.add(httpContent);
byteBufferList.add(buf.nioBuffer());
} catch (InterruptedException e) {
LOG.error("Error while getting full message body", e);
}
}
return byteBufferList;
}
@Override
public InputStream getInputStream() {
String contentEncodingHeader = getHeader(Constants.CONTENT_ENCODING);
if (contentEncodingHeader != null) {
// removing the header because, we are handling the decoded content and we need to send out
// as encoded one. so once this header is removed, transport will encode again by looking the
// accept-encoding request header
removeHeader(Constants.CONTENT_ENCODING);
try {
if (contentEncodingHeader.equalsIgnoreCase(Constants.ENCODING_GZIP)) {
return new GZIPInputStream(super.getInputStream());
} else if (contentEncodingHeader.equalsIgnoreCase(Constants.ENCODING_DEFLATE)) {
return new InflaterInputStream(super.getInputStream());
} else {
LOG.warn("Unknown Content-Encoding: " + contentEncodingHeader);
}
} catch (IOException e) {
LOG.error("Error while creating inputStream for content-encoding: " + contentEncodingHeader, e);
}
}
return super.getInputStream();
}
@Override
public boolean isEmpty() {
return this.httpContentQueue.isEmpty();
}
@Override
public int getFullMessageLength() {
List<HttpContent> contentList = new ArrayList<>();
boolean isEndOfMessageProcessed = false;
while (!isEndOfMessageProcessed) {
try {
HttpContent httpContent = httpContentQueue.poll(soTimeOut, TimeUnit.SECONDS);
if ((httpContent instanceof LastHttpContent) || (isEndOfMsgAdded() && httpContentQueue.isEmpty())) {
isEndOfMessageProcessed = true;
}
contentList.add(httpContent);
} catch (InterruptedException e) {
LOG.error("Error while getting full message length", e);
}
}
int size = 0;
for (HttpContent httpContent : contentList) {
size += httpContent.content().readableBytes();
httpContentQueue.add(httpContent);
}
return size;
}
@Override
public boolean isEndOfMsgAdded() {
return super.isEndOfMsgAdded();
}
@Override
public void addMessageBody(ByteBuffer msgBody) {
if (isAlreadyRead()) {
outContentQueue.add(new DefaultHttpContent(Unpooled.copiedBuffer(msgBody)));
} else {
httpContentQueue.add(new DefaultHttpContent(Unpooled.copiedBuffer(msgBody)));
}
}
public void markMessageEnd() {
if (isAlreadyRead()) {
outContentQueue.add(new EmptyLastHttpContent());
} else {
httpContentQueue.add(new EmptyLastHttpContent());
}
}
@Override
public void setEndOfMsgAdded(boolean endOfMsgAdded) {
super.setEndOfMsgAdded(endOfMsgAdded);
if (isAlreadyRead()) {
outContentQueue.forEach(buffer -> {
httpContentQueue.add(buffer);
});
outContentQueue.clear();
}
}
@Override
public void release() {
httpContentQueue.forEach(content -> content.release());
garbageCollected.forEach(content -> content.release());
}
}