Androidアプリで,HTTP通信のPOSTリクエストをする汎用クラス (文字化け無し+非同期タスク)
重要なお知らせ:
この記事で公開した情報は,AndroidのMVCフレームワーク「Android-MVC」の機能の一部として取り込まれました。
より正確な設計情報や,動作可能な全ソースコードを閲覧したい場合,「Android-MVC」の公式ページより技術情報を参照してください。
AndroidのMVCフレームワーク - 「Android-MVC」
http://code.google.com/p/android-mvc-...
Androidアプリ開発時に,HTTP通信でWebサーバに対しPOSTリクエストをするサンプルコード。
ここでPOSTパラメータは,UTF8などのエンコードで日本語のマルチバイト文字列を含むとする。
この通信処理を,文字化け無しで正しく扱う。
送信するリクエストヘッダも,サーバから受信するレスポンス内容も,指定した文字コード通りに適切に扱う。
また,通信に時間がかかる場合,その処理を非同期タスクにして
UI上ではプログレスバーなどを表示する必要がある。
そのサンプルも扱う。
(1)Webサーバ側の準備
Windowsマシン上で,Apacheのhtdocsの最上位に下記のPHPファイルを設置。
android_post_test.php(UTF-8で保存)
<?php echo "レスポンスのテスト。\npost_1 = " . $_POST["post_1"] . "\npost_2 = " . $_POST["post_2"] ; ?>
受け取ったPOSTパラメータを表示するだけの,ごく簡単なCGIプログラム。
Webアプリとしての利用は想定していないので,htmlspecialchars() によるエスケープはしていない。
ブラウザで下記のURLにアクセス。
http://localhost/android_post_test.php
POSTパラメータが何もないので,
「レスポンスのテスト。 post_1 = post_2 = 」
と表示されるはず。
PHPプログラミングの基礎
http://www.atmarkit.co.jp/flinux/rens...
- $_POSTや$_GETはスーパーグローバル変数と呼ばれ、PHPであらかじめ用意された連想配列
なお,ApacheはXAMPPなんかで簡単にインストールできる。
Windows Vista上に XAMPP と XOOPS Cube をインストールし,サイトをカスタマイズする手順
http://language-and-engineering.hatenablog.jp/entry/20110730/p1
(2)エミュレータからの接続テスト
Androidのエミュレータの中から,上記のサーバプログラムにアクセスする。
自分で作ったアプリからのアクセスを試す前に,まずエミュレータ内のブラウザからアクセスしてみるのが手っ取り早い。
注意点として,エミュレータ内から,エミュレータの母機となるマシンにアクセスする際は「localhost」ではなく,IPアドレスとして「10.0.2.2」を用いる。
Androidのエミュレータから起動元のPC(localhost)へアクセスするには
http://blog.livedoor.jp/sylc/archives...
- エミュレータ環境からではエミュレータ自体が「localhost」
- なので、自マシンには仮想的なネットワークアドレス「10.0.2.2」が割り振られている。エミュレータ内のブラウザからホスト側のサーバにアクセス可能。127.0.0.1のかわりになる。
Androidエミュレータ内でブラウザを起動し,下記のURLにアクセス。
http://10.0.2.2/android_post_test.php
先ほどと同じように表示されるはず。
(3)Androidクライアントアプリ(シンプルな同期処理バージョン)
自作アプリでのサーバアクセス方法。
非同期処理を使わずに,シンプルなサンプルコードとする。
アクティビティ:HttptestActivity.java
package com.example; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; /** * 同期通信でPOSTリクエストをするサンプルコード * */ public class HttptestActivity extends Activity implements OnClickListener { private Button btn = null; private TextView tv = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btn = (Button)findViewById(R.id.btn1); tv = (TextView)findViewById(R.id.tv1); btn.setOnClickListener(this); } @Override public void onClick(View v) { // ボタン押下時 if( v == btn ) { exec_post(); } } // POST通信を実行(AsyncTaskによる非同期処理を使わないバージョン) private void exec_post() { Log.d("posttest", "postします"); String ret = ""; // URL URI url = null; try { url = new URI( "http://10.0.2.2/android_post_test.php" ); Log.d("posttest", "URLはOK"); } catch (URISyntaxException e) { e.printStackTrace(); ret = e.toString(); } // POSTパラメータ付きでPOSTリクエストを構築 HttpPost request = new HttpPost( url ); List<NameValuePair> post_params = new ArrayList<NameValuePair>(); post_params.add(new BasicNameValuePair("post_1", "ユーザID")); post_params.add(new BasicNameValuePair("post_2", "パスワード")); try { // 送信パラメータのエンコードを指定 request.setEntity(new UrlEncodedFormEntity(post_params, "UTF-8")); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } // POSTリクエストを実行 DefaultHttpClient httpClient = new DefaultHttpClient(); try { Log.d("posttest", "POST開始"); ret = httpClient.execute(request, new ResponseHandler<String>() { @Override public String handleResponse(HttpResponse response) throws IOException { Log.d( "posttest", "レスポンスコード:" + response.getStatusLine().getStatusCode() ); // 正常に受信できた場合は200 switch (response.getStatusLine().getStatusCode()) { case HttpStatus.SC_OK: Log.d("posttest", "レスポンス取得に成功"); // レスポンスデータをエンコード済みの文字列として取得する return EntityUtils.toString(response.getEntity(), "UTF-8"); case HttpStatus.SC_NOT_FOUND: Log.d("posttest", "データが存在しない"); return null; default: Log.d("posttest", "通信エラー"); return null; } } }); } catch (IOException e) { Log.d("posttest", "通信に失敗:" + e.toString()); } finally { // shutdownすると通信できなくなる httpClient.getConnectionManager().shutdown(); } // 受信結果をUIに表示 tv.setText( ret ); } }
レイアウト定義ファイル:main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <!-- 通信を開始 --> <Button android:id="@+id/btn1" android:layout_height="wrap_content" android:layout_width="300dp" android:layout_marginTop="30dp" android:layout_marginBottom="30dp" android:padding="20dp" android:textSize="22dp" android:text="@string/btn_label" ></Button> <!-- 通信の結果を表示 --> <TextView android:id="@+id/tv1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="10dp" android:textSize="15dp" android:text="@string/temp_text" /> </LinearLayout>
マニフェストのmanifestタグ直下に,下記の要素を追加:
<uses-permission android:name="android.permission.INTERNET" />
これでEclipseからビルドして,エミュレータで実行。
ボタンを押下。
少しして(数秒以内に),下記のような文字列が画面に表示されるはずだ。
レスポンスのテスト。
post_1 = ユーザID
post_2 = パスワード
マルチバイトの日本語文字列も,文字化けせずにPOSTして返ってくるのを取得できた。
※なお,「まるいち」など環境依存文字を含む場合も大丈夫である。
だが,はてなダイアリーのDBの文字コードがEUCなので,このエントリ上で「まるいち」を記載する事ができない。残念。
以下は解説。
まず,DefaultHttpClientを使ったHTTP通信について。
このクラスを選んだ理由は,POSTリクエストが簡潔に書けるから。
かわりにHttpURLConnectionを使う手もあるが,このクラスは異常系の設計が微妙におかしいので使わない事に。
AndroidでPOST通信
http://blog.5ive.info/archives/1040
- POSTする時はorg.apache.http系のクラスを使うと割と簡潔に書ける
Android:HTTP通信でGET, POSTする
http://319ring.net/blog/archives/1667
- API Level 8からはAndroidHttpClientというのがある
DefaultHttpClientでHTTP通信を行う(GET)
http://blog.global-eng.co.jp/android/...
java.net.HttpURLConnection が設計ミスを起こしている件
http://d.hatena.ne.jp/succeed/2010020...
- javaのAPIなんだけど,サーバがエラーコードを返したときに,なぜかIOExceptionを投げてしまうのでInputStreamが作れない。というクラスライブラリの設計ミス
POST通信時に,レスポンス情報を正しく文字エンコーディングするのは比較的容易。
単なるStringの扱いなので・・・。
しかし,POSTリクエストのパラメータ(ヘッダ情報)を文字化けさせずに正しくエンコードする部分は,つまずきやすい。
HTTP Post通信での文字化け
http://groups.google.com/group/androi...
- Androidから送信したマルチバイト文字のみが文字化けしてデータベースに格納されてしまいます
- UrlEncodedFormEntityのコンストラクタに、第二引数としてサーバ側が期待する文字コードを指定してみてください。
AndroidでHTTP POST
http://shokai.org/blog/archives/5509
- httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
なお,改行コードとして「\r\n」を使うと,Androidアプリの画面上では表示がおかしくなる。
「\n」を使うこと。
あと,ログは多めに出してある。適宜削除すること。
(4)Androidクライアントアプリ(安全な非同期処理バージョン)
前項の方法だと,サーバ接続に5秒以上の時間がかかった場合,アプリが落ちる。
Androidアプリ開発者にとっての最大の恥である「ANRダイアログ」が出てしまうのだ。
ANR (“Application Not Responding”)
http://y-anz-m.blogspot.com/2010/10/a...
- ANR はいつ起こる?
- main thread (“event thread” / “UI thread”) が 5 秒以上反応しない
- 典型的には... ネットワーク操作 (ダウンロードなど) を main thread で行っている
Android 開発ガイド / ベストプラクティス / 7. レスポンスのための設計 / ANR を避ける方法
http://www.techdoctranslator.com/andr...
- Android アプリケーションは通常はまったくのシングルスレッド ( つまりメインスレッド ) で動いています。これは、処理が完了するのに時間のかかるメインスレッドでなんらかの動作をしているアプリケーションが、ANR ダイアログの引き金となってしまう、ということを意味
- ネットワークやデータベースのような潜在的に実行に長い時間を要する操作や、ビットマップのリサイズのようにコンピュータ的にも負担となる計算は、子のスレッドで動作させる必要があります・・・アプリケーションをこの方法で設計することにより、メインスレッドは入力に対する応答性を維持することができ、それにより 5 秒の入力イベントタイムアウトが原因で起こる ANR ダイアログを回避することができます
したがって,前項の設計はまずい。
非同期タスクを生成し,そのタスクにHTTP通信処理を任せるべきだ。
別スレッドでの処理が終わるまでの間,UI側はプログレスバーとかで操作をブロックしておけば安心。
ここでは,前項のHTTP通信をAsyncTask内に記述し,非同期処理させる。
その処理中は「通信中です・・・」というダイアログを出して,通信が終わったら自動的にダイアログが消えるようにする。
そのような処理のために汎用的に使う事ができる,便利クラスを作ろう。
そして,使いまわして他の各種UIからも呼び出せるようにする。
まず,前項のアクティビティクラスを改変。
アクティビティ:HttptestActivity.java
package com.example; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; /** * 非同期通信でPOSTリクエストをする * */ public class HttptestActivity extends Activity implements OnClickListener { private Button btn = null; private TextView tv = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btn = (Button)findViewById(R.id.btn1); tv = (TextView)findViewById(R.id.tv1); btn.setOnClickListener(this); } @Override public void onClick(View v) { // ボタン押下時 if( v == btn ) { exec_post(); } } // POST通信を実行(AsyncTaskによる非同期処理を使うバージョン) private void exec_post() { // 非同期タスクを定義 HttpPostTask task = new HttpPostTask( this, "http://10.0.2.2/android_post_test.php", // タスク完了時に呼ばれるUIのハンドラ new HttpPostHandler(){ @Override public void onPostCompleted(String response) { // 受信結果をUIに表示 tv.setText( response ); } @Override public void onPostFailed(String response) { tv.setText( response ); Toast.makeText( getApplicationContext(), "エラーが発生しました。", Toast.LENGTH_LONG ).show(); } } ); task.addPostParam( "post_1", "ユーザID" ); task.addPostParam( "post_2", "パスワード" ); // タスクを開始 task.execute(); } }
そして,非同期通信のタスククラスを定義。
HttpPostTask.java:
package com.example; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import android.app.Activity; import android.app.ProgressDialog; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; /** * HTTP通信でPOSTリクエストを投げる処理を非同期で行うタスク。 * */ public class HttpPostTask extends AsyncTask<Void, Void, Void> { // 設定事項 private String request_encoding = "UTF-8"; private String response_encoding = "UTF-8"; // 初期化事項 private Activity parent_activity = null; private String post_url = null; private Handler ui_handler = null; private List<NameValuePair> post_params = null; // 処理中に使うメンバ private ResponseHandler<Void> response_handler = null; private String http_err_msg = null; private String http_ret_msg = null; private ProgressDialog dialog = null; // 生成時 public HttpPostTask( Activity parent_activity, String post_url, Handler ui_handler ) { // 初期化 this.parent_activity = parent_activity; this.post_url = post_url; this.ui_handler = ui_handler; // 送信パラメータは初期化せず,new後にsetさせる post_params = new ArrayList<NameValuePair>(); } /* --------------------- POSTパラメータ --------------------- */ // 追加 public void addPostParam( String post_name, String post_value ) { post_params.add(new BasicNameValuePair( post_name, post_value )); } /* --------------------- 処理本体 --------------------- */ // タスク開始時 protected void onPreExecute() { // ダイアログを表示 dialog = new ProgressDialog( parent_activity ); dialog.setMessage("通信中・・・"); dialog.show(); // レスポンスハンドラを生成 response_handler = new ResponseHandler<Void>() { // HTTPレスポンスから,受信文字列をエンコードして文字列として返す @Override public Void handleResponse(HttpResponse response) throws IOException { Log.d( "posttest", "レスポンスコード:" + response.getStatusLine().getStatusCode() ); // 正常に受信できた場合は200 switch (response.getStatusLine().getStatusCode()) { case HttpStatus.SC_OK: Log.d("posttest", "レスポンス取得に成功"); // レスポンスデータをエンコード済みの文字列として取得する。 // ※IOExceptionの可能性あり HttpPostTask.this.http_ret_msg = EntityUtils.toString( response.getEntity(), HttpPostTask.this.response_encoding ); break; case HttpStatus.SC_NOT_FOUND: // 404 Log.d("posttest", "存在しない"); HttpPostTask.this.http_err_msg = "404 Not Found"; break; default: Log.d("posttest", "通信エラー"); HttpPostTask.this.http_err_msg = "通信エラーが発生"; } return null; } }; } // メイン処理 protected Void doInBackground(Void... unused) { Log.d("posttest", "postします"); // URL URI url = null; try { url = new URI( post_url ); Log.d("posttest", "URLはOK"); } catch (URISyntaxException e) { e.printStackTrace(); http_err_msg = "不正なURL"; return null; } // POSTパラメータ付きでPOSTリクエストを構築 HttpPost request = new HttpPost( url ); try { // 送信パラメータのエンコードを指定 request.setEntity(new UrlEncodedFormEntity(post_params, request_encoding)); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); http_err_msg = "不正な文字コード"; return null; } // POSTリクエストを実行 DefaultHttpClient httpClient = new DefaultHttpClient(); Log.d("posttest", "POST開始"); try { httpClient.execute(request, response_handler); } catch (ClientProtocolException e) { e.printStackTrace(); http_err_msg = "プロトコルのエラー"; } catch (IOException e) { e.printStackTrace(); http_err_msg = "IOエラー"; } // shutdownすると通信できなくなる httpClient.getConnectionManager().shutdown(); return null; } // タスク終了時 protected void onPostExecute(Void unused) { // ダイアログを消す dialog.dismiss(); // 受信結果をUIに渡すためにまとめる Message message = new Message(); Bundle bundle = new Bundle(); if (http_err_msg != null) { // エラー発生時 bundle.putBoolean("http_post_success", false); bundle.putString("http_response", http_err_msg); } else { // 通信成功時 bundle.putBoolean("http_post_success", true); bundle.putString("http_response", http_ret_msg); } message.setData(bundle); // 受信結果に基づいてUI操作させる ui_handler.sendMessage(message); } }
そして,このタスククラスにフィットするような,UI側での記述を楽にしてくれるハンドラクラスを定義。
HttpPostHandler.java
package com.example; import android.os.Handler; import android.os.Message; /** * HTTP通信のPOSTタスク完了時に,通信の成否に応じて,受信した通信内容をUI上で取り扱うための抽象クラス。 * */ public abstract class HttpPostHandler extends Handler { // このメソッドは隠ぺいし,Messageなどの低レベルオブジェクトを // 直接扱わないでもよいようにさせる public void handleMessage(Message msg) { boolean isPostSuccess = msg.getData().getBoolean("http_post_success"); String http_response = msg.getData().get("http_response").toString(); if( isPostSuccess ) { onPostCompleted( http_response ); } else { onPostFailed( http_response ); } } // 下記をoverrideさせずに抽象化した理由は,本クラス指定時に // 「実装されていないメソッドの追加」でメソッドスタブを楽に自動生成させるため。 // また,異常系の処理フローも真剣にコーディングさせるため。 // 通信成功時の処理を記述させる。 // 名前をonPostSuccessではなくonPostCompletedにした理由は, // メソッド自動生成時に正常系が先頭に来るようにするため。 public abstract void onPostCompleted( String response ); // 通信失敗時の処理を記述させる public abstract void onPostFailed( String response ); }
他のリソースは前項と同じ。
このプロジェクトをエミュレータで実行して,ボタンを押下すると,サーバから得られる処理の結果はさっきと同じだ。
だがしかし,先ほどとは比べ物にならないほど良いユーザビリティが味わえる。
「通信中です」というダイアログ上で,クルクル・・・と回るアイコンを見ると,
まるでWebの世界にAjaxが初めて現れたころに感じた感動と同じような感覚を味わうのではないか。
また,エラー処理もしている。
Apache上のphpのファイル名を変更してから,アプリ側で再度通信を試みてみよう。
「404 Not Found」と画面に表示され,トーストで「エラーが発生しました。」と出る。
さらに,この通信用のクラスが汎用的に設計されている点にも注目。
- アクセスすべきURL
- POSTパラメータ
- レスポンスを得た際のUI操作処理
- 異常系の処理
などは,いずれもアクティビティ側のコードに記載できるのだ。
しかも,低レイヤのオブジェクトは無視して,プログラミングしやすいように配慮されている。
だからこのクラスはどんどん使いまわせる。
以下は解説。
AsyncTaskの定義方法や,プログレスバーとの連携方法は,下記サイトが参考になる。
AsyncTaskでユーザビリティを向上させる(HTTP通信での画像取得処理)
http://labs.techfirm.co.jp/android/ch...
- AsyncTask継承クラス定義時のジェネリクスの3つの型は,順にParams、Progress、Resultとして扱われ、それぞれ:
- バックグラウンド処理を開始するインスタンスメソッドexecuteの引数の型
- 進捗度合を表示する時に利用したい型
- バックグラウンド処理完了時に受け取る型
- ここではString, Void, Bitmap
【Android】AsyncTaskを使ってみる
http://team-pag.interprism.co.jp/memb...
- スリープなどの時間がかかる重い処理をAsynkTaskに任せ,UI側ではプログレスバーで進捗を%で表示するサンプルコード
- AsyncTaskのジェネリクス型パラメータはObject, Integer, Boolean
AndroidでAsyncTaskを使ったバックグラウンド処理
http://blog.livedoor.jp/grs_man/archi...
- 同上。AsyncTaskのジェネリクス型パラメータはString, Integer, Long
Grab a URL Source with ProgressDialog and AsyncTask
http://www.androidsnippets.com/grab-a...
- タスク内容はHTTPのGET通信。ダイアログは出すが,進捗は表示しない
- 型パラメータはString, Void, Void
Android(開発)/HttpClientで接続する(単純テキストGET非同期編)
http://yakinikunotare.boo.jp/orebase/...
- 同上。型パラメータはString, String, String
「UIを操作するための処理」をAsyncTaskに渡す際,Handlerを使っている。
(こういう時に,Javaにもクロージャがあればいいのにと感じるのだが・・・)
タスククラス側で,ハンドラーにpost(new Runnable())すれば,タスククラス内でハンドラーの挙動を記述できる。
でも,UI操作の具体的な内容は,タスククラスの呼び出し元のUIクラスが一番よく知っているはずだ。
だから,AsyncTask内ではHandlerの挙動はブラックボックスにしてある。
結び
Androidアプリにおいて,ユーザビリティを向上させ,ANRの発生を極力防ぐためには,どうしても非同期タスクに頻繁に頼ることになる。
確かにそれはセオリーだ。
しかし,非同期タスクを多用すると,ソースコードから処理フローが追いづらくなる。
コールバックベースのスタイルでコーディングするために,リスナとかハンドラを,バラバラに別個のオブジェクトとして細分化して定義しなければならない。
そうすると,処理の流れに沿ったコーディングができない。
「ソースコードが設計書になってくれない」のだ。
もちろん,今回はそういう問題を避けるためにHandlerを巧妙に設計し,
必要になった時点で匿名クラスの実装を記述させることによって,
可能な限りコールバックの流れが追いやすいように工夫したのは事実だ。
とはいえ,JavaScriptで「function(){}」を渡せば済むのと比べると,やはりコードが冗長だ。
結局,Javaの言語仕様としてクロージャが欲しい,という事に尽きる。
もし,
「ソースコードそのものが,処理定義書・外部設計書としてのドキュメントの役割を果たしてくれる」
ようなAndroidコードが書ければ,大いに望ましいのだが。
こういうのが,Javaをはじめとする静的言語と,RubyやJavaScriptなどの動的言語とで大きく違う点だ。
Rubyでは「考えた通りの順番でコーディングすればその通り動く」。(アンディ・ハントもそう言っていた。)
またjQueryのように,本来はコールバックの連鎖であるはずの一群の処理を,美しいメソッドチェインで記述する事によって,処理仕様を明示的に示す。という事も,動的言語ならやりやすい。
もちろん両者とも一長一短だが。
とにかく,Androidアプリ開発においてもっとスマートに
「やりたい事のDSL」を,流れるように記述できるようなAPIがあればいいんだが。
それが,本ブログで繰り返し提唱している「Android on Rails」の到来なのだ。
この願いは,ぜいたく過ぎるだろうか。
関連する記事:
ネットワークスペシャリスト試験を,Webだけで独学合格する対策リンク集。過去問(午前午後)解答解説や教科書・オンライン参考書
http://language-and-engineering.hatenablog.jp/entry/20140922/NetworkSpecialis...
ネットワーク図の書き方 (物理/論理構成図の作成手順と,Excelで使えるアイコン素材のリンク集)
http://language-and-engineering.hatenablog.jp/entry/20110916/p1
ネットワーク接続が切れたら,自動的に再接続してくれるバッチ (WiFiの電波が弱くても,自動で無線LANにつなぎなおす)
http://language-and-engineering.hatenablog.jp/entry/20140918/BATWifiNetworkCo...
ネットワーク技術の業務スキルレベル 判別表 (5段階)
http://language-and-engineering.hatenablog.jp/entry/20110827/p1
VMWare上のLinuxで,Apacheに自己署名のSSL通信をさせるための手順 (オレオレ証明書で,仮想マシン上のWebサーバにHTTPS接続)
http://language-and-engineering.hatenablog.jp/entry/20120627/p1