/*
 * 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 java.util.concurrent.TimeUnit;											// 8.0.0.0 (2021/07/31) Add

// 8.0.0.0 (2021/07/31) httpclient-4.5.5.jar → httpclient5-5.1.jar
// org.apache.http → org.apache.hc.core5.http or org.apache.hc.client5.http
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;									// 8.0.0.0 (2021/07/31) Add
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.BasicNameValuePair;
// import org.apache.hc.core5.http.message.StatusLine;							// 8.0.0.0 (2021/07/31) Delete
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.config.RequestConfig;
// import org.apache.http.client.config.CookieSpecs;							// 8.0.0.0 (2021/07/31) Delete
// import org.apache.hc.client5.http.auth.CredentialsProvider;					// 8.0.0.0 (2021/07/31) Delete
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
// import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;				// 8.0.0.0 (2021/07/31) Delete
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
//import org.apache.http.client.methods.HttpUriRequest;							// 8.0.0.0 (2021/07/31)
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
// import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;				// 8.0.0.0 (2021/08/31)
// import org.apache.hc.client5.http.cookie.Cookie;								// 8.0.0.0 (2021/08/31)
// import org.apache.hc.client5.http.impl.classic.HttpClients;
// import org.apache.hc.client5.http.cookie.CookieStore;
// import org.apache.http.impl.client.DefaultHttpClient;;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
// import org.apache.hc.client5.http.cookie.Cookie;
import org.apache.hc.client5.http.protocol.HttpClientContext;
// import java.io.UnsupportedEncodingException;									// 8.0.0.0 (2021/07/31) Delete

import java.net.URI;
import java.net.URISyntaxException;
// import java.util.Calendar;														// 8.0.0.0 (2021/08/31)
import java.util.Map;
// import java.util.Map.Entry;
import java.util.HashMap;

// import org.apache.http.impl.client.DefaultRedirectStrategy;
// import org.apache.http.HttpRequest;
// import org.apache.http.HttpResponse;
// import org.apache.http.protocol.HttpContext;
// import org.apache.http.ProtocolException;
// import org.apache.http.impl.client.LaxRedirectStrategy;						// 8.0.0.0 (2021/07/31) Delete

// 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[*] : [-postRedirect=true]    POST時に強制的にﾘﾀﾞｲﾚｸﾄを行います(GET時は自動でﾘﾀﾞｲﾚｸﾄします)(初期値:false) 7.2.5.0 (2020/06/01)
 *   args[*] : [-usePost=true]         POSTを強制的に使用する場合にｾｯﾄします(初期値:false)
 *   args[*] : [-errEx=true/false]     trueの場合、ﾚｽﾎﾟﾝｽｺｰﾄﾞが、4XX,5XX の時に RuntimeException を投げます(初期値:false)
 *   args[*] : [-authJson=JSONコード]  JSONコードで認証する場合に使用します。8.0.0.0 (2021/08/31)
 *   args[*] : [-authURL=認証用URL]    JSONコードで認証するURLを指定します。8.0.0.0 (2021/08/31)
 *   args[*] : [-reqJson=JSONコード]   パラメータをJSONで指定する場合に使用します。8.0.0.0 (2021/08/31)
 *   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) 新規作成
 * @og.rev 8.0.0.0 (2021/08/31) httpclient5 対応
 *
 * https://hc.apache.org/httpcomponents-core-5.1.x/current/httpcore5/apidocs/
 * https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/
 *
 * @version  8.0.0.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK11.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 final Map<String,String> jsonMap = new  HashMap<>();

	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		postRedirect ;				// 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)
	private boolean		isDebug		;

	private String		authJson	;				// -authJson		// 8.0.0.0 (2021/08/31)
	private String		authURL		;				// -authURL			// 8.0.0.0 (2021/08/31)
	private String		reqJson		;				// -reqJson			// 8.0.0.0 (2021/08/31)

	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) 新規作成
	 * @og.rev 8.0.0.0 (2021/07/31) httpclient4 → httpclient5 対応
	 *
	 * @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];
		}
	}

//	/**
//	 * Form認証でのログイン
//	 *
//	 * https://hc.apache.org/httpcomponents-client-5.1.x/examples.html
//	 * https://github.com/apache/httpcomponents-client/blob/5.1.x/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientFormLogin.java
//	 *
//	 * @og.rev 8.0.0.0 (2021/07/31) httpclient4 → httpclient5 対応
//	 */
//	private String formLogin() {
//		String body = null;
//		String secCheckURL = null;
//
//		final BasicCookieStore cookieStore = new BasicCookieStore();
//		try (final CloseableHttpClient httpclient = HttpClients.custom()
//				.setDefaultCookieStore(cookieStore)
//				.build()) {
////			final HttpGet httpget = new HttpGet(urlStr);
//			final URI uri = new URI(urlStr);					// j_security_check のアドレスを取るために。
//			final HttpGet httpget = new HttpGet(uri);
//
//			try (final CloseableHttpResponse response1 = httpclient.execute(httpget)) {
//				final HttpEntity entity = response1.getEntity();
//
//				System.out.println("Login form get: " + response1.getCode() + " " + response1.getReasonPhrase());
//				final String loginBody = EntityUtils.toString( entity, charset );
//				// 一旦Form認証画面のHTMLが返ってくるので、その中から j_security_check を探してくる。
//				final int ed = loginBody.indexOf( "j_security_check" );
//				if( ed >= 0 ) {
//					final int st = loginBody.lastIndexOf( '"',ed );		// 逆順に探す。
//					if( st >= 0 ) {
//						secCheckURL = uri.getScheme() + "://" + uri.getHost() + ':' + uri.getPort()
//											+ loginBody.substring( st+1,ed ) + "j_security_check" ;
//						System.out.println(secCheckURL);
//					}
//				}
//
//				EntityUtils.consume(entity); 		// リソースを解放
//
//	//			System.out.println("Initial set of cookies:");
//	//			final List<Cookie> cookies = cookieStore.getCookies();
//	//			if (cookies.isEmpty()) {
//	//				System.out.println("None");
//	//			} else {
//	//				for (int i = 0; i < cookies.size(); i++) {
//	//					System.out.println("- " + cookies.get(i));
//	//				}
//	//			}
//			}
//
//			final ClassicHttpRequest login = ClassicRequestBuilder.post()
//	//				.setUri(new URI("http://localhost:8828/gf/jsp/j_security_check"))
//					.setUri(new URI( secCheckURL ))
//					.addParameter("j_username", user)
//					.addParameter("j_password", pass)
//					.addParameter("j_security_check", "login")
//					.build();
//			try (final CloseableHttpResponse response2 = httpclient.execute(login)) {
//				final HttpEntity entity = response2.getEntity();
//
//				System.out.println("Login form get: " + response2.getCode() + " " + response2.getReasonPhrase());
//				body = EntityUtils.toString( entity, charset );
//
//				EntityUtils.consume(entity); 		// リソースを解放
//
//	//			System.out.println("Post logon cookies:");
//	//			final List<Cookie> cookies = cookieStore.getCookies();
//	//			if (cookies.isEmpty()) {
//	//				System.out.println("None");
//	//			} else {
//	//				for (int i = 0; i < cookies.size(); i++) {
//	//					System.out.println("- " + cookies.get(i));
//	//				}
//	//			}
//			}
//		}
//		catch( IOException ex ) { ex.printStackTrace(); }
//		catch( URISyntaxException ex ) { ex.printStackTrace(); }
//		catch( final ParseException ex ) {ex.printStackTrace();}
//
//		return body ;
//	}

	/**
	 * Form認証でのログイン
	 *
	 * https://hc.apache.org/httpcomponents-client-5.1.x/examples.html
	 * https://github.com/apache/httpcomponents-client/blob/5.1.x/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientFormLogin.java
	 *
	 * @og.rev 8.0.0.0 (2021/07/31) httpclient4 → httpclient5 対応
	 */
	private String formLogin( final CloseableHttpClient client , final URI uri ) {
		String body = null;

		try {
			final URI secCheckURI = uri.resolve( "j_security_check" );
			if( isDebug ) { System.out.println( "security URI=" + secCheckURI ); }

			final ClassicHttpRequest login = ClassicRequestBuilder.post()
	//				.setUri(new URI("http://localhost:8828/gf/jsp/j_security_check"))
					.setUri( secCheckURI )
					.addParameter("j_username", user)
					.addParameter("j_password", pass)
					.addParameter("j_security_check", "login")
					.build();

			try( CloseableHttpResponse response = client.execute(login) ) {
				final HttpEntity entity = response.getEntity();

				rpsCode    = response.getCode();									// 8.0.0.0 (2021/07/31)
				rpsMessage = code2Message( rpsCode ).trim();						// 8.0.0.0 (2021/07/31)

				// Form認証の場合、バイナリ処理は、formLogin 内で行います。
				// バイナリファイルとして受け取る場合。成功(200番台)のみ処理します。
				if( !StringUtil.isNull( dwldFile ) && rpsCode >= 200 && rpsCode < 300 ) {
					Files.write( Paths.get( dwldFile ) , EntityUtils.toByteArray( entity ) );
					body = dwldFile;
				}
				else {
					body = EntityUtils.toString( entity, charset );
				}
				EntityUtils.consume(entity); 		// リソースを解放
			}
		}
		catch( final IOException | ParseException ex ) {
			throw new OgRuntimeException( ex );
		}

		return body ;
	}

	/**
	 * 特別版ログイン
	 *
	 * @og.rev 8.0.0.0 (2021/08/31) httpclient4 → httpclient5 対応
	 */
	private String jsonLogin( final CloseableHttpClient client ) {
		String body = null;

		try {
			final ClassicHttpRequest login = new HttpPost( authURL );

			// 8.0.0.0 (2021/08/31)
			login.setHeader("Content-type", "application/json");
			final HttpEntity stringEntity = new StringEntity(authJson,ContentType.APPLICATION_JSON);
			login.setEntity( stringEntity );

			try( CloseableHttpResponse response = client.execute(login) ) {
				final HttpEntity entity = response.getEntity();

				rpsCode    = response.getCode();									// 8.0.0.0 (2021/07/31)
				rpsMessage = code2Message( rpsCode ).trim();						// 8.0.0.0 (2021/07/31)
				if( isDebug ) { System.out.println( "jsonLogin=" + rpsCode ); }

				body = EntityUtils.toString( entity, charset );

				if( isDebug ) { System.out.println( "Login=" + body ); }

				// ログイン時のリターンJSONから、ﾊﾟﾗﾒｰﾀを取得するMapを作成します。
				if( !jsonMap.isEmpty() ) {
					for( final String key : jsonMap.keySet() ) {
						final int st = body.indexOf( key );
						if( st >= 0 ) {
							final int ed = body.indexOf( ',',st+key.length() );
							final StringBuilder buf = new StringBuilder();
							if( ed > 0 ) {
								for( int i=st+key.length(); i<ed; i++ ) {
									final char ch = body.charAt(i);
									if( ch != '"' && ch != ':' && ch != ' ' ) {
										buf.append( ch );
									}
								}
							}
							jsonMap.put( key,buf.toString() );
							if( isDebug ) { System.out.println( "Map=" +  key + ":" + buf.toString() ); }
						}
					}
				}

				EntityUtils.consume(entity); 		// リソースを解放
			}
		}
		catch( final IOException | ParseException ex ) {
			throw new OgRuntimeException( ex );
		}

		return body ;
	}

	/**
	 * 特別版ログイン
	 *
	 * @og.rev 8.0.0.0 (2021/08/31) httpclient4 → httpclient5 対応
	 */
	private String jsonDataget( final CloseableHttpClient client , final URI uri ) {
		String body = null;

		try {
			final ClassicHttpRequest httpReq ;

			if( reqJson == null ) {
				httpReq = new HttpGet( uri );	// public/html/reportMain.html の時
			}
			else {
				httpReq = new HttpPost( uri );
				httpReq.setHeader("Content-type", "application/json");

				String tempJson = reqJson;
				if( !jsonMap.isEmpty() ) {
					for( final Map.Entry<String,String> entry : jsonMap.entrySet() ) {
						tempJson = tempJson.replace( '$'+entry.getKey()+'$' , entry.getValue() );
					}
				}

				if( isDebug ) { System.out.println( "reqJson=" + tempJson ); }

				final HttpEntity stringEntity = new StringEntity(tempJson,ContentType.APPLICATION_JSON);
				httpReq.setEntity( stringEntity );
			}

			try( CloseableHttpResponse response = client.execute(httpReq) ) {
				final HttpEntity entity = response.getEntity();

				rpsCode    = response.getCode();									// 8.0.0.0 (2021/07/31)
				rpsMessage = code2Message( rpsCode ).trim();						// 8.0.0.0 (2021/07/31)
				if( isDebug ) { System.out.println( "jsonDataget=" + rpsCode ); }

				body = EntityUtils.toString( entity, charset );
				EntityUtils.consume(entity); 		// リソースを解放
			}
		}
		catch( final IOException | ParseException ex ) {
			throw new OgRuntimeException( ex );
		}

		return body ;
	}

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

			if( !reqParamList.isEmpty() ) {
				method.setEntity( new UrlEncodedFormEntity( reqParamList ) );						// 8.0.0.0 (2021/07/31)
				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()
										.setCharset( StandardCharsets.UTF_8 )	// ﾌｧｲﾙ名の文字化け対策
										.addBinaryBody( "upload" ,
														file ,
														ContentType.DEFAULT_BINARY ,
														file.getName() )
										.build();
				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 );
		}

		final HttpClientContext context = HttpClientContext.create();
//		if( ckStore != null ) {					// 8.0.0.0 (2021/07/31) 未使用
//			context.setCookieStore(ckStore);
//		}

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

			rpsCode    = response.getCode();									// 8.0.0.0 (2021/07/31)
			if( isDebug ) { System.out.println( "readData=" + rpsCode ); }
			rpsMessage = code2Message( rpsCode ).trim();						// 8.0.0.0 (2021/07/31)

			final HttpEntity entity = response.getEntity();

			if( entity == null ) {
				body = rpsMessage;			// HttpEntity が受け取れなかった場合は、メッセージを表示します。
			}
			else {
				// body は一度しか処理できない。EntityUtils.toByteArray( entity ) か、EntityUtils.toString( entity, charset );

				final URI uri = method.getUri();

				// バイナリファイルとして受け取る場合。成功(200番台)のみ処理します。
				if( !StringUtil.isNull( dwldFile ) && rpsCode >= 200 && rpsCode < 300 ) {
					final byte[] dwnBody = EntityUtils.toByteArray( entity );
					final String text = new String( dwnBody,charset );

					// 一旦Form認証画面のHTMLが返ってくるので、その中から j_security_check を探してくる。
					if( text.contains( "j_security_check" ) ) {
						body = formLogin( client,uri );		// Form認証時の再接続処理
					}
					else {
						Files.write( Paths.get( dwldFile ) , dwnBody );
						body = dwldFile;
					}
				}
				else {
					// form認証チェックが必要なので、バイナリでも文字列で受け取る。
					body = EntityUtils.toString( entity, charset );
					// 一旦Form認証画面のHTMLが返ってくるので、その中から j_security_check を探してくる。
					if( body.contains( "j_security_check" ) ) {
						body = formLogin( client,uri );		// Form認証時の再接続処理
					}
					else if( authJson != null ) {				// 8.0.0.0 (2021/08/31)
						body = jsonLogin( client );			// 認証時の再接続処理
						body = jsonDataget( client,uri );	// 認証後の再接続処理
					}
				}
			}
			EntityUtils.consume(entity); 			// リソースを解放
		}
		// 8.0.0.0 (2021/07/31) Add
		catch( final ParseException | URISyntaxException ex ) {
			throw new OgRuntimeException( ex );
		}

		return body;
	}

	/**
	 * 接続先の HttpClient オブジェクトを作成します。
	 *
	 * 接続に必要な情報を、設定します。
	 * CloseableHttpClient は、AutoCloseable を継承しています。
	 *
	 * 7.2.5.0 (2020/06/01)
	 *   通常、HttpClientはGETの場合は自動でﾘﾀﾞｲﾚｸﾄ処理しますが、
	 *   POSTの場合は、302が返るだけでﾘﾀﾞｲﾚｸﾄ処理しません。
	 *   http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3[HTTP RFC 2616]で規定されています。
	 *   ここでは、ﾀﾞｳﾝﾛｰﾄﾞﾌｧｲﾙがあり、POSTの場合だけ強制的に
	 *
	 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
	 * @og.rev 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)
	 * @og.rev 8.0.0.0 (2021/07/31) httpclient4 → httpclient5 対応
	 *
	 * @return  HttpConnectionオブジェクト
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	private CloseableHttpClient getClient() throws MalformedURLException {
		final HttpClientBuilder clBuild = HttpClientBuilder.create();

		final BasicCookieStore cookieStore = new BasicCookieStore();
		clBuild.setDefaultCookieStore(cookieStore) ;

		if( timeout >= 0 ) {
			final RequestConfig.Builder reqConfig = RequestConfig.custom();
			reqConfig.setConnectTimeout( timeout ,TimeUnit.SECONDS );		// 8.0.0.0 (2021/07/31) timeoutの単位は(秒)

			clBuild.setDefaultRequestConfig( reqConfig.build() );
		}

		// 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)
		// 8.0.0.0 (2021/07/31) httpclient5-5.1対応により LaxRedirectStrategy 廃止の為一旦削除
	//	if( postRedirect ) {
	//		clBuild.setRedirectStrategy( new LaxRedirectStrategy() );
	//	}

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

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

		// Auth
		// https://github.com/apache/httpcomponents-client/blob/5.1.x/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientAuthentication.java
		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.toCharArray() );	// 8.0.0.0 (2021/07/31)

			final BasicCredentialsProvider credProvider = new BasicCredentialsProvider();			// 8.0.0.0 (2021/07/31)
			credProvider.setCredentials( scope,cred );
			clBuild.setDefaultCredentialsProvider( credProvider );
		}

	//	// (デフォルトの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 ) {
					if( isDebug ) { System.out.println( "GET → POST変更： URLの長さ制限＜" + reqParamBuf.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) 新規作成
	 * @og.rev 7.2.5.0 (2020/06/01) upldFileのnull判定を入れます。
	 *
	 * @param	upldFile ｱｯﾌﾟﾛｰﾄﾞするファイル名。
	 * @throws  IOException 入出力エラーが発生したとき
	 */
	public void setUploadFile( final String upldFile ) throws IOException {
		if( upldFile != null ) {
			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;
	}

	/**
	 * jsonによる2フェーズ認証を行う場合の設定。
	 *
	 * 認証用のデータをJSON形式の文字列で設定します。
	 * urlは、認証するためのURLを指定します。
	 *
	 * @og.rev 8.0.0.0 (2021/08/31) 新規作成
	 *
	 * @param	json	認証用のデータを設定するJSON文字列
	 * @param	url		JSON文字列で認証を行うURL(無ければnull)
	 */
	public void setAuthJson( final String json,final String url ) {
		if( json != null && url != null ) {
			authJson = json;
			authURL  = url;
		}
	// null 設定も可能とする。
	//	else {
	//		final String errMsg = "setAuthJson を使用する場合は、authJsonとauthURLの両方を設定してください。" + CR
	//					+ " authJson=[" + json + "]"	+ CR
	//					+ " authURL =[" + url + "]" ;
	//		throw new IllegalArgumentException( errMsg );
	//	}
	}

	/**
	 * パラメータをJSONで指定する場合に使用します。
	 *
	 * JSON形式でパラメータを指定する場合に使用します。
	 * $XXXX$ 文字列は、先のjson認証で取得した各種ﾊﾟﾗﾒｰﾀを割り当てます。
	 *
	 * @og.rev 8.0.0.0 (2021/08/31) 新規作成
	 *
	 * @param	json	JSON文字列のパラメータ
	 */
	public void setReqJson( final String json ) {
		if( json == null ) { return; }

		reqJson = json;

		// $xxxxx$ 文字列を見つけて、Mapのｷｰとして登録しておく
		int st = reqJson.indexOf( '$' );
		while( st >= 0 ) {
			final int ed = reqJson.indexOf( '$',st+1 );
			if( ed > 0 ) {
				jsonMap.put( reqJson.substring( st+1,ed ) , "" );	// $ は含めず
			}
			st = reqJson.indexOf( '$',ed+1 );
		}
	}

	/**
	 * trueの場合、POST時に強制的にﾘﾀﾞｲﾚｸﾄを行います(初期値:false)。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)
	 * @og.rev 8.0.0.0 (2021/07/31) httpclient5-5.1対応により LaxRedirectStrategy 廃止の為一旦削除
	 *
	 * @param	useRedirect	true:POST時に強制的にﾘﾀﾞｲﾚｸﾄ/false:通常
	 */
	public void setPostRedirect( final boolean useRedirect ) {
		postRedirect = useRedirect;
	}

	/**
	 * 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[*] : [-useForm=true/false]   認証方式にFORM認証を指定する場合、trueをｾｯﾄします(初期値:false) 8.0.0.0 (2021/08/20)
	 *   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。
	 *   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)
	 *   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)
	 *   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です
	 *   args[*] : [-download=ファイル名]  ﾌｧｲﾙ名を指定して、ﾀﾞｳﾝﾛｰﾄﾞします
	 *   args[*] : [-upload=ファイル名]    ﾌｧｲﾙ名を指定して、multipart/form-dataでﾌｧｲﾙｱｯﾌﾟﾛｰﾄﾞします
	 *   args[*] : [-postRedirect=true]    POST時に強制的にﾘﾀﾞｲﾚｸﾄを行います(GET時は自動でﾘﾀﾞｲﾚｸﾄします)(初期値:false) 7.2.5.0 (2020/06/01)
	 *   args[*] : [-usePost=true]         POSTを強制的に使用する場合にｾｯﾄします(初期値:false) 8.0.0.0 (2021/08/20)
	 *   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) 新規作成
	 * @og.rev 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)引数を追加
	 *
	 * @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                     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認証/FORM認証のｴﾘｱへのｱｸｾｽ時のﾕｰｻﾞｰとﾊﾟｽﾜｰﾄﾞを指定します"						);
//			LogWriter.log( "   args[*] : [-useForm=true/false]   認証方式にFORM認証を指定する場合､trueをｾｯﾄします(初期値:false)"					);
			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[*] : [-postRedirect=true]    POST時に強制的にﾘﾀﾞｲﾚｸﾄを行います(GET時は自動でﾘﾀﾞｲﾚｸﾄします)(初期値:false)"		);
			LogWriter.log( "   args[*] : [-usePost=true]         POSTを強制的に使用する場合にｾｯﾄします(初期値:false)"								);
			LogWriter.log( "   args[*] : [-errEx=true/false]     trueの場合､ﾚｽﾎﾟﾝｽｺｰﾄﾞが､4XX,5XX の時に RuntimeException を投げます(初期値:false)"	);
			LogWriter.log( "   args[*] : [-authJson=JSONコード]  JSONコードで認証する場合に使用します。8.0.0.0 (2021/08/31)"						);
			LogWriter.log( "   args[*] : [-authURL=認証用URL]    JSONコードで認証するURLを指定します。8.0.0.0 (2021/08/31)"							);
			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 ;					// -auth
//		boolean	useForm			= false ;					// -useForm			// 8.0.0.0 (2021/08/20)
		String	proxy			= null ;					// -proxy
		int		timeout			= -1 ;						// -timeout
		String	encode			= DEFAULT_CHARSET ;			// -encode
		String	outFile			= null ;					// -out
		String	dwldFile		= null ;					// -download
		String	upldFile		= null ;					// -upload
		boolean	isEx			= false ;					// -errEx
		boolean	isDebug			= false ;					// -debug
		boolean	postRedirect 	= false ;					// -postRedirect	// 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)
		boolean	nonWriter		= false ;					// -out 指定で見つからない場合
		boolean	isPost			= false ;					// -usePost
		String	authJson		= null ;					// -authJson		// 8.0.0.0 (2021/08/31)
		String	authURL			= null ;					// -authURL			// 8.0.0.0 (2021/08/31)
		String	reqJson			= null ;					// -reqJson			// 8.0.0.0 (2021/08/31)

//		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( "-useForm=" ) ) {
//				useForm = "true".equalsIgnoreCase( arg.substring( "-useForm=".length() ) );
//			}
			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() ) );
			}
			// 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にﾘﾀﾞｲﾚｸﾄ)
			else if( arg.startsWith( "-postRedirect=" ) ) {
				postRedirect = "true".equalsIgnoreCase( arg.substring( "-postRedirect=".length() ) );
			}
			else if( arg.startsWith( "-debug=" ) ) {
				isDebug = "true".equalsIgnoreCase( arg.substring( "-debug=".length() ) );
			}
			else if( arg.startsWith( "-usePost=" ) ) {
				isPost = "true".equalsIgnoreCase( arg.substring( "-usePost=".length() ) );
			}
			else if( arg.startsWith( "-authJson=" ) ) {				// 8.0.0.0 (2021/08/31)
				authJson = arg.substring( "-authJson=".length() );
				if( StringUtil.isNull( authJson ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-authURL=" ) ) {				// 8.0.0.0 (2021/08/31)
				authURL = arg.substring( "-authURL=".length() );
				if( StringUtil.isNull( authURL ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			else if( arg.startsWith( "-reqJson=" ) ) {				// 8.0.0.0 (2021/08/31)
				reqJson = arg.substring( "-reqJson=".length() );
				if( StringUtil.isNull( reqJson ) ) {
					System.err.println( arg + "指定した場合は、引数を設定してください。" );
				}
			}
			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.usePost( isPost );
			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 );
			conn.setPostRedirect(	postRedirect );			// 7.2.5.0 (2020/06/01)
			conn.setAuthJson(		authJson,authURL );		// 8.0.0.0 (2021/08/31)
			conn.setReqJson(		reqJson );				// 8.0.0.0 (2021/08/31)

			final String outData = conn.readData();			// 8.0.0.0 (2021/08/20) ﾃｽﾄ用

			try( 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 );
			System.err.println( th.getMessage() );
		}
	}
}
