blob: 920e7f68752d4126e7529a5c4218d3924ebca036 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.jmeter.protocol.http.control;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URI;
import java.net.URL;
import org.apache.commons.io.IOUtils;
import org.apache.jmeter.junit.JMeterTestCase;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/**
* Class for testing the HTTPMirrorThread, which is handling the
* incoming requests for the HTTPMirrorServer
*/
public class TestHTTPMirrorThread extends JMeterTestCase {
/** The encodings used for http headers and control information */
private static final String ISO_8859_1 = "ISO-8859-1"; // $NON-NLS-1$
private static final String UTF_8 = "UTF-8"; // $NON-NLS-1$
private static final byte[] CRLF = { 0x0d, 0x0a };
private static final int HTTP_SERVER_PORT = 8181;
@RegisterExtension
private static final HttpMirrorServerExtension HTTP_MIRROR_SERVER = new HttpMirrorServerExtension(HTTP_SERVER_PORT);
@Test
public void testGetRequest() throws Exception {
// Connect to the http server, and do a simple http get
Socket clientSocket = new Socket("localhost", HTTP_SERVER_PORT);
OutputStream outputStream = clientSocket.getOutputStream();
InputStream inputStream = clientSocket.getInputStream();
// Write to the socket
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// Headers
bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write("Host: localhost".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(CRLF);
bos.close();
outputStream.write(bos.toByteArray());
// Read the response
ByteArrayOutputStream response = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while(( length = inputStream.read(buffer)) != -1) {
response.write(buffer, 0, length);
}
response.close();
byte[] mirroredResponse = getMirroredResponse(response.toByteArray());
// Check that the request and response matches
checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse);
// Close the connection
outputStream.close();
inputStream.close();
clientSocket.close();
// Connect to the http server, and do a simple http get, with
// a pause in the middle of transmitting the header
clientSocket = new Socket("localhost", HTTP_SERVER_PORT);
outputStream = clientSocket.getOutputStream();
inputStream = clientSocket.getInputStream();
// Write to the socket
bos = new ByteArrayOutputStream();
// Headers
bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1));
bos.write(CRLF);
// Write the start of the headers, and then sleep, so that the mirror
// thread will have to block to wait for more data to appear
bos.close();
byte[] firstChunk = bos.toByteArray();
outputStream.write(firstChunk);
Thread.sleep(200);
// Write the rest of the headers
bos = new ByteArrayOutputStream();
bos.write("Host: localhost".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(CRLF);
bos.close();
byte[] secondChunk = bos.toByteArray();
outputStream.write(secondChunk);
// Read the response
response = new ByteArrayOutputStream();
buffer = new byte[1024];
while((length = inputStream.read(buffer)) != -1) {
response.write(buffer, 0, length);
}
response.close();
mirroredResponse = getMirroredResponse(response.toByteArray());
// The content sent
bos = new ByteArrayOutputStream();
bos.write(firstChunk);
bos.write(secondChunk);
bos.close();
// Check that the request and response matches
checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse);
// Close the connection
outputStream.close();
inputStream.close();
clientSocket.close();
}
@Test
public void testPostRequest() throws Exception {
// Connect to the http server, and do a simple http post
Socket clientSocket = new Socket("localhost", HTTP_SERVER_PORT);
OutputStream outputStream = clientSocket.getOutputStream();
InputStream inputStream = clientSocket.getInputStream();
// Construct body
StringBuilder postBodyBuffer = new StringBuilder();
for(int i = 0; i < 1000; i++) {
postBodyBuffer.append("abc");
}
byte[] postBody = postBodyBuffer.toString().getBytes(ISO_8859_1);
// Write to the socket
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// Headers
bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write("Host: localhost".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(("Content-type: text/plain; charset=" + ISO_8859_1).getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(CRLF);
bos.write(postBody);
bos.close();
// Write the headers and body
outputStream.write(bos.toByteArray());
// Read the response
ByteArrayOutputStream response = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while((length = inputStream.read(buffer)) != -1) {
response.write(buffer, 0, length);
}
response.close();
byte[] mirroredResponse = getMirroredResponse(response.toByteArray());
// Check that the request and response matches
checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse);
// Close the connection
outputStream.close();
inputStream.close();
clientSocket.close();
// Connect to the http server, and do a simple http post, with
// a pause after transmitting the headers
clientSocket = new Socket("localhost", HTTP_SERVER_PORT);
outputStream = clientSocket.getOutputStream();
inputStream = clientSocket.getInputStream();
// Write to the socket
bos = new ByteArrayOutputStream();
// Headers
bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write("Host: localhost".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(("Content-type: text/plain; charset=" + ISO_8859_1).getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(CRLF);
bos.close();
// Write the headers, and then sleep
bos.close();
byte[] firstChunk = bos.toByteArray();
outputStream.write(firstChunk);
Thread.sleep(200);
// Write the body
byte[] secondChunk = postBody;
outputStream.write(secondChunk);
// Read the response
response = new ByteArrayOutputStream();
buffer = new byte[1024];
while((length = inputStream.read(buffer)) != -1) {
response.write(buffer, 0, length);
}
response.close();
mirroredResponse = getMirroredResponse(response.toByteArray());
// The content sent
bos = new ByteArrayOutputStream();
bos.write(firstChunk);
bos.write(secondChunk);
bos.close();
// Check that the request and response matches
checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse);
// Close the connection
outputStream.close();
inputStream.close();
clientSocket.close();
// Connect to the http server, and do a simple http post with utf-8
// encoding of the body, which caused problems when reader/writer
// classes were used in the HttpMirrorThread
clientSocket = new Socket("localhost", HTTP_SERVER_PORT);
outputStream = clientSocket.getOutputStream();
inputStream = clientSocket.getInputStream();
// Construct body
postBodyBuffer = new StringBuilder();
for(int i = 0; i < 1000; i++) {
postBodyBuffer.append("\u0364\u00c5\u2052");
}
postBody = postBodyBuffer.toString().getBytes(UTF_8);
// Write to the socket
bos = new ByteArrayOutputStream();
// Headers
bos.write("GET / HTTP 1.1".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write("Host: localhost".getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(("Content-type: text/plain; charset=" + UTF_8).getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(("Content-length: " + postBody.length).getBytes(ISO_8859_1));
bos.write(CRLF);
bos.write(CRLF);
bos.close();
// Write the headers, and then sleep
bos.close();
firstChunk = bos.toByteArray();
outputStream.write(firstChunk);
Thread.sleep(200);
// Write the body
secondChunk = postBody;
outputStream.write(secondChunk);
// Read the response
response = new ByteArrayOutputStream();
buffer = new byte[1024];
while((length = inputStream.read(buffer)) != -1) {
response.write(buffer, 0, length);
}
response.close();
mirroredResponse = getMirroredResponse(response.toByteArray());
// The content sent
bos = new ByteArrayOutputStream();
bos.write(firstChunk);
bos.write(secondChunk);
bos.close();
// Check that the request and response matches
checkArraysHaveSameContent(bos.toByteArray(), mirroredResponse);
// Close the connection
outputStream.close();
inputStream.close();
clientSocket.close();
}
/*
public void testPostRequestChunked() throws Exception {
// TODO - implement testing of chunked post request
}
*/
@Test
public void testStatus() throws Exception {
URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("X-ResponseStatus", "302 Temporary Redirect");
conn.connect();
assertEquals(302, conn.getResponseCode());
assertEquals("Temporary Redirect", conn.getResponseMessage());
}
@Test
public void testQueryStatus() throws Exception {
URL url = new URI("http",null,"localhost",HTTP_SERVER_PORT,"/path","status=303 See Other",null).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
assertEquals(303, conn.getResponseCode());
assertEquals("See Other", conn.getResponseMessage());
}
@Test
public void testQueryRedirect() throws Exception {
URL url = new URI("http",null,"localhost",HTTP_SERVER_PORT,"/path","redirect=/a/b/c/d?q",null).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.connect();
assertEquals(302, conn.getResponseCode());
assertEquals("Temporary Redirect", conn.getResponseMessage());
assertEquals("/a/b/c/d?q", conn.getHeaderField("Location"));
}
@Test
public void testHeaders() throws Exception {
URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("X-SetHeaders", "Location: /abcd|X-Dummy: none");
conn.connect();
assertEquals(200, conn.getResponseCode());
assertEquals("OK", conn.getResponseMessage());
assertEquals("/abcd", conn.getHeaderField("Location"));
assertEquals("none", conn.getHeaderField("X-Dummy"));
}
@Test
public void testResponseLength() throws Exception {
URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("X-ResponseLength", "10");
conn.connect();
final InputStream inputStream = conn.getInputStream();
assertEquals(10, IOUtils.toByteArray(inputStream).length);
inputStream.close();
}
@Test
public void testCookie() throws Exception {
URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("X-SetCookie", "four=2*2");
conn.connect();
assertEquals("four=2*2", conn.getHeaderField("Set-Cookie"));
}
@Test
public void testSleep() throws Exception {
URL url = new URL("http", "localhost", HTTP_SERVER_PORT, "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("X-Sleep", "200");
// use nanoTime to do timing measurement or calculation
// See https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks
long now = System.nanoTime();
conn.connect();
final InputStream inputStream = conn.getInputStream();
while(inputStream.read() != -1) {} // CHECKSTYLE IGNORE EmptyBlock
inputStream.close();
final long elapsed = (System.nanoTime() - now)/200000L;
assertTrue(elapsed >= 180, "Expected > 180 " + elapsed);
}
/**
* Check that the two byte arrays have identical content
*/
private void checkArraysHaveSameContent(byte[] expected, byte[] actual) throws UnsupportedEncodingException {
if(expected != null && actual != null) {
if(expected.length != actual.length) {
System.out.println(">>>>>>>>>>>>>>>>>>>> (expected) : length " + expected.length);
System.out.println(new String(expected,"UTF-8"));
System.out.println("==================== (actual) : length " + actual.length);
System.out.println(new String(actual,"UTF-8"));
System.out.println("<<<<<<<<<<<<<<<<<<<<");
fail("arrays have different length, expected is " + expected.length + ", actual is " + actual.length);
}
else {
for(int i = 0; i < expected.length; i++) {
if(expected[i] != actual[i]) {
System.out.println(">>>>>>>>>>>>>>>>>>>> (expected) : length " + expected.length);
System.out.println(new String(expected,0,i+1, ISO_8859_1));
System.out.println("==================== (actual) : length " + actual.length);
System.out.println(new String(actual,0,i+1, ISO_8859_1));
System.out.println("<<<<<<<<<<<<<<<<<<<<");
/*
// Useful to when debugging
for(int j = 0; j < expected.length; j++) {
System.out.print(expected[j] + " ");
}
System.out.println();
for(int j = 0; j < actual.length; j++) {
System.out.print(actual[j] + " ");
}
System.out.println();
*/
fail("byte at position " + i + " is different, expected is " + expected[i] + ", actual is " + actual[i]);
}
}
}
}
else {
fail("expected or actual byte arrays were null");
}
}
private byte[] getMirroredResponse(byte[] allResponse) {
// The response includes the headers from the mirror server,
// we want to skip those, to only keep the content mirrored.
// Look for the first CRLFCRLF section
int startOfMirrorResponse = 0;
for(int i = 0; i < allResponse.length; i++) {
// TODO : This is a bit fragile
if(allResponse[i] == 0x0d && allResponse[i+1] == 0x0a && allResponse[i+2] == 0x0d && allResponse[i+3] == 0x0a) {
startOfMirrorResponse = i + 4;
break;
}
}
byte[] mirrorResponse = new byte[allResponse.length - startOfMirrorResponse];
System.arraycopy(allResponse, startOfMirrorResponse, mirrorResponse, 0, mirrorResponse.length);
return mirrorResponse;
}
}