001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.fukurou.util;
017
018import java.io.File;
019import java.io.IOException;
020import java.io.PrintWriter;
021import java.net.HttpURLConnection;
022import java.net.MalformedURLException;
023import java.net.URL;
024import java.util.Arrays;
025import java.util.ArrayList;
026import java.util.List;
027import java.nio.file.Files;
028import java.nio.file.Paths;
029        // import java.nio.charset.Charset;
030import java.nio.charset.StandardCharsets;
031
032import org.apache.http.Header;
033import org.apache.http.HttpEntity;
034import org.apache.http.HttpHost;
035import org.apache.http.StatusLine;
036import org.apache.http.auth.AuthScope;
037import org.apache.http.auth.Credentials;
038import org.apache.http.auth.UsernamePasswordCredentials;
039import org.apache.http.client.config.RequestConfig;
040import org.apache.http.client.config.CookieSpecs;
041import org.apache.http.client.CredentialsProvider;
042import org.apache.http.client.entity.UrlEncodedFormEntity;
043import org.apache.http.client.methods.CloseableHttpResponse;
044import org.apache.http.client.methods.HttpGet;
045import org.apache.http.client.methods.HttpPost;
046import org.apache.http.client.methods.HttpUriRequest;
047import org.apache.http.entity.ContentType;
048import org.apache.http.entity.mime.MultipartEntityBuilder;
049import org.apache.http.entity.mime.HttpMultipartMode;
050import org.apache.http.impl.client.BasicCredentialsProvider;
051import org.apache.http.impl.client.CloseableHttpClient;
052import org.apache.http.impl.client.HttpClientBuilder;
053import org.apache.http.message.BasicHeader;
054import org.apache.http.message.BasicNameValuePair;
055import org.apache.http.NameValuePair;
056import org.apache.http.util.EntityUtils;
057
058import org.apache.http.impl.client.HttpClients;
059import org.apache.http.client.CookieStore;
060import org.apache.http.entity.StringEntity;
061// import org.apache.http.impl.client.DefaultHttpClient;;
062import org.apache.http.client.protocol.HttpClientContext;
063import java.io.UnsupportedEncodingException;
064
065// import org.apache.http.impl.client.DefaultRedirectStrategy;
066// import org.apache.http.HttpRequest;
067// import org.apache.http.HttpResponse;
068// import org.apache.http.protocol.HttpContext;
069// import org.apache.http.ProtocolException;
070import org.apache.http.impl.client.LaxRedirectStrategy;
071
072// import org.opengion.fukurou.system.Closer;
073import org.opengion.fukurou.system.LogWriter;
074import org.opengion.fukurou.system.OgRuntimeException ;
075
076import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;
077import static org.opengion.fukurou.system.HybsConst.CR;
078
079/**
080 * HttpConnect は、指定のURL にアクセスして、データを取得します。
081 * URL へのアクセスにより、エンジンでは各種処理を実行させることが可能になります。
082 * 例えば、帳票デーモンの起動や、長時間かかる処理の実行などです。
083 * なお、URLに引数が付く場合は、ダブルコーテーションで括って下さい。
084 * URL の指定は、先頭に何もつけませ。指定の順番も関係ありません。
085 * - 付き引数は、指定順番は、関係ありません。
086 * 先頭が # の引数は、コメントと判断します。
087 *
088 * <pre>
089 * Usage: java org.opengion.fukurou.util.HttpConnect [-post=キー:ファイル名] … url [user:passwd]
090 *   args[A] : url                     URLを指定します。GETの場合、パラメータは ?KEY=VALです
091 *   args[*] : [-param=key:value]      POST/GET時のパラメータのキーと値を:で区切って指定します。(複数回指定可)
092 *   args[*] : [-header=key:value]     ヘッダーに設定するパラメータのキーと値を:で区切って指定します。(複数回指定可)
093 *   args[*] : [-auth=user:pass]       BASIC認証のエリアへのアクセス時のユーザーとパスワードを指定します
094 *   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。
095 *   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)
096 *   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)
097 *   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です
098 *   args[*] : [-download=ファイル名]  ファイル名を指定して、ダウンロードします
099 *   args[*] : [-upload=ファイル名]    ファイル名を指定して、multipart/form-dataでファイルアップロードします
100 *   args[*] : [-postRedirect=true]    POST時に強制的にリダイレクトを行います(GET時は自動でリダイレクトします)(初期値:false) 7.2.5.0 (2020/06/01)
101 *   args[*] : [-errEx=true/false]     trueの場合、レスポンスコードが、4XX,5XX の時に RuntimeException を投げます(初期値:false)
102 *   args[*] : [#・・・・]                 コメント引数。(BATファイル上に残しておきたいが、使用したくない場合など)
103 *   args[*] : [-debug=true/false]     trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)
104 * </pre>
105 *
106 * ※ URLConnect との違い。
107 *    -info/-data 等の区別の廃止。(実質、-info がなくなる。)
108 *    setDownloadFile(String) 追加(-binaryの代用)
109 *    setUploadFile(String) 追加
110 *    proxy 設定の変更
111 *
112 * @og.rev 6.9.0.0 (2018/01/31) 新規作成
113 *
114 * @version  6.9.0.0
115 * @author   Kazuhiko Hasegawa
116 * @since    JDK8.0,
117 */
118public class HttpConnect {
119        /** エンコードの初期値  {@value} */
120        public static final String DEFAULT_CHARSET = "UTF-8" ;
121        /** 言語の初期値  {@value} */
122        public static final String DEFAULT_LANG = "ja-JP" ;
123        /** User-Agentの初期値  {@value} */
124        public static final String DEFAULT_AGENT = "openGion with Apache HttpClient" ;
125        /** GETで指定するときのURLの長さ制限  {@value}  (IEの場合は、2,083文字) */
126        public static final int MAX_GET_LENGTH = 2000 ;
127
128        private final String urlStr ;
129        private final String user ;
130        private final String pass ;
131
132        private int                     rpsCode         = -1;
133        private String          rpsMessage      ;
134        private String          charset         = DEFAULT_CHARSET ;
135        private String          upldFile        ;
136        private String          dwldFile        ;                               // バイナリファイルとして受け取る場合のファイル名
137        private int                     timeout         = -1;
138        private boolean         isPost          ;
139        private boolean         postRedirect ;                          // 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)
140        private boolean         isDebug         ;
141
142        private HttpHost        proxy           ;
143
144        private CookieStore     ckStore         ;
145
146        // 初期ヘッダー情報
147        private static final List<Header> INIT_HEADER =
148                                                                                        Arrays.asList(
149                                                                                                new BasicHeader( "Accept-Charset"       , DEFAULT_CHARSET ) ,
150                                                                                                new BasicHeader( "Accept-Language"      , DEFAULT_LANG  ) ,
151                                                                                                new BasicHeader( "User-Agent"           , DEFAULT_AGENT )
152                                                                                        );
153
154        private final List<NameValuePair>       reqParamList = new ArrayList<NameValuePair>();  // リクエストパラメーター(主にPOST時)
155        private final List<Header>                      headers          = new ArrayList<>( INIT_HEADER );      // ヘッダーパラメーター
156
157        // GET でのパラメータのマージ。きちんとした方法がわかるまでの暫定処置
158        private final StringBuilder                     reqParamBuf  = new StringBuilder( BUFFER_MIDDLE );
159
160        /**
161         * 接続先URLと、認証用ユーザー:パスワードを指定する、コンストラクター
162         *
163         * 認証が必要ない場合は、userPass は、null でかまいません。
164         * 接続先URLは、HttpConnect で、urlEncode しますので、そのままの文字列でかまいません。
165         *
166         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
167         *
168         * @param       url                     接続するアドレスを指定します。(http://server:port/dir/file.html)
169         * @param       userPass        ユーザー:パスワード(認証接続が必要な場合)
170         */
171        public HttpConnect( final String url, final String userPass ) {
172                urlStr = StringUtil.urlEncode2( url );
173
174                if( StringUtil.isNull( userPass ) ) {
175                        user = null;
176                        pass = null;
177                }
178                else {
179                        final String[] prm = StringUtil.csv2Array( userPass , ':' , 2 );
180                        user = prm[0];
181                        pass = prm[1];
182                }
183//login();
184        }
185
186        private void login() {
187                try {
188                        final String loginURL = "tp://localhost:8827/gf/jsp/j_security_check?j_user=" + user + "&j_password=" + pass + "&j_security_check=login" ;
189        //              final HttpUriRequest method = new HttpPost( loginURL );
190                        final HttpGet method = new HttpGet( loginURL );
191
192                        CloseableHttpClient httpclient = HttpClients.createDefault();
193                        HttpClientContext context = HttpClientContext.create();
194                        try( CloseableHttpResponse response = httpclient.execute(new HttpGet(loginURL), context) ) {
195                                final StatusLine status = response.getStatusLine();
196                                ckStore = context.getCookieStore();
197                        }
198
199        //              HttpClientContext context = HttpClientContext.create();
200        //              try( CloseableHttpClient client = HttpClients.createDefault() ) {
201        //                      CloseableHttpResponse response = client.execute(method, context);
202
203
204//                              StringEntity paramEntity = new StringEntity( "" );
205//                              paramEntity.setChunked( false );
206//                              paramEntity.setContentType( "application/x-www-form-urlencoded" );
207//                              method.setEntity( paramEntity );
208
209                //              DefaultHttpClient client = new DefaultHttpClient();
210                //              HttpResponse response = client.execute( method, context );
211        //                      ckStore = client.getCookieStore();
212        //              }
213                }
214                catch( UnsupportedEncodingException ex ) { ex.printStackTrace(); }
215                catch( IOException ex ) { ex.printStackTrace(); }
216        }
217
218        /**
219         * URL接続先のデータを取得します。
220         *
221         * この処理の前に、必要な情報を設定して置いてください。
222         * また、code や message は、このメソッドを実行しないと取得できませんのでご注意ください。
223         *
224         * 取得したデータは、指定のURL へのアクセスのみです。
225         * 通常のWebブラウザは、イメージや、JavaScriptファイル、CSSファイルなど、
226         * 各種ファイル毎にHTTP接続を行い、取得して、レンダリングします。
227         * このメソッドでの処理では、それらのファイル内に指定されているURLの
228         * 再帰的な取得は行いません。
229         * よって、フレーム処理なども行いません。
230         *
231         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
232         *
233         * @return      接続結果
234         * @og.rtnNotNull
235         * @throws  IOException 入出力エラーが発生したとき
236         * @throws  MalformedURLException URLの形式が間違っている場合
237         */
238        public String readData() throws IOException , MalformedURLException {
239                HttpUriRequest method ;
240                if( isPost ) {
241                        if( isDebug ) { System.out.println( "POST URL=" + urlStr ); }
242                        method = new HttpPost( urlStr );
243
244                        if( !reqParamList.isEmpty() ) {
245                                ((HttpPost)method).setEntity( new UrlEncodedFormEntity( reqParamList , DEFAULT_CHARSET ) );
246                                if( isDebug ) { reqParamList.forEach( v -> System.out.println( "PARAM KEY=" + v.getName() + " , VAL=" + v.getValue() ) ); }
247                        }
248
249                        if( !StringUtil.isNull( upldFile ) ) {
250                                final File file = new File( upldFile );
251                                if( isDebug ) { System.out.println( "  MULTI FILE=" + file ); }
252                                final HttpEntity entity = MultipartEntityBuilder.create()
253                                                                                .setMode( HttpMultipartMode.BROWSER_COMPATIBLE )
254                                                                                .setCharset( StandardCharsets.UTF_8 )   // ファイル名の文字化け対策
255                                                                                .addBinaryBody( "upload" ,
256                                                                                                                file ,
257                                                                                                                ContentType.DEFAULT_BINARY ,
258                                                                                                                file.getName() )
259                                                                                .build();
260                                ((HttpPost)method).setEntity( entity );
261                        }
262                }
263                else {
264                        // GET でのパラメータのマージ。きちんとした方法がわかるまでの暫定処置
265                        final String getStr = reqParamBuf.length() == 0
266                                                                        ? urlStr
267                                                                        : reqParamBuf.toString() ;
268
269                        if( isDebug ) { System.out.println( "GET URL=" + getStr ); }
270
271                        method = new HttpGet( getStr );
272                }
273
274HttpClientContext context = HttpClientContext.create();
275if( ckStore != null ) {
276context.setCookieStore(ckStore);
277}
278
279                String body = null;
280                try( CloseableHttpClient client = getClient() ;
281//                       CloseableHttpResponse response = client.execute(method) ) {
282                         CloseableHttpResponse response = client.execute(method,context) ) {
283
284                        final StatusLine status = response.getStatusLine();
285                        final HttpEntity entity = response.getEntity();
286
287                        rpsCode    = status.getStatusCode();
288                        rpsMessage = ( code2Message( rpsCode ) + CR + status.getReasonPhrase() ).trim();
289
290                        // バイナリファイルとして受け取る場合。成功(200番台)のみ処理します。
291                        if( !StringUtil.isNull( dwldFile ) && rpsCode >= 200 && rpsCode < 300 ) {
292                                Files.write( Paths.get( dwldFile ) , EntityUtils.toByteArray( entity ) );
293                                body = dwldFile;
294                        }
295                        else {
296                                if( entity == null ) {
297                                        body = rpsMessage;              // HttpEntity が受け取れなかった場合は、メッセージを表示します。
298                                }
299                                else {
300                                        body = EntityUtils.toString( entity, charset );
301                                }
302                        }
303                }
304
305                return body;
306        }
307
308        /**
309         * 接続先の HttpClient オブジェクトを作成します。
310         *
311         * 接続に必要な情報を、設定します。
312         * CloseableHttpClient は、AutoCloseable を継承しています。
313         *
314         * 7.2.5.0 (2020/06/01)
315         *   通常、HttpClientはGETの場合は自動でリダイレクト処理しますが、
316         *   POSTの場合は、302が返るだけでリダイレクト処理しません。
317         *   http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3[HTTP RFC 2616]で規定されています。
318         *   ここでは、ダウンロードファイルがあり、POSTの場合だけ強制的に
319         *
320         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
321         * @og.rev 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)
322         *
323         * @return  HttpConnectionオブジェクト
324         * @throws  IOException 入出力エラーが発生したとき
325         */
326        private CloseableHttpClient getClient() throws MalformedURLException {
327
328                final HttpClientBuilder clBuild = HttpClientBuilder.create();
329
330                // request configuration
331                final RequestConfig.Builder reqConfig = RequestConfig.custom()
332                        .setCookieSpec( CookieSpecs.STANDARD );                                 // 最新のRFC準拠ヘッダーを理解するのが困難なので。
333
334                if( timeout >= 0 ) {
335                        reqConfig.setConnectTimeout( timeout * 1000 )                   // timeoutの単位は(秒)、設定値は、ミリ秒
336                                         .setSocketTimeout(  timeout * 1000 );
337                }
338
339                clBuild.setDefaultRequestConfig( reqConfig.build() );
340
341                // 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)
342                if( postRedirect ) {
343                        clBuild.setRedirectStrategy( new LaxRedirectStrategy() );
344                }
345
346                // headers (初期設定も入っているので、通常は、empty にはならない。)
347                if( !headers.isEmpty() ) {
348                        clBuild.setDefaultHeaders( headers );
349                }
350
351                // Auth
352                if( !StringUtil.isNull( user ) ) {
353                        final URL url = new URL( urlStr );
354                        final AuthScope   scope = new AuthScope( url.getHost(), url.getPort() );
355                        final Credentials cred  = new UsernamePasswordCredentials( user ,pass );
356
357                        final CredentialsProvider credProvider = new BasicCredentialsProvider();
358                        credProvider.setCredentials( scope,cred );
359
360                        clBuild.setDefaultCredentialsProvider( credProvider );
361                }
362
363                // Proxy
364                if( proxy != null ) {
365                        clBuild.setProxy( proxy );
366                }
367
368        //      // (デフォルトのHttpClientは、最新のRFC準拠ヘッダーを理解するのが困難です。)
369        //  // RequestConfig に、CookieSpecs.STANDARD を設定しているが、効果なければ、使わなくしてしまう。
370        //      clBuild.disableCookieManagement();
371
372                return clBuild.build();         // HttpClient httpClient  = HttpClientBuilder.create().*****.build();
373        }
374
375        /**
376         * 接続先に使用する引数(パラメータ)を追加します。
377         *
378         * これは、POSTでも、GETでも使用できます。
379         * POSTの場合は、NameValuePair として、HttpPost に、Entity としてセットするデータを設定します。
380         * GET の場合は、既存の接続先URLに、&amp;キー=値・・・・ で、追記します。
381         * すでに、パラメータが指定済みの場合は、&amp; で、そうでなければ、? で連結します。
382         * ここで指定するパラメータは、内部で、urlEncode しますので、そのままの文字列でかまいません。
383         *
384         * デフォルトは、GETですが、Internet Explorer では URL に最大 2,083 文字しか指定できないため、
385         * それ以上の場合は、POST に自動で切り替えます。
386         *
387         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
388         *
389         * @param       key     パラメータキー(nullの場合は、登録しません)
390         * @param       val     パラメータ値
391         */
392        public void addRequestProperty( final String key, final String val ) {
393                if( !StringUtil.isNull( key ) ) {
394                        reqParamList.add( new BasicNameValuePair( key,val ) );                          // POST のときのパラメータ。(GETでも使えるはず?)
395
396                        if( !isPost ) {                                                                                                         // 明らかに、GET でない場合は、この処理を行わない。
397                                if( reqParamBuf.length() == 0 ) {                                                               // 初めての場合
398                                        reqParamBuf.append( urlStr )
399                                                                .append( urlStr.indexOf( '?' ) > 0 ? '&' : '?' )
400                                                                .append( StringUtil.urlEncode2( key ) )
401                                                                .append( '=' )
402                                                                .append( StringUtil.urlEncode2( val ) );                // null のときは、長さゼロ文字列になる。
403                                }
404                                else if( reqParamBuf.length() > MAX_GET_LENGTH ) {
405                                        System.out.println( "GET → POST変更: URLの長さ制限<" + reqParamBuf.length() );
406                                        isPost = true;                                                                                          // GETで送れるURLの長さ制限を超えた場合は、POSTにする。
407                                }
408                                else {
409                                        reqParamBuf.append( '&' )
410                                                                .append( StringUtil.urlEncode2( key ) )
411                                                                .append( '=' )
412                                                                .append( StringUtil.urlEncode2( val ) );                // null のときは、長さゼロ文字列になる。
413                                }
414                        }
415                }
416        }
417
418        /**
419         * setRequestPropertyでセットするデータを設定します。
420         *
421         * keys,vals各々、カンマ区切りで分解します。
422         *
423         * @og.rev 5.10.16.0 (2019/10/04) 追加
424         *
425         * @param       keys    パラメータキー(カンマ区切り)
426         * @param       vals    パラメータ(カンマ区切り)
427         */
428        public void setRequestProperty( final String keys, final String vals ) {
429                if( keys != null && keys.length() > 0 && vals != null && vals.length() > 0 ){
430                        final String[]  propKeys = StringUtil.csv2Array( keys );
431                        final String[]  propVals = StringUtil.csv2Array( vals );
432
433                        if( propKeys.length == propVals.length && propKeys.length > 0 ) {
434                                for( int i=0; i<propKeys.length; i++ ) {
435                                        addRequestProperty( propKeys[i], propVals[i] );
436                                }
437                        }
438                        else {
439                                final String errMsg = "パラメータのキーと、値の数が一致しません。"   + CR
440                                                        + " key=[" + keys + "]"                                                                 + CR
441                                                        + " val=[" + vals + "]" ;
442                                throw new IllegalArgumentException( errMsg );
443                        }
444                }
445        }
446
447        /**
448         * 指定のURLに対して、コネクトするのに使用するプロキシ設定を行います。
449         * このときに、ヘッダー情報を内部変数に設定しておきます。
450         *
451         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
452         *
453         * @param       host    接続するプロキシのホスト名(nullの場合は、登録しません)
454         * @param       port    接続するプロキシのポート番号
455         */
456        public void setProxy( final String host,final int port ) {
457                if( !StringUtil.isNull( host ) ) {
458                        proxy = new HttpHost( host , port );
459                }
460        }
461
462        /**
463         * Header として、HttpClient にセットするデータを設定します。
464         *
465         * 例えばJSON形式でPOSTする場合は通常"Content-Type", "application/json"を指定します。
466         *
467         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
468         *
469         * @param       key     パラメータキー(nullの場合は、登録しません)
470         * @param       val     パラメータ値(nullの場合は、登録しません)
471         */
472        public void addHeaderProperty( final String key, final String val ) {
473                if( !StringUtil.isNull( key ) && !StringUtil.isNull( val ) ) {
474                        headers.add( new BasicHeader( key,val ) );
475                }
476        }
477
478        /**
479         * URL接続先のバイナリファイルをダウンロード取得します。
480         *
481         * 取得したファイルは、dwldFile にバイナリのまま書き込まれます。
482         * よって、エンコードの指定は不要です。
483         *
484         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
485         *
486         * @param       dwldFile ダウンロードするファイル名。
487         * @throws  IOException 入出力エラーが発生したとき
488         */
489        public void setDownloadFile( final String dwldFile ) throws IOException {
490                this.dwldFile = dwldFile;
491        }
492
493        /**
494         * URL接続先のバイナリファイルをアップロードします。
495         *
496         * 取得したファイルは、upldFile にバイナリのまま書き込まれます。
497         * よって、エンコードの指定は不要です。
498         * アップロード は、multipart/form-data で送信するため、isPost = true を
499         * 内部的に設定しておきます。
500         *
501         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
502         * @og.rev 7.2.5.0 (2020/06/01) upldFileのnull判定を入れます。
503         *
504         * @param       upldFile アップロードするファイル名。
505         * @throws  IOException 入出力エラーが発生したとき
506         */
507        public void setUploadFile( final String upldFile ) throws IOException {
508                if( upldFile != null ) {
509                        this.upldFile = upldFile;
510                        isPost = true;
511                }
512        }
513
514        /**
515         * エンコード情報を設定します。
516         *
517         * 初期値は、UTF-8 です。
518         *
519         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
520         *
521         * @param  chset エンコード情報(nullの場合は、初期値:UTF-8 になります)
522         */
523        public void setCharset( final String chset ) {
524                if( !StringUtil.isNull( chset ) ) {
525                        charset = chset;
526                }
527        }
528
529        /**
530         * 接続タイムアウト時間を(秒)で指定します
531         *
532         * 実際には、org.apache.http.client.config.RequestConfig に対して、
533         *       .setConnectTimeout( timeout * 1000 )
534         *       .setSocketTimeout(  timeout * 1000 )
535         * のように、 1000倍して設定しています。
536         * 0 は、無限のタイムアウト、マイナスは、設定しません。(つまりJavaの初期値のまま)
537         *
538         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
539         *
540         * @param       tout    タイムアウト時間(秒) (ゼロは、無制限)
541         */
542        public void setTimeout( final int tout ) {
543                timeout = tout;
544        }
545
546        /**
547         * trueの場合、POSTを使用して接続します(初期値:false)。
548         *
549         * 通常はGETですが、外部から強制的に、POSTで送信したい場合に、
550         * 設定します。
551         * ただし、バイナリファイルをアップロードか、URLの長さ制限が、
552         * {@value #MAX_GET_LENGTH} を超えた場合は、内部で自動的に、post にします。
553         *
554         * @og.rev 6.9.0.1 (2018/02/05) 新規作成
555         *
556         * @param       usePost true:POST使用/false:通常(GET)
557         */
558        public void usePost( final boolean usePost ) {
559                isPost = usePost;
560        }
561
562        /**
563         * trueの場合、POST時に強制的にリダイレクトを行います(初期値:false)。
564         *
565         * @og.rev 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)
566         *
567         * @param       useRedirect     true:POST時に強制的にリダイレクト/false:通常
568         */
569        public void setPostRedirect( final boolean useRedirect ) {
570                postRedirect = useRedirect;
571        }
572
573        /**
574         * trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)。
575         *
576         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
577         *
578         * @param       isDebug true:デバッグ用のメッセージを出力/false:通常
579         */
580        public void setDebug( final boolean isDebug ) {
581                this.isDebug = isDebug;
582        }
583
584        /**
585         * 実行結果のステータスコード 情報を取得します。
586         *
587         * 結果は、#readData() メソッドをコールしないと取れません。
588         * 未実行の場合は、-1 がセットされています。
589         *
590         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
591         *
592         * @return      結果コード 情報
593         * @see         #readData()
594         */
595        public int getCode() { return rpsCode; }
596
597        /**
598         * メッセージ 情報を取得します。
599         *
600         * 結果は、#readData() メソッドをコールしないと取れません。
601         * 未実行の場合は、null がセットされています。
602         *
603         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
604         *
605         * @return      メッセージ 情報
606         */
607        public String getMessage() { return rpsMessage; }
608
609        /**
610         * HttpURLConnection のレスポンスコードに対応するメッセージ文字列を返します。
611         *
612         * HttpURLConnection の getResponseCode() メソッドにより取得された、HTTPレスポンスコード
613         * に対応する文字列を返します。この文字列は、HttpURLConnection で定義された
614         * static 定数のコメントを、定義しています。
615         *
616         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
617         *
618         * @param       code    HTTPレスポンスコード
619         *
620         * @return      レスポンスコードに対応する文字列
621         * @og.rtnNotNull
622         * @see HttpURLConnection#HTTP_ACCEPTED
623         */
624        public static String code2Message( final int code ) {
625                final String msg ;
626                switch( code ) {
627                        case 100                                                                                : msg = "100: 要求は続行可能です。"                                               ;       break;
628                        case 101                                                                                : msg = "101: プロトコルを切り替えます。"                            ;       break;
629                        case HttpURLConnection.HTTP_OK                                  : msg = "200: OK です。"                                                           ;       break;
630                        case HttpURLConnection.HTTP_CREATED                     : msg = "201: 作成されました。"                                                 ;       break;
631                        case HttpURLConnection.HTTP_ACCEPTED                    : msg = "202: 受け入れられました。"                                               ;       break;
632                        case HttpURLConnection.HTTP_NOT_AUTHORITATIVE   : msg = "203: 信頼できない情報です。"                                      ;       break;
633                        case HttpURLConnection.HTTP_NO_CONTENT                  : msg = "204: コンテンツがありません。"                                     ;       break;
634                        case HttpURLConnection.HTTP_RESET                               : msg = "205: コンテンツをリセットします。"                           ;       break;
635                        case HttpURLConnection.HTTP_PARTIAL                     : msg = "206: 部分的なコンテンツです。"                                     ;       break;
636                        case HttpURLConnection.HTTP_MULT_CHOICE                 : msg = "300: 複数の選択肢があります。"                                     ;       break;
637                        case HttpURLConnection.HTTP_MOVED_PERM                  : msg = "301: 永続的に移動されました。"                                     ;       break;
638                        case HttpURLConnection.HTTP_MOVED_TEMP                  : msg = "302: 一時的なリダイレクト。"                                      ;       break;
639                        case HttpURLConnection.HTTP_SEE_OTHER                   : msg = "303: ほかを参照してください。"                                     ;       break;
640                        case HttpURLConnection.HTTP_NOT_MODIFIED                : msg = "304: 変更されていません。"                                               ;       break;
641                        case HttpURLConnection.HTTP_USE_PROXY                   : msg = "305: プロキシを使用します。"                                      ;       break;
642                        case 306                                                                                : msg = "306: 仕様の拡張案です。"                                                ;       break;
643                        case 307                                                                                : msg = "307: 一時的なリダイレクトです。"                            ;       break;
644                        case HttpURLConnection.HTTP_BAD_REQUEST                 : msg = "400: 不当な要求です。"                                                 ;       break;
645                        case HttpURLConnection.HTTP_UNAUTHORIZED                : msg = "401: 認証されませんでした。"                                      ;       break;
646                        case HttpURLConnection.HTTP_PAYMENT_REQUIRED    : msg = "402: 支払いが必要です。"                                                ;       break;
647                        case HttpURLConnection.HTTP_FORBIDDEN                   : msg = "403: 禁止されています。"                                                ;       break;
648                        case HttpURLConnection.HTTP_NOT_FOUND                   : msg = "404: 見つかりませんでした。"                                      ;       break;
649                        case HttpURLConnection.HTTP_BAD_METHOD                  : msg = "405: メソッドは許可されません。"                            ;       break;
650                        case HttpURLConnection.HTTP_NOT_ACCEPTABLE              : msg = "406: 受け入れられません。"                                               ;       break;
651                        case HttpURLConnection.HTTP_PROXY_AUTH                  : msg = "407: プロキシの認証が必要です。"                            ;       break;
652                        case HttpURLConnection.HTTP_CLIENT_TIMEOUT              : msg = "408: 要求がタイムアウトしました。"                           ;       break;
653                        case HttpURLConnection.HTTP_CONFLICT                    : msg = "409: 重複しています。"                                                 ;       break;
654                        case HttpURLConnection.HTTP_GONE                                : msg = "410: 存在しません。"                                                  ;       break;
655                        case HttpURLConnection.HTTP_LENGTH_REQUIRED     : msg = "411: 長さが必要です。"                                                 ;       break;
656                        case HttpURLConnection.HTTP_PRECON_FAILED               : msg = "412: 前提条件が満たされていません。"                  ;       break;
657                        case HttpURLConnection.HTTP_ENTITY_TOO_LARGE    : msg = "413: 要求のエンティティが大きすぎます。"                ;       break;
658                        case HttpURLConnection.HTTP_REQ_TOO_LONG                : msg = "414: 要求のURIが大きすぎます。"                           ;       break;
659                        case HttpURLConnection.HTTP_UNSUPPORTED_TYPE    : msg = "415: サポートされないメディアタイプです。"               ;       break;
660                        case 416                                                                                : msg = "416: 要求された範囲は不十分です。"                           ;       break;
661                        case 417                                                                                : msg = "417: 要求どおりの処理が不可能です。"                  ;       break;
662                        case HttpURLConnection.HTTP_INTERNAL_ERROR              : msg = "500: 内部サーバエラーです。"                                      ;       break;
663                        case HttpURLConnection.HTTP_NOT_IMPLEMENTED     : msg = "501: 実装されていません。"                                               ;       break;
664                        case HttpURLConnection.HTTP_BAD_GATEWAY                 : msg = "502: 誤ったゲートウェイです。"                                     ;       break;
665                        case HttpURLConnection.HTTP_UNAVAILABLE                 : msg = "503: サービスが利用できません。"                            ;       break;
666                        case HttpURLConnection.HTTP_GATEWAY_TIMEOUT     : msg = "504: ゲートウェイがタイムアウトしました。"               ;       break;
667                        case HttpURLConnection.HTTP_VERSION                     : msg = "505: サポートされていないHTTPバージョンです。"   ;       break;
668                        default                                                                                 : msg = code + ": 未定義"                                                          ;       break;
669                }
670                return msg ;
671        }
672
673        /**
674         * サンプル実行用のメインメソッド
675         *
676         * <pre>
677         * Usage: java org.opengion.fukurou.util.HttpConnect [-post=キー:ファイル名] … url [user:passwd]
678         *   args[A] : url                     URLを指定します。GETの場合、パラメータは ?KEY=VALです
679         *   args[*] : [-param=key:value]      POST/GET時のパラメータのキーと値を:で区切って指定します。(複数回指定可)
680         *   args[*] : [-header=key:value]     ヘッダーに設定するパラメータのキーと値を:で区切って指定します。(複数回指定可)
681         *   args[*] : [-auth=user:pass]       BASIC認証のエリアへのアクセス時のユーザーとパスワードを指定します
682         *   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。
683         *   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)
684         *   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)
685         *   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です
686         *   args[*] : [-download=ファイル名]  ファイル名を指定して、ダウンロードします
687         *   args[*] : [-upload=ファイル名]    ファイル名を指定して、multipart/form-dataでファイルアップロードします
688         *   args[*] : [-postRedirect=true]    POST時に強制的にリダイレクトを行います(GET時は自動でリダイレクトします)(初期値:false) 7.2.5.0 (2020/06/01)
689         *   args[*] : [-errEx=true/false]     trueの場合、レスポンスコードが、4XX,5XX の時に RuntimeException を投げます(初期値:false)
690         *   args[*] : [#・・・・]                 コメント引数。(BATファイル上に残しておきたいが、使用したくない場合など)
691         *   args[*] : [-debug=true/false]     trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)
692         * </pre>
693         *
694         * @og.rev 6.9.0.0 (2018/01/31) 新規作成
695         * @og.rev 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)引数を追加
696         *
697         * @param       args    コマンド引数配列
698         * @throws IOException 入出力エラーが発生したとき
699         */
700        public static void main( final String[] args ) throws IOException {
701                if( args.length < 2 ) {
702                        LogWriter.log( "Usage: java org.opengion.fukurou.util.HttpConnect [-data/-binary] … url"                                                                                                        );
703                        LogWriter.log( "   args[A] : url                     URLを指定します。GETの場合、パラメータは ?KEY=VALです"                                                                );
704                        LogWriter.log( "   args[*] : [-param=key:value]      POST/GET時のパラメータのキーと値を:で区切って指定します。(複数回指定可)"                                                );
705                        LogWriter.log( "   args[*] : [-header=key:value]     ヘッダーに設定するパラメータのキーと値を:で区切って指定します。(複数回指定可)"                                        );
706                        LogWriter.log( "   args[*] : [-auth=user:pass]       BASIC認証のエリアへのアクセス時のユーザーとパスワードを指定します"                                               );
707                        LogWriter.log( "   args[*] : [-proxy=host:port]      proxy を使用する場合のホストとポートを指定します。"                                                                      );
708                        LogWriter.log( "   args[*] : [-timeout=3]            接続タイムアウト時間を(秒)で指定します(初期値:無指定)"                                                                     );
709                        LogWriter.log( "   args[*] : [-encode=UTF-8]         エンコードを指定します。(初期値は UTF-8)"                                                                                          );
710                        LogWriter.log( "   args[*] : [-out=ファイル名]       結果をファイルに出力します。初期値は標準出力です"                                                                               );
711                        LogWriter.log( "   args[*] : [-download=ファイル名]  ファイル名を指定して、ダウンロードします"                                                                                                         );
712                        LogWriter.log( "   args[*] : [-upload=ファイル名]    ファイル名を指定して、multipart/form-dataでファイルアップロードします"                                                         );
713                        LogWriter.log( "   args[*] : [-postRedirect=true]    POST時に強制的にリダイレクトを行います(GET時は自動でリダイレクトします)(初期値:false)"                     );
714                        LogWriter.log( "   args[*] : [-errEx=true/false]     trueの場合、レスポンスコードが、4XX,5XX の時に RuntimeException を投げます(初期値:false)" );
715                        LogWriter.log( "   args[*] : [#・・・・]                 コメント引数。(BATファイル上に残しておきたいが、使用したくない場合など)"                                            );
716                        LogWriter.log( "   args[*] : [-debug=true/false]     trueの場合、適度にデバッグ用のメッセージを出力します(初期値:false)"                                   );
717                        return;
718                }
719
720                String  urlStr                  = null ;
721                final List<String> paramKey  = new ArrayList<>();       // パラメーターキー
722                final List<String> paramVal  = new ArrayList<>();       // パラメーター値
723                final List<String> headerKey = new ArrayList<>();       // パラメーターキー
724                final List<String> headerVal = new ArrayList<>();       // パラメーター値
725
726                String  userPass                = null ;
727                String  proxy                   = null ;
728                int             timeout                 = -1 ;
729                String  encode                  = DEFAULT_CHARSET ;
730                String  outFile                 = null ;
731                String  dwldFile                = null ;
732                String  upldFile                = null ;
733                boolean isEx                    = false ;
734                boolean isDebug                 = false ;
735                boolean postRedirect    = false ;                       // 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)
736                boolean nonWriter               = false ;
737
738//              int             code                    = -1;
739
740                for( final String arg : args ) {
741                        if( arg.startsWith( "-param=" ) ) {
742                                final String[] prm = StringUtil.csv2Array( arg.substring( "-param=".length() ) , '=' , 2 );
743                                paramKey.add( prm[0] );
744                                paramVal.add( prm[1] );
745                        }
746                        else if( arg.startsWith( "-header=" ) ) {
747                                final String[] prm = StringUtil.csv2Array( arg.substring( "-header=".length() ) , '=' , 2 );
748                                headerKey.add( prm[0] );
749                                headerVal.add( prm[1] );
750                        }
751                        else if( arg.startsWith( "-auth=" ) ) {
752                                userPass = arg.substring( "-auth=".length() );
753                                if( StringUtil.isNull( userPass ) ) {
754                                        System.err.println( arg + "指定した場合は、引数を設定してください。" );
755                                }
756                        }
757                        else if( arg.startsWith( "-proxy=" ) ) {
758                                proxy = arg.substring( "-proxy=".length() );
759                                if( StringUtil.isNull( proxy ) ) {
760                                        System.err.println( arg + "指定した場合は、引数を設定してください。" );
761                                }
762                        }
763                        else if( arg.startsWith( "-timeout=" ) ) {
764                                timeout = Integer.parseInt( arg.substring( "-timeout=".length() ) );
765                        }
766                        else if( arg.startsWith( "-encode=" ) ) {
767                                encode = arg.substring( "-encode=".length() );
768                                if( StringUtil.isNull( encode ) ) {
769                                        System.err.println( arg + "指定した場合は、引数を設定してください。" );
770                                }
771                        }
772                        else if( arg.startsWith( "-out=" ) ) {
773                                outFile = arg.substring( "-out=".length() );
774                                if( StringUtil.isNull( outFile ) ) {
775                                        System.err.println( arg + "指定した場合は、引数を設定してください。" );
776                                }
777                                else {
778                                        if( "null".equalsIgnoreCase( outFile ) || "none".equalsIgnoreCase( outFile ) ) {
779                                                outFile   = null;
780                                                nonWriter = true;
781                                        }
782                                }
783                        }
784                        else if( arg.startsWith( "-download=" ) ) {
785                                dwldFile = arg.substring( "-download=".length() );
786                                if( StringUtil.isNull( dwldFile ) ) {
787                                        System.err.println( arg + "指定した場合は、引数を設定してください。" );
788                                }
789                        }
790                        else if( arg.startsWith( "-upload=" ) ) {
791                                upldFile = arg.substring( "-upload=".length() );
792                                if( StringUtil.isNull( upldFile ) ) {
793                                        System.err.println( arg + "指定した場合は、引数を設定してください。" );
794                                }
795                        }
796                        else if( arg.startsWith( "-errEx=" ) ) {
797                                isEx = "true".equalsIgnoreCase( arg.substring( "-errEx=".length() ) );
798                        }
799                        // 7.2.5.0 (2020/06/01) postRedirect(POST時に強制的にリダイレクト)
800                        else if( arg.startsWith( "-postRedirect=" ) ) {
801                                postRedirect = "true".equalsIgnoreCase( arg.substring( "-postRedirect=".length() ) );
802                        }
803                        else if( arg.startsWith( "-debug=" ) ) {
804                                isDebug = "true".equalsIgnoreCase( arg.substring( "-debug=".length() ) );
805                        }
806                        else if( StringUtil.startsChar( arg , '-' ) ) {                 // 引数が未定義(処理は継続させます。)
807                                System.err.println( "Error Argment:" + arg );
808                        }
809                        else if( StringUtil.startsChar( arg , '#' ) ) {                 // 引数がコメント
810                                continue;
811                        }
812                        else {
813                                urlStr = arg;
814                        }
815                }
816
817                try {                                                                   // try catch を入れます。
818                        final HttpConnect conn = new HttpConnect( urlStr,userPass );
819                        conn.setDebug( isDebug );                       // 最初に入れておけば、それ以降、有効になります。
820
821                        for( int i=0; i<paramKey.size(); i++ ) {
822                                conn.addRequestProperty( paramKey.get(i) , paramVal.get(i) );
823                        }
824
825                        for( int i=0; i<headerKey.size(); i++ ) {
826                                conn.addHeaderProperty( headerKey.get(i) , headerVal.get(i) );
827                        }
828
829                        // 6.8.1.3 (2017/08/04) proxy の設定
830                        if( !StringUtil.isNull( proxy ) ) {
831                                final String[] prm = StringUtil.csv2Array( proxy , ':' , 2 );
832                                final String host = prm[0];
833                                final int    port = Integer.parseInt( prm[1] );
834                                conn.setProxy( host , port );
835                        }
836
837                        conn.setCharset(                encode );               // encode 指定
838                        conn.setTimeout(                timeout );              // timeout属性追加
839                        conn.setUploadFile(             upldFile );
840                        conn.setDownloadFile(   dwldFile );
841                        conn.setPostRedirect(   postRedirect ); // 7.2.5.0 (2020/06/01)
842
843                        final String outData = conn.readData();
844
845                        try( PrintWriter writer = StringUtil.isNull( outFile )
846                                                                                                ? FileUtil.getLogWriter( "System.out" )
847                                                                                                : FileUtil.getPrintWriter( new File( outFile ),encode ) ) {
848                                if( !nonWriter ) {
849                                        writer.println( outData );
850                                }
851                                final int code = conn.getCode();
852
853                                // isEx=trueの場合、レスポンスコードが、4XX,5XX の時に RuntimeException を投げます
854                                if( code >= 400 ) {
855                                        final String errMsg = conn.getMessage();
856                                        writer.println( errMsg );
857                                        if( isEx ) {
858                                                throw new OgRuntimeException( errMsg );
859                                        }
860                                        else {
861                                                System.exit( code );
862                                        }
863                                }
864                        }
865                }
866                catch( final Throwable th ) {
867                        throw new OgRuntimeException( th );
868                }
869        }
870}