OSDN Git Service

Merge commit 'cb03a1ed529387c1084e04e6f141eb9d35d095b8'
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / net / ServerAccess.java
index a19fff1..7f65db8 100644 (file)
@@ -12,14 +12,12 @@ import io.bitbucket.olyutorskii.jiocema.DecodeNotifier;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.lang.ref.SoftReference;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.Proxy;
 import java.net.URL;
 import java.nio.charset.Charset;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.logging.Logger;
@@ -33,29 +31,46 @@ import jp.sfjp.jindolf.data.Village;
 
 /**
  * 国ごとの人狼BBSサーバとの通信を一手に引き受ける。
+ *
+ * <p>受信対象はHTMLと各種アイコンJPEG画像。
+ *
+ * <p>プロクシサーバを介したHTTP通信を管理する。
+ *
+ * <p>国ごとに文字コードを保持し、HTML文書のデコードに用いられる。
+ *
+ * <p>画像(40種強)はキャッシュ管理が行われる。
+ *
+ * <p>※ 2020-02現在、進行中の村は存在しないゆえ、
+ * Cookie認証処理は削除された。
+ *
+ * <p>最後にHTTP受信が行われた時刻を保持する。
  */
 public class ServerAccess{
 
+    private static final int BUFLEN_CONTENT = 200 * 1024;
+
     private static final String USER_AGENT = HttpUtils.getUserAgentName();
     private static final String JINRO_CGI = "./index.rb";
+
     private static final
             Map<String, SoftReference<BufferedImage>> IMAGE_CACHE;
+    private static final Object CACHE_LOCK = new Object();
 
     private static final Logger LOGGER = Logger.getAnonymousLogger();
-    private static final String ENC_POST = "UTF-8";
 
     static{
-        Map<String, SoftReference<BufferedImage>> cache =
-                new HashMap<>();
-        IMAGE_CACHE = Collections.synchronizedMap(cache);
+        IMAGE_CACHE = new HashMap<>();
     }
 
 
     private final URL baseURL;
-    private final AuthManager authManager;
 
     private final Charset charset;
+    private final boolean isSJIS;
+    private final boolean isUTF8;
+
     private Proxy proxy = Proxy.NO_PROXY;
+
     private long lastServerMs;
     private long lastLocalMs;
     private long lastSystemMs;
@@ -63,7 +78,9 @@ public class ServerAccess{
 
     /**
      * 人狼BBSサーバとの接続管理を生成する。
-     * この時点ではまだ通信は行われない。
+     *
+     * <p>この時点ではまだ通信は行われない。
+     *
      * @param baseURL 国別のベースURL
      * @param charset 国のCharset
      * @throws IllegalArgumentException 不正なURL
@@ -73,15 +90,32 @@ public class ServerAccess{
         super();
 
         this.baseURL = baseURL;
-        this.authManager = new AuthManager(this.baseURL);
         this.charset = charset;
 
+        String charsetName = this.charset.name();
+        if("Shift_JIS".equalsIgnoreCase(charsetName)){
+            this.isSJIS = true;
+            this.isUTF8 = false;
+        }else if("UTF-8".equalsIgnoreCase(charsetName)){
+            this.isSJIS = false;
+            this.isUTF8 = true;
+        }else{
+            throw new IllegalArgumentException(charsetName);
+        }
+        assert this.isSJIS ^ this.isUTF8;
+
         return;
     }
 
 
     /**
      * 画像キャッシュを検索する。
+     *
+     * <p>キーは画像URL文字列。
+     *
+     * <p>ソフト参照オブジェクトの解放などにより、
+     * キャッシュの状況は変化する。
+     *
      * @param key キー
      * @return キャッシュされた画像。キャッシュされていなければnull。
      */
@@ -90,31 +124,33 @@ public class ServerAccess{
 
         BufferedImage image;
 
-        synchronized(IMAGE_CACHE){
+        // atomic get and remove
+        synchronized(CACHE_LOCK){
             SoftReference<BufferedImage> ref = IMAGE_CACHE.get(key);
             if(ref == null) return null;
 
-            Object referent = ref.get();
-            if(referent == null){
+            image = ref.get();
+            if(image == null){
                 IMAGE_CACHE.remove(key);
-                return null;
             }
-
-            image = (BufferedImage) referent;
         }
 
         return image;
     }
 
     /**
-     * 画像キャッシュに登録する。
+     * キャッシュに画像を登録する。
+     *
+     * <p>キーは画像URL文字列。
+     *
      * @param key キー
      * @param image キャッシュしたい画像。
      */
     private static void putImageCache(String key, BufferedImage image){
         if(key == null || image == null) return;
 
-        synchronized(IMAGE_CACHE){
+        // atomic get and put
+        synchronized(CACHE_LOCK){
             if(getImageCache(key) != null) return;
             SoftReference<BufferedImage> ref =
                     new SoftReference<>(image);
@@ -124,8 +160,10 @@ public class ServerAccess{
         return;
     }
 
+
     /**
-     * HTTP-Proxyを返す。
+     * HTTP通信に使われるProxyを返す。
+     *
      * @return HTTP-Proxy
      */
     public Proxy getProxy(){
@@ -133,7 +171,8 @@ public class ServerAccess{
     }
 
     /**
-     * HTTP-Proxyを設定する。
+     * HTTP通信に使われるProxyを設定する。
+     *
      * @param proxy HTTP-Proxy。nullならProxyなしと解釈される。
      */
     public void setProxy(Proxy proxy){
@@ -144,6 +183,7 @@ public class ServerAccess{
 
     /**
      * 国のベースURLを返す。
+     *
      * @return ベースURL
      */
     public URL getBaseURL(){
@@ -152,7 +192,8 @@ public class ServerAccess{
 
     /**
      * 与えられたクエリーとCGIのURLから新たにURLを合成する。
-     * @param query クエリー
+     *
+     * @param query ?から始まるクエリー
      * @return 新たなURL
      */
     protected URL getQueryURL(String query){
@@ -171,7 +212,32 @@ public class ServerAccess{
     }
 
     /**
-     * エンコーディングされた入力ストリームから文字列を生成する。
+     * 指定された村の最新PeriodのHTMLデータのURLを取得する。
+     *
+     * @param village 村
+     * @return URL
+     */
+    public URL getVillageURL(Village village){
+        String query = village.getCGIQuery();
+        URL url = getQueryURL(query);
+        return url;
+    }
+
+    /**
+     * 指定されたPeriodのHTMLデータのURLを取得する。
+     *
+     * @param period 日
+     * @return URL
+     */
+    public URL getPeriodURL(Period period){
+        String query = period.getCGIQuery();
+        URL url = getQueryURL(query);
+        return url;
+    }
+
+    /**
+     * エンコーディングされた入力ストリームからHTML文字列を受信する。
+     *
      * @param istream 入力ストリーム
      * @return 文字列
      * @throws java.io.IOException 入出力エラー(おそらくネットワーク関連)
@@ -180,12 +246,12 @@ public class ServerAccess{
             throws IOException{
         DecodeNotifier decoder;
         ContentBuilder builder;
-        if(this.charset.name().equalsIgnoreCase("Shift_JIS")){
+        if(this.isSJIS){
             decoder = new SjisNotifier();
-            builder = new ContentBuilderSJ(200 * 1024);
-        }else if(this.charset.name().equalsIgnoreCase("UTF-8")){
+            builder = new ContentBuilderSJ(BUFLEN_CONTENT);
+        }else if(this.isUTF8){
             decoder = new DecodeNotifier(this.charset.newDecoder());
-            builder = new ContentBuilder(200 * 1024);
+            builder = new ContentBuilder(BUFLEN_CONTENT);
         }else{
             assert false;
             return null;
@@ -205,20 +271,8 @@ public class ServerAccess{
     }
 
     /**
-     * 与えられたクエリーを用いてHTMLデータを取得する。
-     * @param query HTTP-GET クエリー
-     * @return HTMLデータ
-     * @throws java.io.IOException ネットワークエラー
-     */
-    protected HtmlSequence downloadHTML(String query)
-            throws IOException{
-        URL url = getQueryURL(query);
-        HtmlSequence result = downloadHTML(url);
-        return result;
-    }
-
-    /**
      * 与えられたURLを用いてHTMLデータを取得する。
+     *
      * @param url URL
      * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
@@ -251,10 +305,11 @@ public class ServerAccess{
             return null;
         }
 
-        InputStream stream = TallyInputStream.getInputStream(connection);
-        DecodedContent html = downloadHTMLStream(stream);
+        DecodedContent html;
+        try(InputStream is = TallyInputStream.getInputStream(connection)){
+            html = downloadHTMLStream(is);
+        }
 
-        stream.close();
         connection.disconnect();
 
         HtmlSequence hseq = new HtmlSequence(url, datems, html);
@@ -263,97 +318,22 @@ public class ServerAccess{
     }
 
     /**
-     * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。
-     * @param url 画像URL文字列
-     * @return 画像イメージ
-     * @throws java.io.IOException ネットワークエラー
-     */
-    public BufferedImage downloadImage(String url) throws IOException{
-        URL absolute;
-        try{
-            URL base = getBaseURL();
-            absolute = new URL(base, url);
-        }catch(MalformedURLException e){
-            assert false;
-            return null;
-        }
-
-        BufferedImage image;
-        image = getImageCache(absolute.toString());
-        if(image != null) return image;
-
-        HttpURLConnection connection =
-                (HttpURLConnection) absolute.openConnection(this.proxy);
-        connection.setRequestProperty("Accept", "*/*");
-        connection.setRequestProperty("User-Agent", USER_AGENT);
-        connection.setUseCaches(true);
-        connection.setInstanceFollowRedirects(true);
-        connection.setDoInput(true);
-        connection.setRequestMethod("GET");
-
-        connection.connect();
-
-        int responseCode       = connection.getResponseCode();
-        if(responseCode != HttpURLConnection.HTTP_OK){
-            String logMessage =  "イメージのダウンロードに失敗しました。";
-            logMessage += HttpUtils.formatHttpStat(connection, 0, 0);
-            LOGGER.warning(logMessage);
-            return null;
-        }
-
-        InputStream stream = TallyInputStream.getInputStream(connection);
-        image = ImageIO.read(stream);
-        stream.close();
-
-        connection.disconnect();
-
-        putImageCache(absolute.toString(), image);
-
-        return image;
-    }
-
-    /**
-     * 指定された認証情報をPOSTする。
-     * @param authData 認証情報
-     * @return 認証情報が受け入れられたらtrue
+     * 与えられたクエリーを用いてHTMLデータを取得する。
+     *
+     * @param query HTTP-GET クエリー
+     * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
      */
-    protected boolean postAuthData(String authData) throws IOException{
-        URL url = getQueryURL("");
-        HttpURLConnection connection =
-                (HttpURLConnection) url.openConnection(this.proxy);
-        connection.setRequestProperty("Accept", "*/*");
-        connection.setRequestProperty("User-Agent", USER_AGENT);
-        connection.setUseCaches(false);
-        connection.setInstanceFollowRedirects(false);
-        connection.setDoInput(true);
-        connection.setDoOutput(true);
-        connection.setRequestMethod("POST");
-
-        byte[] authBytes = authData.getBytes(ENC_POST);
-
-        OutputStream os = TallyOutputStream.getOutputStream(connection);
-        os.write(authBytes);
-        os.flush();
-        os.close();
-
-        updateLastAccess(connection);
-
-        connection.disconnect();
-
-        if( ! this.authManager.hasLoggedIn() ){
-            String logMessage =  "認証情報の送信に失敗しました。";
-            LOGGER.warning(logMessage);
-            return false;
-        }
-
-        LOGGER.info("正しく認証が行われました。");
-
-        return true;
+    protected HtmlSequence downloadHTML(String query)
+            throws IOException{
+        URL url = getQueryURL(query);
+        HtmlSequence result = downloadHTML(url);
+        return result;
     }
 
     /**
      * トップページのHTMLデータを取得する。
+     *
      * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
      */
@@ -363,6 +343,9 @@ public class ServerAccess{
 
     /**
      * 国に含まれる村一覧HTMLデータを取得する。
+     *
+     * <p>wolf国には存在しない。
+     *
      * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
      */
@@ -372,20 +355,24 @@ public class ServerAccess{
 
     /**
      * 指定された村のPeriod一覧のHTMLデータを取得する。
-     * 現在ゲーム進行中の村にも可能。
+     *
+     * <p>現在ゲーム進行中の村にも可能。
      * ※ 古国では使えないよ!
+     *
      * @param village 村
      * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
      */
     public HtmlSequence getHTMLBoneHead(Village village) throws IOException{
-        String villageID = village.getVillageID();
-        return downloadHTML("?vid=" + villageID + "&meslog=");
+        String query = village.getCGIQuery();
+        return downloadHTML(query + "&meslog=");
     }
 
     /**
      * 指定された村の最新PeriodのHTMLデータをロードする。
-     * 既にGAMEOVERの村ではPeriod一覧のHTMLデータとなる。
+     *
+     * <p>既にGAMEOVERの村ではPeriod一覧のHTMLデータとなる。
+     *
      * @param village 村
      * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
@@ -396,18 +383,8 @@ public class ServerAccess{
     }
 
     /**
-     * 指定された村の最新PeriodのHTMLデータのURLを取得する。
-     * @param village 村
-     * @return URL
-     */
-    public URL getVillageURL(Village village){
-        String villageID = village.getVillageID();
-        URL url = getQueryURL("?vid=" + villageID);
-        return url;
-    }
-
-    /**
      * 指定されたPeriodのHTMLデータをロードする。
+     *
      * @param period Period
      * @return HTMLデータ
      * @throws java.io.IOException ネットワークエラー
@@ -418,79 +395,68 @@ public class ServerAccess{
     }
 
     /**
-     * 指定されたPeriodのHTMLデータのURLを取得する。
-     * @param period 日
-     * @return URL
-     */
-    public URL getPeriodURL(Period period){
-        String query = period.getCGIQuery();
-        URL url = getQueryURL(query);
-        return url;
-    }
-
-    /**
-     * 最終アクセス時刻を更新する。
-     * @param connection HTTP接続
-     * @return リソース送信時刻
-     */
-    public long updateLastAccess(HttpURLConnection connection){
-        this.lastServerMs = connection.getDate();
-        this.lastLocalMs = System.currentTimeMillis();
-        this.lastSystemMs = System.nanoTime() / (1000 * 1000);
-        return this.lastServerMs;
-    }
-
-    /**
-     * 与えられたユーザIDとパスワードでログイン処理を行う。
-     * @param userID ユーザID
-     * @param password パスワード
-     * @return ログインに成功すればtrue
+     * 絶対または相対URLの指すパーマネントなイメージ画像をダウンロードする。
+     *
+     * @param url 画像URL文字列
+     * @return 画像イメージ
      * @throws java.io.IOException ネットワークエラー
      */
-    public final boolean login(String userID, char[] password)
-            throws IOException{
-        if(this.authManager.hasLoggedIn()){
-            return true;
-        }
-
-        String postText = AuthManager.buildLoginPostData(userID, password);
-        boolean result;
+    public BufferedImage downloadImage(String url) throws IOException{
+        URL absolute;
         try{
-            result = postAuthData(postText);
-        }catch(IOException e){
-            this.authManager.clearAuthentication();
-            throw e;
+            URL base = getBaseURL();
+            absolute = new URL(base, url);
+        }catch(MalformedURLException e){
+            assert false;
+            return null;
         }
 
-        return result;
-    }
+        String urlTxt = absolute.toString();
+        BufferedImage image;
+        image = getImageCache(urlTxt);
+        if(image != null) return image;
 
-    /**
-     * ログアウト処理を行う。
-     * @throws java.io.IOException ネットワーク入出力エラー
-     */
-    public void logout() throws IOException{
-        if( ! this.authManager.hasLoggedIn() ){
-            return;
+        HttpURLConnection connection =
+                (HttpURLConnection) absolute.openConnection(this.proxy);
+        connection.setRequestProperty("Accept", "*/*");
+        connection.setRequestProperty("User-Agent", USER_AGENT);
+        connection.setUseCaches(true);
+        connection.setInstanceFollowRedirects(true);
+        connection.setDoInput(true);
+        connection.setRequestMethod("GET");
+
+        connection.connect();
+
+        int responseCode       = connection.getResponseCode();
+        if(responseCode != HttpURLConnection.HTTP_OK){
+            String logMessage =  "イメージのダウンロードに失敗しました。";
+            logMessage += HttpUtils.formatHttpStat(connection, 0, 0);
+            LOGGER.warning(logMessage);
+            return null;
         }
 
-        try{
-            postAuthData(AuthManager.POST_LOGOUT);
-        }finally{
-            this.authManager.clearAuthentication();
+        try(InputStream is = TallyInputStream.getInputStream(connection)){
+            image = ImageIO.read(is);
         }
 
-        return;
+        connection.disconnect();
+
+        putImageCache(urlTxt, image);
+
+        return image;
     }
-    // TODO シャットダウンフックでログアウトさせようかな…
 
     /**
-     * ログイン中か否か判定する。
-     * @return ログイン中ならtrue
+     * 最終アクセス時刻を更新する。
+     *
+     * @param connection HTTP接続
+     * @return リソース送信時刻
      */
-    public boolean hasLoggedIn(){
-        boolean result = this.authManager.hasLoggedIn();
-        return result;
+    public long updateLastAccess(HttpURLConnection connection){
+        this.lastServerMs = connection.getDate();
+        this.lastLocalMs = System.currentTimeMillis();
+        this.lastSystemMs = System.nanoTime() / (1000 * 1000);
+        return this.lastServerMs;
     }
 
 }