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;
/**
* 国ごとの人狼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;
/**
* 人狼BBSサーバとの接続管理を生成する。
- * この時点ではまだ通信は行われない。
+ *
+ * <p>この時点ではまだ通信は行われない。
+ *
* @param baseURL 国別のベースURL
* @param charset 国のCharset
* @throws IllegalArgumentException 不正なURL
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。
*/
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);
return;
}
+
/**
- * HTTP-Proxyを返す。
+ * HTTP通信に使われるProxyを返す。
+ *
* @return HTTP-Proxy
*/
public Proxy getProxy(){
}
/**
- * HTTP-Proxyを設定する。
+ * HTTP通信に使われるProxyを設定する。
+ *
* @param proxy HTTP-Proxy。nullならProxyなしと解釈される。
*/
public void setProxy(Proxy proxy){
/**
* 国のベースURLを返す。
+ *
* @return ベースURL
*/
public URL getBaseURL(){
/**
* 与えられたクエリーとCGIのURLから新たにURLを合成する。
- * @param query クエリー
+ *
+ * @param query ?から始まるクエリー
* @return 新たなURL
*/
protected URL getQueryURL(String query){
}
/**
- * エンコーディングされた入力ストリームから文字列を生成する。
+ * 指定された村の最新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 入出力エラー(おそらくネットワーク関連)
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;
}
/**
- * 与えられたクエリーを用いて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 ネットワークエラー
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);
}
/**
- * 絶対または相対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 ネットワークエラー
*/
/**
* 国に含まれる村一覧HTMLデータを取得する。
+ *
+ * <p>wolf国には存在しない。
+ *
* @return HTMLデータ
* @throws java.io.IOException ネットワークエラー
*/
/**
* 指定された村の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 ネットワークエラー
}
/**
- * 指定された村の最新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 ネットワークエラー
}
/**
- * 指定された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;
}
}