/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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.opengion.fukurou.util;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.nio.file.Files;
import java.nio.file.Paths;
	// import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.NameValuePair;
import org.apache.http.util.EntityUtils;

// import org.opengion.fukurou.system.Closer;
import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.system.OgRuntimeException ;

import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;
import static org.opengion.fukurou.system.HybsConst.CR;

/**
 * HttpConnect は、指定のURL にアクセスして、データを取得します。
 * URL へのアクセスにより、エンジンでは各種処理を実行させることが可能になります。
 * 例えば、帳票デーモンの起動や、長時間かかる処理の実行などです。
 * なお、URLに引数が付く場合は、ダブルコーテーションで括って下さい。
 * URL の指定は、先頭に何もつけませ。指定の順番も関係ありません。
 * - 付き引数は、指定順番は、関係ありません。
 * 先頭が # の引数は、コメントと判断します。
 *
 * <pre>
 * Usage: java org.opengion.fukurou.util.HttpConnect [-post=キー:ファイル名] … url [user:passwd]
 *   args[A] : url                     ＵＲＬを指定します。GETの場合、パラメータは ?KEY=VALです
 *   args[*] : [-param=key:value]      POST/GET時のﾊﾟﾗﾒｰﾀのｷｰと値を:で区切って指定します。(複数回指定可)
 *   args[*] : [-header=key:value]     ﾍｯﾀﾞｰに設定するﾊﾟﾗﾒｰﾀのｷｰと値を:で区切って指定します。(複数回指定可)
 *   args[*] : [-auth=user:pass]       BASIC認証のエリアへのアクセス時のユーザーとパスワードを指定します
 *   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。
 *   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)
 *   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)
 *   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です
 *   args[*] : [-download=ファイル名]  ﾌｧｲﾙ名を指定して、ﾀﾞｳﾝﾛｰﾄﾞします
 *   args[*] : [-upload=ファイル名]    ﾌｧｲﾙ名を指定して、multipart/form-dataでﾌｧｲﾙｱｯﾌﾟﾛｰﾄﾞします
 *   args[*] : [-errEx=true/false]     trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)
 *   args[*] : [#････]                 ｺﾒﾝﾄ引数。(BATﾌｧｲﾙ上に残しておきたいが、使用したくない場合など)
 *   args[*] : [-debug=true/false]     trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)
 * </pre>
 *
 * ※ URLConnect との違い。
 *    -info/-data 等の区別の廃止。(実質、-info がなくなる。)
 *    setDownloadFile(String) 追加(-binaryの代用)
 *    setUploadFile(String) 追加
 *    proxy 設定の変更
 *
 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
 *
 * @version  6.9.0.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public class HttpConnect {
	/** エンコードの初期値  {@value} */
	public static final String DEFAULT_CHARSET = "UTF-8" ;
	/** 言語の初期値  {@value} */
	public static final String DEFAULT_LANG = "ja-JP" ;
	/** User-Agentの初期値  {@value} */
	public static final String DEFAULT_AGENT = "openGion with Apache HttpClient" ;
	/** GETで指定するときのURLの長さ制限  {@value}  (IEの場合は、2,083文字) */
	public static final int MAX_GET_LENGTH = 2000 ;

	private final String urlStr ;
	private final String user ;
	private final String pass ;

	private int			rpsCode		= -1;
	private String		rpsMessage	;
	private String		charset		= DEFAULT_CHARSET ;
	private String		upldFile	;
	private String		dwldFile	;						// ﾊﾞｲﾅﾘﾌｧｲﾙとして受け取る場合のファイル名
	private int			timeout		= -1;
	private boolean		isPost		;
	private boolean		isDebug		;

	private HttpHost	proxy		;

	// 初期ﾍｯﾀﾞｰ情報
	private static final List<Header> INIT_HEADER =
											Arrays.asList(
												new BasicHeader( "Accept-Charset"	, DEFAULT_CHARSET ) ,
												new BasicHeader( "Accept-Language"	, DEFAULT_LANG  ) ,
												new BasicHeader( "User-Agent"		, DEFAULT_AGENT )
											);

	private final List<NameValuePair>	reqParamList = new ArrayList<NameValuePair>();	// ﾘｸｴｽﾄﾊﾟﾗﾒｰﾀｰ(主にPOST時)
	private final List<Header>			headers		 = new ArrayList<>( INIT_HEADER );	// ﾍｯﾀﾞｰﾊﾟﾗﾒｰﾀｰ

	// GET でのﾊﾟﾗﾒｰﾀのマージ。きちんとした方法がわかるまでの暫定処置
	private final StringBuilder			reqParamBuf  = new StringBuilder( BUFFER_MIDDLE );

	/**
	 * 接続先URLと、認証用ユーザー:パスワードを指定する、コンストラクター
	 *
	 * 認証が必要ない場合は、userPass は、null でかまいません。
	 * 接続先URLは、HttpConnect で、urlEncode しますので、そのままの文字列でかまいません。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	url			接続するアドレスを指定します。(http://server:port/dir/file.html)
	 * @param	userPass	ユーザー：パスワード(認証接続が必要な場合)
	 */
	public HttpConnect( final String url, final String userPass ) {
		urlStr = StringUtil.urlEncode2( url );

		if( StringUtil.isNull( userPass ) ) {
			user = null;
			pass = null;
		}
		else {
			final String[] prm = StringUtil.csv2Array( userPass , ':' , 2 );
			user = prm[0];
			pass = prm[1];
		}
	}

	/**
	 * URL接続先のデータを取得します。
	 *
	 * この処理の前に、必要な情報を設定して置いてください。
	 * また、code や message は、このメソッドを実行しないと取得できませんのでご注意ください。
	 * 
	 * 取得したデータは、指定のURL へのアクセスのみです。
	 * 通常のWebブラウザは、イメージや、JavaScriptファイル、CSSファイルなど、
	 * 各種ファイル毎にHTTP接続を行い、取得して、レンダリングします。
	 * このメソッドでの処理では、それらのファイル内に指定されているURLの
	 * 再帰的な取得は行いません。
	 * よって、フレーム処理なども行いません。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @return	接続結果
	 * @og.rtnNotNull
	 * @throws  IOException 入出力エラーが発生したとき
	 * @throws  MalformedURLException URLの形式が間違っている場合
	 */
	public String readData() throws IOException , MalformedURLException {
		HttpUriRequest method ;
		if( isPost ) {
			if( isDebug ) { System.out.println( "POST URL=" + urlStr ); }
			method = new HttpPost( urlStr );

			if( !reqParamList.isEmpty() ) {
				((HttpPost)method).setEntity( new UrlEncodedFormEntity( reqParamList , DEFAULT_CHARSET ) );
				if( isDebug ) { reqParamList.forEach( v -> System.out.println( "PARAM KEY=" + v.getName() + " , VAL=" + v.getValue() ) ); }
			}

			if( !StringUtil.isNull( upldFile ) ) {
				final File file = new File( upldFile );
				if( isDebug ) { System.out.println( "  MULTI FILE=" + file ); }
				final HttpEntity entity = MultipartEntityBuilder.create()
										.setMode( HttpMultipartMode.BROWSER_COMPATIBLE )
										.setCharset( StandardCharsets.UTF_8 )	// ﾌｧｲﾙ名の文字化け対策
										.addBinaryBody( "upload" ,
														file ,
														ContentType.DEFAULT_BINARY ,
														file.getName() )
										.build();
				((HttpPost)method).setEntity( entity );
			}
		}
		else {
			// GET でのﾊﾟﾗﾒｰﾀのマージ。きちんとした方法がわかるまでの暫定処置
			final String getStr = reqParamBuf.length() == 0
									? urlStr
									: reqParamBuf.toString() ;

			if( isDebug ) { System.out.println( "GET URL=" + getStr ); }

			method = new HttpGet( getStr );
		}

		String body = null;
		try( CloseableHttpClient client = getClient() ;
			 CloseableHttpResponse response = client.execute(method) ) {

			final StatusLine status = response.getStatusLine();
			final HttpEntity entity = response.getEntity();

			rpsCode    = status.getStatusCode();
			rpsMessage = ( code2Message( rpsCode ) + CR + status.getReasonPhrase() ).trim();

			// バイナリファイルとして受け取る場合。正常時のみ処理します。
			if( !StringUtil.isNull( dwldFile ) && rpsCode >= 200 && rpsCode < 300 ) {
				Files.write( Paths.get( dwldFile ) , EntityUtils.toByteArray( entity ) );
				body = dwldFile;
			}
			else {
				if( entity == null ) {
					body = rpsMessage;		// HttpEntity が受け取れなかった場合は、メッセージを表示します。
				}
				else {
					body = EntityUtils.toString( entity, charset );
				}
			}
		}

		return body;
	}

	/**
	 * 接続先の HttpClient オブジェクトを作成します。
	 *
	 * 接続に必要な情報を、設定します。
	 * CloseableHttpClient は、AutoCloseable を継承しています。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @return  HttpConnectionオブジェクト
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	private CloseableHttpClient getClient() throws MalformedURLException {

		final HttpClientBuilder clBuild = HttpClientBuilder.create();

		// request configuration
		final RequestConfig.Builder reqConfig = RequestConfig.custom()
			.setCookieSpec( CookieSpecs.STANDARD );					// 最新のRFC準拠ヘッダーを理解するのが困難なので。

		if( timeout >= 0 ) {
			reqConfig.setConnectTimeout( timeout * 1000 )			// timeoutの単位は(秒)、設定値は、ミリ秒
					 .setSocketTimeout(  timeout * 1000 );
		}

		clBuild.setDefaultRequestConfig( reqConfig.build() );

		// headers (初期設定も入っているので、通常は、empty にはならない。)
		if( !headers.isEmpty() ) {
			clBuild.setDefaultHeaders( headers );
		}

		// Auth
		if( !StringUtil.isNull( user ) ) {
			final URL url = new URL( urlStr );
			final AuthScope   scope = new AuthScope( url.getHost(), url.getPort() );
			final Credentials cred  = new UsernamePasswordCredentials( user ,pass );

			final CredentialsProvider credProvider = new BasicCredentialsProvider();
			credProvider.setCredentials( scope,cred );

			clBuild.setDefaultCredentialsProvider( credProvider );
		}

		// Proxy
		if( proxy != null ) {
			clBuild.setProxy( proxy );
		}

	//	// (デフォルトのHttpClientは、最新のRFC準拠ヘッダーを理解するのが困難です。)
	//  // RequestConfig に、CookieSpecs.STANDARD を設定しているが、効果なければ、使わなくしてしまう。
	//	clBuild.disableCookieManagement();

		return clBuild.build();		// HttpClient httpClient  = HttpClientBuilder.create().*****.build();
	}

	/**
	 * 接続先に使用する引数(ﾊﾟﾗﾒｰﾀ)を追加します。
	 *
	 * これは、POSTでも、GETでも使用できます。
	 * POSTの場合は、NameValuePair として、HttpPost に、Entity としてセットするデータを設定します。
	 * GET の場合は、既存の接続先URLに、&amp;ｷｰ=値････ で、追記します。
	 * すでに、ﾊﾟﾗﾒｰﾀが指定済みの場合は、&amp; で、そうでなければ、? で連結します。
	 * ここで指定するﾊﾟﾗﾒｰﾀは、内部で、urlEncode しますので、そのままの文字列でかまいません。
	 *
	 * ﾃﾞﾌｫﾙﾄは、GETですが、Internet Explorer では URL に最大 2,083 文字しか指定できないため、
	 * それ以上の場合は、POST に自動で切り替えます。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	key	パラメータキー(nullの場合は、登録しません)
	 * @param	val	パラメータ値
	 */
	public void addRequestProperty( final String key, final String val ) {
		if( !StringUtil.isNull( key ) ) {
			reqParamList.add( new BasicNameValuePair( key,val ) );				// POST のときのﾊﾟﾗﾒｰﾀ。(GETでも使えるはず？)

			if( !isPost ) {														// 明らかに、GET でない場合は、この処理を行わない。
				if( reqParamBuf.length() == 0 ) {								// 初めての場合
					reqParamBuf.append( urlStr )
								.append( urlStr.indexOf( '?' ) > 0 ? '&' : '?' )
								.append( StringUtil.urlEncode2( key ) )
								.append( '=' )
								.append( StringUtil.urlEncode2( val ) );		// null のときは、長さゼロ文字列になる。
				}
				else if( reqParamBuf.length() > MAX_GET_LENGTH ) {
					isPost = true;												// GETで送れるURLの長さ制限を超えた場合は、POSTにする。
				}
				else {
					reqParamBuf.append( '&' )
								.append( StringUtil.urlEncode2( key ) )
								.append( '=' )
								.append( StringUtil.urlEncode2( val ) );		// null のときは、長さゼロ文字列になる。
				}
			}
		}
	}

	/**
	 * setRequestPropertyでセットするデータを設定します。
	 *
	 * keys,vals各々、カンマ区切りで分解します。
	 *
	 * @og.rev 5.10.16.0 (2019/10/04) 追加
	 *
	 * @param	keys	パラメータキー（カンマ区切り）
	 * @param	vals	パラメータ（カンマ区切り）
	 */
	public void setRequestProperty( final String keys, final String vals ) {
		if( keys != null && keys.length() > 0 && vals != null && vals.length() > 0 ){
			final String[]	propKeys = StringUtil.csv2Array( keys );
			final String[]	propVals = StringUtil.csv2Array( vals );

			if( propKeys.length == propVals.length && propKeys.length > 0 ) {
				for( int i=0; i<propKeys.length; i++ ) {
					addRequestProperty( propKeys[i], propVals[i] );
				}
			}
			else {
				final String errMsg = "パラメータのキーと、値の数が一致しません。"	+ CR
							+ " key=[" + keys + "]"									+ CR
							+ " val=[" + vals + "]" ;
				throw new IllegalArgumentException( errMsg );
			}
		}
	}

	/**
	 * 指定のURLに対して、コネクトするのに使用するプロキシ設定を行います。
	 * このときに、ヘッダー情報を内部変数に設定しておきます。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	host	接続するプロキシのホスト名(nullの場合は、登録しません)
	 * @param	port	接続するプロキシのポート番号
	 */
	public void setProxy( final String host,final int port ) {
		if( !StringUtil.isNull( host ) ) {
			proxy = new HttpHost( host , port );
		}
	}

	/**
	 * Header として、HttpClient にセットするデータを設定します。
	 *
	 * 例えばJSON形式でPOSTする場合は通常"Content-Type", "application/json"を指定します。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	key	パラメータキー(nullの場合は、登録しません)
	 * @param	val	パラメータ値(nullの場合は、登録しません)
	 */
	public void addHeaderProperty( final String key, final String val ) {
		if( !StringUtil.isNull( key ) && !StringUtil.isNull( val ) ) {
			headers.add( new BasicHeader( key,val ) );
		}
	}

	/**
	 * URL接続先のバイナリファイルをダウンロード取得します。
	 *
	 * 取得したファイルは、dwldFile にバイナリのまま書き込まれます。
	 * よって、エンコードの指定は不要です。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	dwldFile ﾀﾞｳﾝﾛｰﾄﾞするファイル名。
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public void setDownloadFile( final String dwldFile ) throws IOException {
		this.dwldFile = dwldFile;
	}

	/**
	 * URL接続先のバイナリファイルをアップロードします。
	 *
	 * 取得したファイルは、upldFile にバイナリのまま書き込まれます。
	 * よって、エンコードの指定は不要です。
	 * アップロード は、multipart/form-data で送信するため、isPost = true を
	 * 内部的に設定しておきます。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	upldFile ｱｯﾌﾟﾛｰﾄﾞするファイル名。
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public void setUploadFile( final String upldFile ) throws IOException {
		this.upldFile = upldFile;
		isPost = true;
	}

	/**
	 * エンコード情報を設定します。
	 *
	 * 初期値は、UTF-8 です。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param  chset エンコード情報(nullの場合は、初期値:UTF-8 になります)
	 */
	public void setCharset( final String chset ) {
		if( !StringUtil.isNull( chset ) ) {
			charset = chset;
		}
	}

	/**
	 * 接続タイムアウト時間を(秒)で指定します
	 *
	 * 実際には、org.apache.http.client.config.RequestConfig に対して、
	 *       .setConnectTimeout( timeout * 1000 )
	 *       .setSocketTimeout(  timeout * 1000 )
	 * のように、 1000倍して設定しています。
	 * 0 は、無限のタイムアウト、マイナスは、設定しません。(つまりJavaの初期値のまま)
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	tout	タイムアウト時間(秒) (ゼロは、無制限)
	 */
	public void setTimeout( final int tout ) {
		timeout = tout;
	}

	/**
	 * trueの場合、POSTを使用して接続します(初期値:false)。
	 *
	 * 通常はGETですが、外部から強制的に、POSTで送信したい場合に、
	 * 設定します。
	 * ただし、バイナリファイルをアップロードか、URLの長さ制限が、
	 * {@value #MAX_GET_LENGTH} を超えた場合は、内部で自動的に、post にします。
	 *
	 * @og.rev 6.9.0.1 (2018/02/05) 新規作成
	 *
	 * @param	usePost	true:POST使用/false:通常(GET)
	 */
	public void usePost( final boolean usePost ) {
		isPost = usePost;
	}

	/**
	 * trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	isDebug	true:デバッグ用のメッセージを出力/false:通常
	 */
	public void setDebug( final boolean isDebug ) {
		this.isDebug = isDebug;
	}

	/**
	 * 実行結果のステータスコード 情報を取得します。
	 *
	 * 結果は、#readData() メソッドをコールしないと取れません。
	 * 未実行の場合は、-1 がセットされています。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @return	結果コード 情報
	 * @see		#readData()
	 */
	public int getCode() { return rpsCode; }

	/**
	 * メッセージ 情報を取得します。
	 *
	 * 結果は、#readData() メソッドをコールしないと取れません。
	 * 未実行の場合は、null がセットされています。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @return	メッセージ 情報
	 */
	public String getMessage() { return rpsMessage; }

	/**
	 * HttpURLConnection のレスポンスコードに対応するメッセージ文字列を返します。
	 *
	 * HttpURLConnection の getResponseCode() メソッドにより取得された、HTTPレスポンスコード
	 * に対応する文字列を返します。この文字列は、HttpURLConnection で定義された
	 * static 定数のコメントを、定義しています。
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	code	HTTPレスポンスコード
	 *
	 * @return	レスポンスコードに対応する文字列
	 * @og.rtnNotNull
	 * @see HttpURLConnection#HTTP_ACCEPTED
	 */
	public static String code2Message( final int code ) {
		final String msg ;
		switch( code ) {
			case 100					 					: msg = "100: 要求は続行可能です。"						;	break;
			case 101										: msg = "101: プロトコルを切り替えます。"				;	break;
			case HttpURLConnection.HTTP_OK 					: msg = "200: OK です。"								;	break;
			case HttpURLConnection.HTTP_CREATED 			: msg = "201: 作成されました。"							;	break;
			case HttpURLConnection.HTTP_ACCEPTED			: msg = "202: 受け入れられました。"						;	break;
			case HttpURLConnection.HTTP_NOT_AUTHORITATIVE 	: msg = "203: 信頼できない情報です。"					;	break;
			case HttpURLConnection.HTTP_NO_CONTENT 			: msg = "204: コンテンツがありません。"					;	break;
			case HttpURLConnection.HTTP_RESET 				: msg = "205: コンテンツをリセットします。"				;	break;
			case HttpURLConnection.HTTP_PARTIAL 			: msg = "206: 部分的なコンテンツです。"					;	break;
			case HttpURLConnection.HTTP_MULT_CHOICE 		: msg = "300: 複数の選択肢があります。"					;	break;
			case HttpURLConnection.HTTP_MOVED_PERM 			: msg = "301: 永続的に移動されました。"					;	break;
			case HttpURLConnection.HTTP_MOVED_TEMP 			: msg = "302: 一時的なリダイレクト。"					;	break;
			case HttpURLConnection.HTTP_SEE_OTHER 			: msg = "303: ほかを参照してください。"					;	break;
			case HttpURLConnection.HTTP_NOT_MODIFIED 		: msg = "304: 変更されていません。"						;	break;
			case HttpURLConnection.HTTP_USE_PROXY 			: msg = "305: プロキシを使用します。"					;	break;
			case 306					 					: msg = "306: 仕様の拡張案です。"						;	break;
			case 307										: msg = "307: 一時的なリダイレクトです。"				;	break;
			case HttpURLConnection.HTTP_BAD_REQUEST 		: msg = "400: 不当な要求です。"							;	break;
			case HttpURLConnection.HTTP_UNAUTHORIZED 		: msg = "401: 認証されませんでした。"					;	break;
			case HttpURLConnection.HTTP_PAYMENT_REQUIRED 	: msg = "402: 支払いが必要です。"						;	break;
			case HttpURLConnection.HTTP_FORBIDDEN 			: msg = "403: 禁止されています。"						;	break;
			case HttpURLConnection.HTTP_NOT_FOUND 			: msg = "404: 見つかりませんでした。"					;	break;
			case HttpURLConnection.HTTP_BAD_METHOD 			: msg = "405: メソッドは許可されません。"				;	break;
			case HttpURLConnection.HTTP_NOT_ACCEPTABLE 		: msg = "406: 受け入れられません。"						;	break;
			case HttpURLConnection.HTTP_PROXY_AUTH 			: msg = "407: プロキシの認証が必要です。"				;	break;
			case HttpURLConnection.HTTP_CLIENT_TIMEOUT 		: msg = "408: 要求がタイムアウトしました。"				;	break;
			case HttpURLConnection.HTTP_CONFLICT 			: msg = "409: 重複しています。"							;	break;
			case HttpURLConnection.HTTP_GONE 				: msg = "410: 存在しません。"							;	break;
			case HttpURLConnection.HTTP_LENGTH_REQUIRED 	: msg = "411: 長さが必要です。"							;	break;
			case HttpURLConnection.HTTP_PRECON_FAILED 		: msg = "412: 前提条件が満たされていません。"			;	break;
			case HttpURLConnection.HTTP_ENTITY_TOO_LARGE 	: msg = "413: 要求のエンティティが大きすぎます。"		;	break;
			case HttpURLConnection.HTTP_REQ_TOO_LONG 		: msg = "414: 要求のURIが大きすぎます。"				;	break;
			case HttpURLConnection.HTTP_UNSUPPORTED_TYPE 	: msg = "415: サポートされないメディアタイプです。"		;	break;
			case 416					 					: msg = "416: 要求された範囲は不十分です。"				;	break;
			case 417										: msg = "417: 要求どおりの処理が不可能です。"			;	break;
			case HttpURLConnection.HTTP_INTERNAL_ERROR 		: msg = "500: 内部サーバエラーです。"					;	break;
			case HttpURLConnection.HTTP_NOT_IMPLEMENTED 	: msg = "501: 実装されていません。"						;	break;
			case HttpURLConnection.HTTP_BAD_GATEWAY 		: msg = "502: 誤ったゲートウェイです。"					;	break;
			case HttpURLConnection.HTTP_UNAVAILABLE 		: msg = "503: サービスが利用できません。"				;	break;
			case HttpURLConnection.HTTP_GATEWAY_TIMEOUT 	: msg = "504: ゲートウェイがタイムアウトしました。"		;	break;
			case HttpURLConnection.HTTP_VERSION 			: msg = "505: サポートされていないHTTPバージョンです。"	;	break;
			default 										: msg = code + ": 未定義" 								;	break;
		}
		return msg ;
	}

	/**
	 * サンプル実行用のメインメソッド
	 *
	 * <pre>
	 * Usage: java org.opengion.fukurou.util.HttpConnect [-post=キー:ファイル名] … url [user:passwd]
	 *   args[A] : url                     ＵＲＬを指定します。GETの場合、パラメータは ?KEY=VALです
	 *   args[*] : [-param=key:value]      POST/GET時のﾊﾟﾗﾒｰﾀのｷｰと値を:で区切って指定します。(複数回指定可)
	 *   args[*] : [-header=key:value]     ﾍｯﾀﾞｰに設定するﾊﾟﾗﾒｰﾀのｷｰと値を:で区切って指定します。(複数回指定可)
	 *   args[*] : [-auth=user:pass]       BASIC認証のエリアへのアクセス時のユーザーとパスワードを指定します
	 *   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。
	 *   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)
	 *   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)
	 *   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です
	 *   args[*] : [-download=ファイル名]  ﾌｧｲﾙ名を指定して、ﾀﾞｳﾝﾛｰﾄﾞします
	 *   args[*] : [-upload=ファイル名]    ﾌｧｲﾙ名を指定して、multipart/form-dataでﾌｧｲﾙｱｯﾌﾟﾛｰﾄﾞします
	 *   args[*] : [-errEx=true/false]     trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)
	 *   args[*] : [#････]                 ｺﾒﾝﾄ引数。(BATﾌｧｲﾙ上に残しておきたいが、使用したくない場合など)
	 *   args[*] : [-debug=true/false]     trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)
	 * </pre>
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 *
	 * @param	args	コマンド引数配列
	 * @throws IOException 入出力エラーが発生したとき
	 */
	public static void main( final String[] args ) throws IOException {
		if( args.length < 2 ) {
			LogWriter.log( "Usage: java org.opengion.fukurou.util.HttpConnect [-data/-binary] … url"													);
			LogWriter.log( "   args[A] : url                     ＵＲＬを指定します。GETの場合、パラメータは ?KEY=VALです"								);
			LogWriter.log( "   args[*] : [-param=key:value]      POST/GET時のﾊﾟﾗﾒｰﾀのｷｰと値を:で区切って指定します。(複数回指定可)"						);
			LogWriter.log( "   args[*] : [-header=key:value]     ﾍｯﾀﾞｰに設定するﾊﾟﾗﾒｰﾀのｷｰと値を:で区切って指定します。(複数回指定可)"					);
			LogWriter.log( "   args[*] : [-auth=user:pass]       BASIC認証のエリアへのアクセス時のユーザーとパスワードを指定します"						);
			LogWriter.log( "   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。"									);
			LogWriter.log( "   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)"									);
			LogWriter.log( "   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)"												);
			LogWriter.log( "   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です"										);
			LogWriter.log( "   args[*] : [-download=ファイル名]  ﾌｧｲﾙ名を指定して、ﾀﾞｳﾝﾛｰﾄﾞします"														);
			LogWriter.log( "   args[*] : [-upload=ファイル名]    ﾌｧｲﾙ名を指定して、multipart/form-dataでﾌｧｲﾙｱｯﾌﾟﾛｰﾄﾞします"								);
			LogWriter.log( "   args[*] : [-errEx=true/false]     trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)"	);
			LogWriter.log( "   args[*] : [#････]                 ｺﾒﾝﾄ引数。(BATﾌｧｲﾙ上に残しておきたいが、使用したくない場合など)"						);
			LogWriter.log( "   args[*] : [-debug=true/false]     trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)"					);
			return;
		}

		String	urlStr		= null ;
		final List<String> paramKey  = new ArrayList<>();	// ﾊﾟﾗﾒｰﾀｰｷｰ
		final List<String> paramVal  = new ArrayList<>();	// ﾊﾟﾗﾒｰﾀｰ値
		final List<String> headerKey = new ArrayList<>();	// ﾊﾟﾗﾒｰﾀｰｷｰ
		final List<String> headerVal = new ArrayList<>();	// ﾊﾟﾗﾒｰﾀｰ値

		String	userPass	= null ;
		String	proxy		= null ;
		int		timeout		= -1 ;
		String	encode		= DEFAULT_CHARSET ;
		String	outFile		= null ;
		String	dwldFile	= null ;
		String	upldFile	= null ;
		boolean	isEx		= false ;
		boolean	isDebug		= false ;
		boolean	nonWriter	= false ;

//		int		code		= -1;

		for( final String arg : args ) {
			if( arg.startsWith( "-param=" ) ) {
				final String[] prm = StringUtil.csv2Array( arg.substring( "-param=".length() ) , '=' , 2 );
				paramKey.add( prm[0] );
				paramVal.add( prm[1] );
			}
			else if( arg.startsWith( "-header=" ) ) {
				final String[] prm = StringUtil.csv2Array( arg.substring( "-header=".length() ) , '=' , 2 );
				headerKey.add( prm[0] );
				headerVal.add( prm[1] );
			}
			else if( arg.startsWith( "-auth=" ) ) {
				userPass = arg.substring( "-auth=".length() );
				if( StringUtil.isNull( userPass ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-proxy=" ) ) {
				proxy = arg.substring( "-proxy=".length() );
				if( StringUtil.isNull( proxy ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-timeout=" ) ) {
				timeout = Integer.parseInt( arg.substring( "-timeout=".length() ) );
			}
			else if( arg.startsWith( "-encode=" ) ) {
				encode = arg.substring( "-encode=".length() );
				if( StringUtil.isNull( encode ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-out=" ) ) {
				outFile = arg.substring( "-out=".length() );
				if( StringUtil.isNull( outFile ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
				else {
					if( "null".equalsIgnoreCase( outFile ) || "none".equalsIgnoreCase( outFile ) ) {
						outFile   = null;
						nonWriter = true;
					}
				}
			}
			else if( arg.startsWith( "-download=" ) ) {
				dwldFile = arg.substring( "-download=".length() );
				if( StringUtil.isNull( dwldFile ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-upload=" ) ) {
				upldFile = arg.substring( "-upload=".length() );
				if( StringUtil.isNull( upldFile ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-errEx=" ) ) {
				isEx = "true".equalsIgnoreCase( arg.substring( "-errEx=".length() ) );
			}
			else if( arg.startsWith( "-debug=" ) ) {
				isDebug = "true".equalsIgnoreCase( arg.substring( "-debug=".length() ) );
			}
			else if( StringUtil.startsChar( arg , '-' ) ) {			// 引数が未定義(処理は継続させます。)
				System.err.println( "Error Argment:" + arg );
			}
			else if( StringUtil.startsChar( arg , '#' ) ) {			// 引数がコメント
				continue;
			}
			else {
				urlStr = arg;
			}
		}

		try {									// try catch を入れます。
			final HttpConnect conn = new HttpConnect( urlStr,userPass );
			conn.setDebug( isDebug );			// 最初に入れておけば、それ以降、有効になります。

			for( int i=0; i<paramKey.size(); i++ ) {
				conn.addRequestProperty( paramKey.get(i) , paramVal.get(i) );
			}

			for( int i=0; i<headerKey.size(); i++ ) {
				conn.addHeaderProperty( headerKey.get(i) , headerVal.get(i) );
			}

			// 6.8.1.3 (2017/08/04) proxy の設定
			if( !StringUtil.isNull( proxy ) ) {
				final String[] prm = StringUtil.csv2Array( proxy , ':' , 2 );
				final String host = prm[0];
				final int    port = Integer.parseInt( prm[1] );
				conn.setProxy( host , port );
			}

			conn.setCharset(		encode );		// encode 指定
			conn.setTimeout(		timeout );		// timeout属性追加
			conn.setUploadFile(		upldFile );
			conn.setDownloadFile(	dwldFile );

			final String outData = conn.readData();

			try( final PrintWriter writer = StringUtil.isNull( outFile )
												? FileUtil.getLogWriter( "System.out" )
												: FileUtil.getPrintWriter( new File( outFile ),encode ) ) {
				if( !nonWriter ) {
					writer.println( outData );
				}
				final int code = conn.getCode();

				// isEx=trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます
				if( code >= 400 ) {
					final String errMsg = conn.getMessage();
					writer.println( errMsg );
					if( isEx ) {
						throw new OgRuntimeException( errMsg );
					}
					else {
						System.exit( code );
					}
				}
			}
		}
		catch( final Throwable th ) {
			throw new OgRuntimeException( th );
		}
	}
}
