/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* 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 com.fujitsu.dc.test.utils;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import org.apache.commons.codec.CharEncoding;
import org.apache.http.HttpHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.test.DcCoreTestConfig;
import com.fujitsu.dc.test.unit.core.UrlUtils;
import com.sun.jersey.core.header.OutBoundHeaders;
/**
* Httpリクエストを送るテスト用ユーティリティ.
* <h2>使い方の例</h2>
* <pre>
* HttpTest.request("{リクエストファイル}")
* .with("キーワード", "値")
* .with("キーワード", "値")
* .returns()
* .statusCode(201);
* </pre>
* リクエストファイルは以下のようにHTTPの電文をそのまま記述する。
* <pre>
* GET / HTTP/1.1
* Host: ?
* Connection: close
* Content-Length: 0
* </pre>
*/
public class Http {
/**
* ログ用オブジェクト.
*/
private static Logger log = LoggerFactory.getLogger(Http.class);
String method;
String path;
InputStream is;
Map<String, String> params;
byte[] paraBody;
OutBoundHeaders headers = new OutBoundHeaders();
URL url;
Socket socket;
InputStream sIn;
OutputStream sOut;
BufferedReader sReader;
BufferedOutputStream sWriter;
private Http() {
this.params = new HashMap<String, String>();
}
/**
* リクエストを実行しレスポンスを得ます.
* @return TResponse
*/
public TResponse returns() {
BufferedReader br = null;
try {
// ファイルの読み込み
InputStreamReader isr = new InputStreamReader(is, CharEncoding.UTF_8);
br = new BufferedReader(isr);
String firstLine = br.readLine();
firstLine = this.processParams(firstLine);
String[] l1 = firstLine.split(" ");
this.method = l1[0];
this.path = l1[1];
String protoVersion = l1[2];
// ソケットOpen
this.url = new URL(baseUrl + this.path);
try {
this.socket = createSocket(this.url);
} catch (KeyManagementException e) {
throw new RuntimeException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (CertificateException e) {
throw new RuntimeException(e);
}
this.sIn = this.socket.getInputStream();
this.sOut = this.socket.getOutputStream();
this.sReader = new BufferedReader(new InputStreamReader(this.sIn, CharEncoding.UTF_8));
this.sWriter = new BufferedOutputStream(this.sOut);
// 第1行送信
StringBuilder sb = new StringBuilder();
sb.append(this.method);
sb.append(" ");
sb.append(this.url.getPath());
if (this.url.getQuery() != null) {
sb.append("?");
sb.append(this.url.getQuery());
}
sb.append(" ");
sb.append(protoVersion);
this.sWriter.write(sb.toString().getBytes(CharEncoding.UTF_8));
this.sWriter.write(CRLF.getBytes(CharEncoding.UTF_8));
log.debug("Req Start -------");
log.debug(sb.toString());
// Header
String line = null;
String lastLine = null;
int contentLengthLineNum = -1;
List<String> lines = new ArrayList<String>();
int i = 0;
while ((line = br.readLine()) != null) {
if (line.length() == 0) {
break;
}
line = this.processParams(line);
if (line.toLowerCase().startsWith(HttpHeaders.CONTENT_LENGTH.toLowerCase())) {
// Content-Lengthが出てきた行を覚えておく。(複数あったときは後勝ち)
contentLengthLineNum = i;
} else if (line.toLowerCase().startsWith(HttpHeaders.HOST.toLowerCase())) {
line = line.replaceAll("\\?", this.url.getAuthority());
}
lines.add(line + CRLF);
lastLine = line;
i++;
}
// Version情報のヘッダを追加
lines.add("X-Dc-Version: " + DcCoreTestConfig.getCoreVersion() + CRLF);
String body = null;
// 前処理で空行でBreakしたときはBodyがあることの証。
if (line != null) {
log.debug("Req Body-------");
i = 1;
StringWriter sw = new StringWriter();
int chr;
while ((chr = br.read()) != -1) {
sw.write((char) chr);
}
body = sw.toString();
body = this.processParams(body);
// Content-Lengthヘッダの値を設定
if (contentLengthLineNum != -1) {
String contentLength = lines.get(contentLengthLineNum)
.replaceAll("\\?", String.valueOf(body.getBytes().length));
lines.set(contentLengthLineNum, contentLength);
}
} else {
if (this.paraBody != null) {
log.debug("Req Body-------");
// バイナリBodyのサイズを取得
String contentLength = lines.get(contentLengthLineNum)
.replaceAll("\\?", String.valueOf(this.paraBody.length));
lines.set(contentLengthLineNum, contentLength);
} else {
// 最終行が空行でなければ空行を送る.
if (lastLine.length() > 0) {
log.debug("one more CRLF");
this.sWriter.write(CRLF.getBytes(CharEncoding.UTF_8));
}
}
}
// Headerの送信
for (String l : lines) {
this.sWriter.write(l.getBytes(CharEncoding.UTF_8));
if (log.isDebugEnabled()) {
l.replaceAll(CRLF, "");
log.debug(l);
}
}
// 改行コード
this.sWriter.write(CRLF.getBytes(CharEncoding.UTF_8));
// Bodyの送信
if (body != null) {
this.sWriter.write(body.getBytes(CharEncoding.UTF_8));
log.debug(body);
}
// バイナリBodyの送信
if (this.paraBody != null) {
this.sWriter.write(this.paraBody);
this.sWriter.write(CRLF.getBytes(CharEncoding.UTF_8));
}
this.sWriter.flush();
// レスポンスオブジェクト生成
TResponse ret = new TResponse(this.sReader);
return ret;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (br != null) {
br.close();
}
if (this.sWriter != null) {
this.sWriter.close();
}
if (this.sReader != null) {
this.sReader.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 置換パラメタの処理
String processParams(String in) {
String ret = in;
for (String k : this.params.keySet()) {
ret = ret.replaceAll("\\$\\{" + k + "\\}", this.params.get(k));
}
return ret;
}
static final String CRLF = "\r\n";
/**
* システムプロパティから接続先のURLを取得する。 指定がない場合はデフォルトのURLを使用する。
*/
private static String baseUrl = System.getProperty(UrlUtils.PROP_TARGET_URL, UrlUtils.DEFAULT_TARGET_URL);
/**
* @param baseUrltoSet baseUrl
*/
public static void setBaseUrl(String baseUrltoSet) {
baseUrl = baseUrltoSet;
}
/**
* @return BaseURL
*/
public static String getBaseUrl() {
return baseUrl;
}
/**
* リクエストファイルを定義してHttpTestオブジェクトを生成.
* @param resPath リクエストファイルのりソースパス
* @return 応答
*/
public static Http request(String resPath) {
Http ret = new Http();
ret.is = ClassLoader.getSystemResourceAsStream("request/" + resPath);
return ret;
}
/**
* 置換キーワードを追加設定します.
* @param key key
* @param value value
* @return HttpTest
*/
public Http with(final String key, final String value) {
this.params.put(key, value);
return this;
}
/**
* 置換キーワードを追加設定します.
* @param value value
* @return HttpTest
*/
public Http setBodyBinary(final byte[] value) {
this.paraBody = value;
return this;
}
static final int PORT_HTTP = 80;
static final int PORT_HTTPS = 443;
static Socket createSocket(URL url)
throws IOException, KeyStoreException,
NoSuchAlgorithmException, CertificateException, KeyManagementException {
String host = url.getHost();
int port = url.getPort();
String proto = url.getProtocol();
if (port < 0) {
if ("https".equals(proto)) {
port = PORT_HTTPS;
}
if ("http".equals(proto)) {
port = PORT_HTTP;
}
}
log.debug("sock: " + host + ":" + port);
log.debug("proto: " + proto);
// HTTPSのときは、証明書チェックなしのいい加減なSSLSocketを作って返す。
if ("https".equals(proto)) {
KeyManager[] km = null;
TrustManager[] tm = {
new javax.net.ssl.X509TrustManager() {
public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws java.security.cert.CertificateException {
log.debug("Insecure SSLSocket Impl for Testing: NOP at X509TrustManager#checkClientTrusted");
}
public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
throws java.security.cert.CertificateException {
log.debug("Insecure SSLSocket Impl for Testing: NOP at X509TrustManager#checkServerTrusted");
}
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(km, tm, new SecureRandom());
SocketFactory sf = sslContext.getSocketFactory();
return (SSLSocket) sf.createSocket(host, port);
}
// HTTPSでないときは普通のソケット
return new Socket(host, port);
}
}