//
// Copyright 2010 Cinch Logic Pty Ltd.
//
// http://www.chililog.com
//
// Licensed 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.chililog.server.workbench;
import static org.junit.Assert.*;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Random;
import java.util.TimeZone;
import java.util.UUID;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.lang.StringUtils;
import org.chililog.server.common.AppProperties;
import org.chililog.server.common.Log4JLogger;
import org.chililog.server.workbench.WorkbenchService;
import org.jboss.netty.util.internal.jzlib.JZlib;
import org.jboss.netty.util.internal.jzlib.ZStream;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Test the UI web server
*
* @author vibul
*
*/
public class WorkbenchServiceTest {
private static Log4JLogger _logger = Log4JLogger.getLogger(WorkbenchServiceTest.class);
private static String _workbenchStaticFilesDirectory = null;
@BeforeClass
public static void classSetup() throws Exception {
_workbenchStaticFilesDirectory = AppProperties.getInstance().getWorkbenchStaticFilesDirectory() + "/testdata";
File dir = deleteDirectory(_workbenchStaticFilesDirectory);
dir.mkdirs();
WorkbenchService.getInstance().start();
}
@AfterClass
public static void classTeardown() {
WorkbenchService.getInstance().stop();
deleteDirectory(_workbenchStaticFilesDirectory);
}
private static File deleteDirectory(String dirPath) {
File dir = new File(dirPath);
if (dir.exists()) {
for (String f : dir.list()) {
new File(dir, f).delete();
}
dir.delete();
}
return dir;
}
@Test
public void testEchoGET2() throws IOException {
// Create a URL for the desired page
URL url = new URL("http://localhost:8989/echo/test");
// Read all the text returned by the server
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
StringBuffer sb = new StringBuffer();
String str;
while ((str = in.readLine()) != null) {
sb.append(str + "\n");
}
in.close();
_logger.info(sb.toString());
assertTrue(sb.toString().contains("REQUEST_URI: /echo/test"));
}
/**
* We should get back a 404 file not found when we cannot route to a service
*
* @throws IOException
*/
@Test(expected = FileNotFoundException.class)
public void testNotFound() throws IOException {
// Create a URL for the desired page
URL url = new URL("http://localhost:8989/not/found");
url.getContent();
}
/**
* Check if our 304 Not Modified is working when getting a static file.
*
* @throws IOException
* @throws ParseException
*/
@Test()
public void testStaticFileCache() throws IOException, ParseException {
String TEXT = "abc\n123";
String fileName = UUID.randomUUID().toString() + ".txt";
File file = new File(_workbenchStaticFilesDirectory, fileName);
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
out.write(TEXT);
out.close();
// Refresh
file = new File(file.getPath());
// ******************************************************
// Initial request
// ******************************************************
// Create a URL for the desired page
URL url = new URL("http://localhost:8989/static/testdata/" + fileName);
URLConnection conn = url.openConnection();
// Read all the text returned by the server
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer sb = new StringBuffer();
String str;
while ((str = in.readLine()) != null) {
sb.append(str + "\n");
}
in.close();
assertEquals(TEXT, sb.toString().trim());
// Get headers
HashMap<String, String> headers = new HashMap<String, String>();
for (int i = 0;; i++) {
String name = conn.getHeaderFieldKey(i);
String value = conn.getHeaderField(i);
if (name == null && value == null) {
break;
}
if (name == null) {
_logger.debug("*** Intial Call, Response code: %s", value);
} else {
headers.put(name, value);
_logger.debug("%s = %s", name, value);
}
}
assertEquals("7", headers.get("Content-Length"));
assertEquals("text/plain", headers.get("Content-Type"));
// Check last modified should be the same as the file's last modified date
SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
assertEquals(fmt.format(new Date(file.lastModified())), headers.get("Last-Modified"));
// Check Expiry
Date expires = fmt.parse(headers.get("Expires"));
Date serverDate = fmt.parse(headers.get("Date"));
Calendar cal = new GregorianCalendar();
cal.setTime(serverDate);
cal.add(Calendar.SECOND, AppProperties.getInstance().getWorkbenchStaticFilesCacheSeconds());
assertEquals(cal.getTimeInMillis(), expires.getTime());
// ******************************************************
// Cache Validation
// ******************************************************
url = new URL("http://localhost:8989/static/testdata/" + fileName);
conn = url.openConnection();
conn.setIfModifiedSince(fmt.parse(headers.get("Last-Modified")).getTime());
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
sb = new StringBuffer();
while ((str = in.readLine()) != null) {
sb.append(str + "\n");
}
in.close();
// No content should be returned
assertEquals("", sb.toString().trim());
HashMap<String, String> headers2 = new HashMap<String, String>();
String responseCode = "";
for (int i = 0;; i++) {
String name = conn.getHeaderFieldKey(i);
String value = conn.getHeaderField(i);
if (name == null && value == null) {
break;
}
if (name == null) {
responseCode = value;
_logger.debug("*** Cache Call, Response code: %s", value);
} else {
headers2.put(name, value);
_logger.debug("%s = %s", name, value);
}
}
// Should get back a 304
assertEquals("HTTP/1.1 304 Not Modified", responseCode);
assertTrue(!StringUtils.isBlank(headers2.get("Date")));
// ******************************************************
// Finish
// ******************************************************
// Clean up
file.delete();
}
/**
* Check if our expected file types are compressed
*
* @throws IOException
* @throws ParseException
* @throws DecoderException
*/
@Test()
public void testStaticFileCompression() throws IOException, ParseException, DecoderException {
String[] fileExtensions = new String[] { ".html", ".js", ".css", ".json", ".txt", ".xml", ".nocompression" };
// Get 10K string
String TEXT = new RandomString(1024 * 10).nextString();
byte[] TEXT_ARRAY = TEXT.getBytes("UTF-8");
for (String fileExtension : fileExtensions) {
String fileName = UUID.randomUUID().toString() + fileExtension;
File file = new File(_workbenchStaticFilesDirectory, fileName);
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
out.write(TEXT);
out.close();
// Refresh
file = new File(file.getPath());
// Create a URL for the desired page
URL url = new URL("http://localhost:8989/static/testdata/" + fileName + "?testquerystring=abc");
URLConnection conn = url.openConnection();
conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
// Read all the compressed data
ByteArrayOutputStream os = new ByteArrayOutputStream();
InputStream is = conn.getInputStream();
int b;
while ((b = is.read()) != -1) {
os.write(b);
}
// Get headers
String responseCode = "";
HashMap<String, String> headers = new HashMap<String, String>();
for (int i = 0;; i++) {
String name = conn.getHeaderFieldKey(i);
String value = conn.getHeaderField(i);
if (name == null && value == null) {
break;
}
if (name == null) {
responseCode = value;
_logger.debug("*** Intial Call, Response code: %s", value);
} else {
headers.put(name, value);
_logger.debug("%s = %s", name, value);
}
}
// Should get back a 200
assertEquals("HTTP/1.1 200 OK", responseCode);
assertTrue(!StringUtils.isBlank(headers.get("Date")));
if (fileExtension != ".nocompression") {
// Uncompress and check it out
assertEquals("gzip", headers.get("Content-Encoding"));
byte[] uncompressedContent = uncompress(os.toByteArray());
for (int j = 0; j < TEXT_ARRAY.length; j++) {
assertEquals(TEXT_ARRAY[j], uncompressedContent[j]);
}
}
// Clean up
file.delete();
}
return;
}
/**
* Check if we redirect to index.html successfully
*
* @throws IOException
*/
@Test()
public void testRedirectToIndexHtml() throws IOException {
URL url = new URL("http://localhost:8989/workbench");
URLConnection conn = url.openConnection();
HashMap<String, String> headers = new HashMap<String, String>();
String responseCode = ApiUtils.getResponseHeaders(conn, headers);
assertEquals("HTTP/1.1 200 OK", responseCode);
url = new URL("http://localhost:8989/workbench/");
conn = url.openConnection();
headers = new HashMap<String, String>();
responseCode = ApiUtils.getResponseHeaders(conn, headers);
assertEquals("HTTP/1.1 200 OK", responseCode);
}
/**
* Check for ApiNotFound error. 404 Not Found
*
* @throws IOException
* @throws ParseException
*/
@Test()
public void testApiNotFound() throws IOException, ParseException {
URL url = new URL("http://localhost:8989/api/notfound");
URLConnection conn = url.openConnection();
// String content = ApiUtils.getResponseContent((HttpURLConnection) conn);
HashMap<String, String> headers = new HashMap<String, String>();
String responseCode = ApiUtils.getResponseHeaders(conn, headers);
// _logger.debug(WebServerManagerTest.formatResponseForLogging(responseCode, headers, content));
assertEquals("HTTP/1.1 404 Not Found", responseCode);
assertTrue(!StringUtils.isBlank(headers.get("Date")));
}
/**
* Uncompress. See http://www.jcraft.com/jzlib/. This is the same lib that is used inside netty
*
* @param input
* @return
* @throws DecoderException
*/
public byte[] uncompress(byte[] input) throws DecoderException {
int uncomprLen = 40000;
byte[] uncompr = new byte[uncomprLen];
int err;
ZStream d_stream = new ZStream();
d_stream.next_in = input;
d_stream.next_in_index = 0;
d_stream.next_out = uncompr;
d_stream.next_out_index = 0;
err = d_stream.inflateInit(JZlib.W_GZIP);
checkZipError(d_stream, err, "inflateInit");
while (d_stream.total_out < uncomprLen && d_stream.total_in < input.length) {
d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */
err = d_stream.inflate(JZlib.Z_NO_FLUSH);
if (err == JZlib.Z_STREAM_END)
break;
checkZipError(d_stream, err, "inflate");
}
err = d_stream.inflateEnd();
checkZipError(d_stream, err, "inflateEnd");
return uncompr;
}
/**
* Check for zip error
*
* @param z
* @param err
* @param msg
* @throws DecoderException
*/
void checkZipError(ZStream z, int err, String msg) throws DecoderException {
if (err != JZlib.Z_OK) {
throw new DecoderException(z.msg);
}
}
/**
* Create a random string
*/
public static class RandomString {
private final char[] symbols = new char[36];
private final Random random = new Random();
private final char[] buf;
public RandomString(int length) {
for (int idx = 0; idx < 10; ++idx)
symbols[idx] = (char) ('0' + idx);
for (int idx = 10; idx < 36; ++idx)
symbols[idx] = (char) ('a' + idx - 10);
if (length < 1)
throw new IllegalArgumentException("length < 1: " + length);
buf = new char[length];
}
public String nextString() {
for (int idx = 0; idx < buf.length; ++idx)
buf[idx] = symbols[random.nextInt(symbols.length)];
return new String(buf);
}
}
}