package kg.apc.jmeter.samplers;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.util.Scanner;
import java.util.regex.Pattern;
import kg.apc.io.SocketChannelWithTimeouts;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
// FIXME: actually keep-alive does not work!
public class HTTPRawSampler extends AbstractIPSampler {
private static final String FILE_NAME = "fileName";
private static final String KEEPALIVE = "keepalive";
private static final String PARSE = "parse";
private static final String RNpattern = "\\r\\n";
private static final String SPACE = " ";
//
protected static final Logger log = LoggingManager.getLoggerForClass();
private static final Pattern anyContent = Pattern.compile(".+", Pattern.DOTALL);
private SocketChannel savedSock;
private static final int fileSendingChunk = JMeterUtils.getPropDefault("kg.apc.jmeter.samplers.FileReadChunkSize", 1024 * 4);
public HTTPRawSampler() {
super();
log.debug("File reading chunk size: " + fileSendingChunk);
}
@Override
public SampleResult sample(Entry entry) {
SampleResult res = super.sample(entry);
if (isParseResult()) {
parseResponse(res);
}
return res;
}
protected byte[] readResponse(SocketChannel channel, SampleResult res) throws IOException {
ByteArrayOutputStream response = new ByteArrayOutputStream();
ByteBuffer recvBuf = getRecvBuf();
recvBuf.clear();
boolean firstPack = true;
int cnt;
int responseSize = 0;
if (log.isDebugEnabled()) {
log.debug("Start reading response");
}
try {
while ((cnt = channel.read(recvBuf)) != -1) {
responseSize += cnt;
if (firstPack) {
res.latencyEnd();
firstPack = false;
}
recvBuf.flip();
if (response.size() <= recvDataLimit) {
byte[] bytes = new byte[cnt];
recvBuf.get(bytes);
response.write(bytes);
}
recvBuf.clear();
}
if (response.size() < 1) {
log.warn("Read no bytes from socket, seems it was closed. Let it be so.");
channel.close();
}
} catch (IOException ex) {
channel.close();
throw ex;
}
if (log.isDebugEnabled()) {
log.debug("Done reading response");
}
res.sampleEnd();
if (!isUseKeepAlive()) {
channel.close();
}
res.setBytes(responseSize);
return response.toByteArray();
}
private void parseResponse(SampleResult res) {
Scanner scanner = new Scanner(res.getResponseDataAsString());
scanner.useDelimiter(RNpattern);
if (!scanner.hasNextLine()) {
return;
}
String httpStatus = scanner.nextLine();
int s = httpStatus.indexOf(SPACE);
int e = httpStatus.indexOf(SPACE, s + 1);
if (s < e) {
String rc = httpStatus.substring(s, e).trim();
try {
int rcInt = Integer.parseInt(rc);
if (rcInt < 100 || rcInt > 599) {
return;
}
res.setResponseCode(rc);
res.setResponseMessage(httpStatus.substring(e).trim());
} catch (NumberFormatException ex) {
return;
}
} else {
return;
}
if (!scanner.hasNextLine()) {
return;
}
int n = 1;
StringBuilder headers = new StringBuilder();
String line;
while (scanner.hasNextLine() && !(line = scanner.nextLine()).isEmpty()) {
headers.append(line).append(CRLF);
n++;
}
res.setResponseHeaders(headers.toString());
if (scanner.hasNext()) {
res.setResponseData(scanner.next(anyContent).getBytes());
} else {
res.setResponseData("".getBytes());
}
}
@Override
protected byte[] processIO(SampleResult res) throws Exception {
SocketChannel sock = (SocketChannel) getSocketChannel();
if (!getRequestData().isEmpty()) {
ByteBuffer sendBuf = ByteBuffer.wrap(getRequestData().getBytes()); // cannot cache it because of variable processing
sock.write(sendBuf);
}
sendFile(getFileToSend(), sock);
if (log.isDebugEnabled()) {
log.debug("Sent request");
}
return readResponse(sock, res);
}
protected SocketChannel getSocketChannel() throws IOException {
if (isUseKeepAlive() && savedSock != null) {
return savedSock;
}
int port;
try {
port = Integer.parseInt(getPort());
} catch (NumberFormatException ex) {
log.warn("Wrong port number: " + getPort() + ", defaulting to 80", ex);
port = 80;
}
InetSocketAddress address = new InetSocketAddress(getHostName(), port);
//log.info("Open sock");
savedSock = (SocketChannel) getChannel();
savedSock.connect(address);
return savedSock;
}
public boolean isUseKeepAlive() {
return getPropertyAsBoolean(KEEPALIVE);
}
public void setUseKeepAlive(boolean selected) {
setProperty(KEEPALIVE, selected);
}
@Override
protected AbstractSelectableChannel getChannel() throws IOException {
int t = getTimeoutAsInt();
if (t > 0) {
SocketChannelWithTimeouts res = (SocketChannelWithTimeouts) SocketChannelWithTimeouts.open();
res.setConnectTimeout(t);
res.setReadTimeout(t);
return res;
} else {
return SocketChannel.open();
}
}
public boolean isParseResult() {
return getPropertyAsBoolean(PARSE);
}
public void setParseResult(boolean selected) {
setProperty(PARSE, selected);
}
public String getFileToSend() {
return getPropertyAsString(FILE_NAME);
}
public void setFileToSend(String text) {
setProperty(FILE_NAME, text);
}
@Override
public boolean interrupt() {
if (savedSock != null && savedSock.isOpen()) {
try {
savedSock.close();
} catch (IOException ex) {
log.warn("Exception while interrupting channel: ", ex);
return false;
}
}
return true;
}
private void sendFile(String filename, SocketChannel sock) throws IOException {
if (filename.isEmpty()) {
return;
}
FileInputStream is = new FileInputStream(new File(filename));
FileChannel source = is.getChannel();
ByteBuffer sendBuf = ByteBuffer.allocateDirect(fileSendingChunk);
while (source.read(sendBuf) > 0) {
sendBuf.flip();
if (log.isDebugEnabled()) {
log.debug("Sending " + sendBuf);
}
sock.write(sendBuf);
sendBuf.rewind();
}
source.close();
}
}