HTTP/2とTLSv1.3(draft-20)をnginx+OpenSSLで対応させる

せっかくなので、当サイトはhttp2に正式対応しました。

昔やったはずじゃ・・・と思っていたのですが、

http/2と似て非なるほぼ同じSPDYの対応でした。

SPDYはいつの間にやらchromeではhttp/2扱いされなくなり(googleが作ったのに)、chrome 51から非対応となってしまいました。

ということでこのブログもhttp1.1に戻っていたので、正式にhttp/2に対応しました。
この辺は別の話なのでこの記事が詳しいです。

EC2+nginxでhttp2対応できたとおもったらできてなかった話。(解決します)

ついでに、当サイトはTLSv1.3(draft-20)に対応しました(多分)。

なお、言っておきますが 現状のブラウザでは見れません。

新しくブラウザが出たとしても見れるかもわかりません。

正直まだstandardになってないですし、仕様は変更されるでしょうし、現状ここでの対応はほぼ無意味です。

ただ、TLSv1.3が正式対応になりOpenSSL1.1.1が正式リリースした時はこの記事は役立つと思います。多分。

OpenSSLのTLS1.3実装に関しては公式が使用方法を書いています。これがとても詳しいです。
Using TLS1.3 With OpenSSL

コネクションの方法など難しいところは置いておくとして、
実際に使用するところで気になるのは暗号化スイートがTLS1.3専用のものが用意されています。

  • TLS13-AES-256-GCM-SHA384
  • TLS13-CHACHA20-POLY1305-SHA256
  • TLS13-AES-128-GCM-SHA256
  • TLS13-AES-128-CCM-8-SHA256
  • TLS13-AES-128-CCM-SHA256

OpenSSLの現在の実装でTLS1.3はこの5つしかサポートされていません。

これ以外を使用してTLSv1.3認証を行おうとすると、当然ですが認証に失敗します。

そのためTLS1.2以下と併用するためにはTLS1.3の時だけ上のスイートを優先する必要があります。

nginx+OpenSSLでのTLSv1.3対応

nginxのmailline versionの最新版1.13.0にて、"ssl_protocols" でTLSv1.3を選択できるようになりました。

*) Feature: the "TLSv1.3" parameter of the "ssl_protocols" directive.

そのため、おなじみの構成であるnginx+OpenSSLでTLSv1.3を使えるようになります(厳密にはなる予定です)。

使用するnginxは1.13.0、OpenSSLは1.1.1-dev(master)です。

OpenSSLはTLSv1.3が正式になったら1.1.1をリリースすると言っていますので、実際に正式になった時もこの設定で動くと思います。

OpenSSL1.1.1(dev)のインストール

リポジトリなどではなくgitからmasterを落としてビルドします。gccなどが必要です。

後、libssl.soが無いと怒られたりしたので/usr/local/lib64/に動的リンクを貼ったりpathを通したりして動きました。

cd /usr/local/src/
git clone https://github.com/openssl/openssl
cd openssl
./config enable-tls1_3
make
make test
make install

/usr/local/bin/openssl version
OpenSSL 1.1.1-dev  xx XXX xxxx

nginx 1.13.0のインストール

こちらはリポジトリはあるのですが、OpenSSLを組み込む関係でこちらもソースコードからビルドが必要です。

このやり方だとyumなどで本体に入っているnginxは削除しました。

configureのオプションは各自吟味してください。

cd /usr/local/src/
wget http://nginx.org/download/nginx-1.13.0.tar.gz
tar zxvf nginx-1.13.0.tar.gz
cd nginx-1.13.0
./configure --prefix=/etc/nginx --sbin-path=/usr/local/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-http_v2_module --with-openssl=../openssl/ --with-openssl-opt=enable-tls1_3
make
make install

ここで重要なのは--with-http_v2_module --with-openssl=../openssl/ --with-openssl-opt=enable-tls1_3です。
–with-opensslは先ほどのopensslのソースコードのpathです。nginxにバンドルします。

–with-http_v2_moduleはhttp/2対応のため、–with-openssl-opt=enable-tls1_3も設定しないと動きませんでした。

なので使うだけならOpenSSL1.1.1(dev)をmake installする必要ないのですが、openssl s_clientを使用するために使います。

あとはpathを通して、nginx -V で確認します。うまくいけば下のようになっているはずです。

nginx version: nginx/1.13.0
built by gcc <略>
built with OpenSSL 1.1.1-dev  xx XXX xxxx
TLS SNI support enabled
configure arguments: <略> --with-http_v2_module --with-openssl=../openssl/ --with-openssl-opt=enable-tls1_3

nginx設定

既にSSL設定が終わっているなら、このあたりの設定を変更するとOKです。再起動も忘れずに。

ssl.conf

#TLSv1.3用設定
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:ECDHE:!COMPLEMENTOFDEFAULT';

#http2用設定
listen 443 ssl http2

SSLv1.3の接続確認

最初に言ったようにブラウザだと確認できませんでした・・・draft-18のものはchromeのchrome://flags/でTLS1.3を指定すると見れます。

https://tls13.crypto.mozilla.org/

このサイトを見るとdraft-18対応済みのchrome/firefoxでは正常に確認でき、TLS1.3と表示されているのがわかります。

対応していないとchromeでは「サポートされていないプロトコルが使用されています」というレアな画面が出ます。

OpenSSLのSSL/TLS client機能で接続確認します。1.1.1は-tls1_3オプションでTLSv1.3で接続しに行きます。
一部省略。

>openssl s_client -connect blog.potproject.net:443 -tls1_3
CONNECTED(00000003)
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify error:num=20:unable to get local issuer certificate
---
Certificate chain
 0 s:CN = potproject.net
   i:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
 1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
   i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
Server certificate

subject=CN = potproject.net

issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3

---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 3026 bytes and written 270 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.3, Cipher is TLS13-AES-128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS13-AES-128-GCM-SHA256
    Session-ID:
    Session-ID-ctx:
    Master-Key: <master-key>
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: <StartTime>
    Timeout   : 7200 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
    Extended master secret: no
---

これで正しいのかイマイチわかりませんが、TLS1.3認証は出来ていることが確認できます。

TLSv1.3の正式に決まるまでもう日は近いと思うので、その時にすぐに対応できればいいな・・・

Facebook PHP SDK v3.2.3でGraph API のバージョンを変更する

今回はいつもと趣向を変えた、バッドノウハウです。

Facebook PHP SDK v3.2.3は、名前の通りFacebookのGraph APIを叩くためのSDKですが、
2015年1月の時点でdeprecated(非推奨)となっております。2年前です。

非推奨というだけで今までは使用できたのですが、このSDKはGraph APIv2.2を使用しているため、利用期限が過ぎ、完全にこのままでは使えなくなりました。

使用期限:2017年3月25日

https://developers.facebook.com/docs/apps/changelog?locale=ja_JP

なので、現在推奨されているFacebook SDK for PHP (v5)を使用しましょう。

で、本来はそこで終了なのですが、実際はそうもいかない人たちもいるようで・・・
Facebook SDK for PHP (v5)はPHP5.3非対応のため、PHP5.3の場合は、このSDKを使用しているところもあります。
PHPバージョンのシェア率を見ると、PHP5.3以下はまだ 25% も存在するようです。
自分もその被害者のため、完全にバッドノウハウなわけですが、対応方法を書いておきます。

この方法で、最新バージョンのv2.8まで動作しました。しかし、完全に動作するとは思わないほうがいいです。
何時使えなくなるかもわからない綱渡り的な状態であり、応急処置みたいなものなので、早いところPHPのバージョンを上げましょう。

参考:facebook graph api not work from 2.2 to 2.3
http://stackoverflow.com/questions/42994019/facebook-graph-api-not-work-from-2-2-to-2-3

まず、バージョンを変更します。デフォルトではv2.2を使用するようになっていますが、そこを明示的に指定することで、バージョンを指定することが出来ます。
v2.8であれば/v2.8/という風にURLを変更します。v2.3にしたほうが無難だと思います。

facebook-php-sdk/src/base_facebook.php 164行目付近

  public static $DOMAIN_MAP = array(
    'api'         => 'https://api.facebook.com/v2.8/',
    'api_video'   => 'https://api-video.facebook.com/v2.8/',
    'api_read'    => 'https://api-read.facebook.com/v2.8/',
    'graph'       => 'https://graph.facebook.com/v2.8/',
    'graph_video' => 'https://graph-video.facebook.com/v2.8/',
    'www'         => 'https://www.facebook.com/v2.8/',
  );

これだけでは動きません。Graph API v2.3から、アクセストークンを取得した時の振る舞いが変更され、配列からjsonを返すように変更されたためです。
そのため、返ってきたjsonをArrayに変換することで動くようにします。
2箇所同じような部分があるので、そこも同じように変更します。両方変更しないとユーザアクセストークンなどが取得できなかったりします。

facebook-php-sdk/src/base_facebook.php 413行目
facebook-php-sdk/src/base_facebook.php 808行目

    $response_params = array();
    parse_str($access_token_response, $response_params);

この部分を以下のように変更します。

    //$response_params = array();
    $response_params = json_decode($access_token_response, true);

これでとりあえずは指定したGraph APIで動作するようになります(v2.3,v2.8以外未確認です)。
OAuthログイン、/me/、/me/account/、/feed/などは動作確認済みです。

もう一度言いますが、PHP5.4以上の方はFacebook SDK for PHP (v5)を使用しましょう。

最近のコーディングとか

potproject.net Advent Calendar 201613日目の記事です。

まだ半分か・・・。


最近のコーディングは、結局いろいろ使ってきたりしたのですが、v1.0になってそれなりに使えるようになり、
現在はv1.7まで上がりかなり安定してきたかなということで、Visual Studio Codeを使ってます。
略してVSCode。

Visual Studio Code – Code Editing. Redefined

多分、このブログを始めたより後にできたエディタじゃないですかね?それくらい新しいエディタだと思います。
Visual Studioは有名なIDEですが、別に互換性があるとかナンバリングじゃなく、全くの別物で、こちらはテキストエディタに近いです(デバッグ機能とかあるけど)
自分が一番気に入っているエディタといえば(今は)これなんで、布教活動でもしていきます。

1.Macでも使える

Visual StudioなのでMicrosoftが開発しています。でも公式サイトにもMac版が普通に提供され、使用できます。
と思ったのですが、つい最近Visual StudioもMac対応を出す予定でしたね。
でもこっちはオープンソースなので、開発当初からWindows/Mac/Linuxとマルチプラットフォーム対応で使いやすいです。
しかも一説によればWindowsよりMacの方が動作が軽いとか聞きます・・・。それもそれでどうなんだ。

2.同じ系統のエディタであるatom.ioやBracketsよりも軽い(体感)

atom.io,Brackets,VSCode
この3つはどれもWeb技術でデスクトップアプリケーションを作れるソフトウェアであるElectronを使って作成されたエディタなのですが、
全部使った中で体感的にVSCodeが一番軽いと思っています。
atomやBracketsは新規タブを開こうとするとかなり重いような印象。しっかり図ってませんが実際軽いと思います。
他にも同じように軽いというブログもありました。やはり一番後発だから、なんですかね?
electron系エディタ戦争終了のおしらせ。codeがイチオシ

3.基本はテキストエディタなのにデバッグ機能が備わっている

これが一番おすすめの理由でもあります。基本はテキストエディタで、ビルドなんかは出来ませんが、デバッグ機能だけはデフォルトで準備されています。
でも、最初から好きな言語がデバッグできるわけではなく、用途に合った拡張機能を入れる必要がありますが、
デバッグ機能が最初から想定されているおかげで、かなり見やすくデバッグが捗る。
拡張機能は色とりどりで、MicrosoftイチオシのC#やtypescriptはもちろん、一般的なc/c++、phpやjavascript(node.js)などのWebサーバ系ソフトウェアで使える言語も可能です。
コーディングに関して、デバッグ機能は必須です。php-debugをよく使ってます。

その他、最近のエディタには当たり前になってきているgit機能、わかりやすくシンプルですがあるとかなり便利な全ファイルの文字列検索など、
大型のIDEや特化したテキストエディタとは違う、これだけは揃えておきたい、という機能がちゃんと入っているエディタと思います。
まだ初版リリース(v0.1)から1年少しでもうバージョンがv1.7にまで増えているのを考えると、かなり活発にアップデートしているという点でも魅力だと思います。

以上、宣伝でした。

サーバー移管した話

potproject.net Advent Calendar 201612日目の記事です。

今日は手抜き。


ようやく、サーバの移管がほぼ完了したのでそれに関する簡単なTipsとメモです。
今回は10分くらいで書ける記事を目指しています。ついでに前の記事の宣伝です。たまには簡単に行きましょう。

20分くらいかかってしまった・・・

WordPress移行

基本的にはWordpressのデフォルト機能として備わっている インポート/エクスポート機能 で移行しました。やはりデフォルトなので一番わかりやすくかつ簡単だと思います。
デフォルト機能なのですが、プラグインが必要だったりします。
古いサーバから管理画面より「ツール」→「エクスポート」からxmlファイルとしてエクスポート、
新しいサーバでインポートで特殊でない大体の記事は問題なく動きます。
スキンやプラグインなどは反映されませんが、そこは数も少ないので手動でした。

Webサーバ構築

この部分は記事別にいろいろまとめています。おさらいです。
自分のサーバではnginx,node.js,sinatra(ruby)と3つほどWebサーバが稼働しています。
centos7+nginx+php-fpm+php7な新しい感じの環境を構築
nginxでRackアプリケーション(sinatra)を動かす

SSL(https)対応

このあたりも(ryです。
おかげさまでSSLオンリーのサイトになりました。
これからは個人サイトといえど必須だと私は思っています。自動更新も忘れずに。
Centos+nginxでLet’s encrypt(certbot)導入と自動更新まで
[nginx]Let’s EncryptでSSLのセキュリティをA+にするまで

Mysqlのダンプ取得

Mysqlのダンプはやはりphpmyadminからエクスポートするのが楽でいいですが、コマンドならmysqldump -u USERNAME -p DBNAME > OUTPUTですね。
取得したらmysql -u USERNAME -p DBNAME < OUTPUTでインポート。たまにデータベースを先に作成する必要があったりすることもあります。その場合は“create database DBNAME“`で作成。

古いサーバはまだ残っていて、放置してるとやはりお金が無駄に取れますので、早いところ解約しなきゃ・・・

[JavaScript/ECMAScript2017]async/awaitとPromiseでjavascript直列/並列処理

potproject.net Advent Calendar 201610日目の記事です。

祝!10日連続更新!


ES8にあたるECMAScript2017では、await/asyncが実装されると聞いて。黙っちゃいられねえ。

まだES2017は策定中ですが、なんかEdgeでも動くようになったらしいし(設定は必要)、多分ほぼ確定。内定状態でしょう。

https://tc39.github.io/ecma262/

しかし、やはり標準では動かないので、このコードはbabelでトランスパイルしてnode.jsで動かしてます。ご了承ください。

await/async修飾子は、元々C#で使われていた非同期処理を同期的に記述できる仕組みです。

一時期C#を使っていてユニバーサル Windows プラットフォーム (UWP) アプリを作っていた時があり、

制約がありすぎて普通にWindows formsで作りてえと思いつつその時に使用しておりました。

基本的に使い方はC#もjavascriptも同じです。しかし、当然javascriptにはTask型なんてありませんので、Promiseがそれの代用になります。

つまりは、await/asyncを使うにはPromiseから習得する必要があります。

なので、ここではPromiseに関しても軽く記載しておきます。

Promiseに関しては、非同期の処理を上手く同期的に見せたり、並列処理が出来るメソッドです。

何が違うかというと、書き方が違います。await/asyncは非同期処理を同期的に行うため、結局直列の処理になりますが、Promiseは並列処理が可能です。

今回書くコードは、Promise.allでの並列処理とawait/asyncでの直列処理の比較です。

プログラム的には、1から指定された数まで全て足して出力するだけの処理です。

arr.reduceを使った非同期処理方法とfor文を使った同期処理方法で、それぞれ処理をして表示します。

一応速度も出力します。

//繰り返し回数
const execute_count=10000000;
(async ()=>{
  //await/asyncでの直列処理
  var startTime = new Date();
  var sumtest = await reduce_sum(execute_count);
  var normalsumtest=normal_sum(execute_count);
  var endTime = new Date();
  if(sumtest===normalsumtest){
    console.log("(await/async):" + (endTime - startTime) + "ms ans:"+sumtest);
  }

  //Promise.allでの並列処理
  var startTime2 = new Date();
  var sumexecute=await sum(execute_count);
  var endTime2 = new Date();
  console.log("(Promise.all):"+(endTime2 - startTime2)+ "ms ans:"+sumexecute);
})();

async function sum(num){
  return new Promise((resolve,reject)=>{
    Promise.all([normal_sum(num), reduce_sum(num)])
    .then(function(sum_result) { 
      if(sum_result[0]===sum_result[1]){
        resolve(sum_result[0]);
      }else{
        reject();
      }
    });

  });

}

function normal_sum(num){
  var arr=[];
  for(var i=1;i<=num;i++){
      arr.push(i);
  }
  var sum = 0;
  for(var x=0;x<arr.length;x++){
    sum+=arr[x];
  }
  return sum;

}

async function reduce_sum(sum){
    return new Promise((resolve,reject)=>{
      var arr=[];
      for(var i=1;i<=sum;i++){
        arr.push(i);
      }
      return resolve(arr.reduce(function(prev, current) {
        return prev+current;
      }));
    });
}

結果はこんな感じ。

(await/async):690ms ans:50000005000000

(Promise.all):786ms ans:50000005000000

並列の方が遅い。ほぼ変わらずです。なんで?って感じですが、多分これが正しいと思います。

javascriptはご存知の通りシングルスレッドなので、並列処理だからってスレッドを増やしたりできません。

シングルスレッドということは同時に別の処理をやることはできないので、だから結局のところ疑似並列処理なだけです。

むしろ並列にしたほうが割り込みが入るので、遅くなると思います。

実際トランスパイラしてるというのもあるし、await/async/Promiseの処理時間とかもあるので上の時間は多分あてになりません。

一番の問題は、コードの見やすさです。

await/asyncを使えばメインのコードにコールバックを一切入れることが無くなるため、見やすいんじゃないかなと思います。

自分は積極的にawait/asyncは使っていきたいです。もうコールバック地獄には入りたくない・・・。

[React Native]ネイティブモジュール(Swift)を使う[iOS編]

potproject.net Advent Calendar 20169日目の記事です。


react-nativeをとりあえずすぐ実機で動かしてみる

で、とりあえず実機で動くかの確認は取れました。
で、今回は、React Nativeとネイティブコードの連携をやってみたいと思います。

React NativeはiOSやAndroidを共通のJavascriptで書いてマルチプラットフォームでしかもネイティブに動くよ!というのがウリです。

しかし、当然ながら、SwiftやObjective-Cを使うようなものも必要になります。カメラとかプッシュ機能。

まあメジャーなものは用意されてる場合もあるんですが、マイナーなものは無かったりするので、

自分で作ってね!ってことでネイティブモジュールという機能で補完できるようになってます。

Cordovaを思い出す。しかしこっちは完全ネイティブなのだ。

このあたりを参考にしました。しかしまあやっぱり例によってSwift3.0なので結構コード変えてたりもします。
React NativeでNative機能をSwiftで書いて使うには

Native Modules

Objective-Cは全く分からないのでSwiftです。でもブリッジしなくてはならないので多少はObjective-Cは避けられないんだなあこれが。
原則、React NativeはネイティブモジュールにSwiftを使うように設計されていません。生成されたプロジェクトもObjective-C用で生成されます。

なので、使うにはObjective-CとSwiftを連携させる必要があります。

swiftでモジュールを作成

まずは、モジュールを作成します。ちなみにこれだけだとまだ動きません。
端末の時間を取得してAny(Dictionary)型でコールバックを返します。

どうやらReact Nativeのjavascriptにデータを受け渡す際は必ずcallbackなのかな。
ちなみにPromiseも使えます。

ReactNativeModule.swift

import Foundation

@objc(ReactNativeModule)
class ReactNativeModule: NSObject {

  @objc func callbackMethod(_ callback: RCTResponseSenderBlock) -> Void {
    // システムのカレンダーを取得
    let cal = Calendar.current
    // 現在時刻のDateComponentsを取り出す
    var dataComps = cal.dateComponents([.year, .month, .day, .hour, .minute], from: Date())
    let object = [
      "year":dataComps.year!,
      "month":dataComps.month!,
      "day":dataComps.day!
    ]
    callback([object])
  }
}

配列で返しても大丈夫なの?と思いでしょうが、ちゃんとReact Native側はjavascript Object型で受け取ってくれます。優秀です。  
callbackMethodの第一引数が無名関数がなっているのは、このあたりの問題から。認識してくれないのです。Swift3.0での変更点となります。
Got “is not a recognized Objective-C method” when bridging Swift to React-Native – stackoverflow

Objective-Cでエクスポートする

ReactNativeModuleBridge.m

#import "RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(ReactNativeModule, NSObject)

RCT_EXTERN_METHOD(callbackMethod:(RCTResponseSenderBlock)callback)
@end

実際はこのObjective-Cが読み込まれます。これが上で書いたSwiftコードの橋渡しになります。

Objective-Cの文法は相変わらず割と理解できていない。

testreactnative-Bridging-Header.h

#import "RCTBridgeModule.h"

RCTBridgeModule.hというObjective-CのコードをSwiftで読み込むために設置。
このあたりに関してはググるといっぱい出てくるため割愛。

React Native側のソースはこんな感じ。画面の更新もしたかったのだが、結構時間掛かりそうで・・・ただログ出すだけ。

index.ios.js

import React, { Component } from 'react';
import {
  NativeModules,
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';
var nm=NativeModules.ReactNativeModule;
nm.callbackMethod(function(res){
    console.log(res);
});

これでデバッグ画面からログが確認できました。

ちゃんと{"year":2016,"month":12,"day":9}と出てきます。Any型も受け渡せるのは楽でいいですねえ。

今回、タイトルが(iOS編)です。

これで気づいた人もいるかもですが、明日はネイティブモジュール(Java)を使う[Android編]をやります。多分。

Swift3.0でjsonパラメータをHTTP POST

potproject.net Advent Calendar 2016
8日目の記事です。

Swift3.0です。本当Swift2.x時代が強すぎて検索に全く引っかかってこないのが辛い。


もう結構前になってしまいますが、iOS10が公開され、iPhone7も発売されました。

しかし、相変わらず自分的にはiOS10もiPhone7も興味ありません。

でも、残念ながらiOS開発をしているとiOS10にも対応しなきゃならなくなり、

小規模なアプリを作っていたので、swift2.3からswift3.0に書き直すことに。

しかし、swift3.0はもう2.xの互換性をかなぐり捨てていて、公式でコンバータが用意されるくらいにめっちゃ変更されてます。

まあ大体はコンバートで対処できるのですが、http通信のコードの部分でエラーが出ていました。

調べるとやっぱりここはうまく自動変換ができない模様。

Swift 3 URLSession.shared() Ambiguous reference to member ‘dataTask(with:completionHandler:) error (bug)

で、大体の人が詰まっているようだったswift3.0でHTTPリクエストを送るコードを自分のメモがてら載せときます。

つかswiftは仕様変わりすぎなんだよね・・・

コピペエンジニアには辛すぎる言語だ。ググっても2と3が混合してて辛い。

まず、swift2.3のコード。バックアップ取ってなかったんで再現になりますが。
Swift2.xだとこんな感じです。
参考:http://qiita.com/sushichop/items/ac4ae99b905ce523c2fe

// create the url-request
        let urlString = "http://httpbin.org/post"
        var request = NSMutableURLRequest(URL: NSURL(string: urlString)!)

        // set the method(HTTP-POST)
        request.HTTPMethod = "POST"
        // set the header(s)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        // set the request-body(JSON)
        var params: [String: AnyObject] = [
            "foo": "bar",
            "baz": [
                "a": 1,
                "b": 20,
                "c": 300
            ]
        ]
        request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: nil)

        // use NSURLSessionDataTask
        var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                var result = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println(result)
            } else {
                println(error)
            }
        })
        task.resume()

いろいろありますが、これはNSURLSessionDataTaskというものを使ってでHTTP POSTを送るようです。

多分今までのswiftだとこれが主流なのかな?

このNSURLSessionがswift3.0ではURLSessionになり、仕様も変わっています。

URLSession.DataTaskは、第一引数に NSMutableURLRequest型ではなくURLRequest型でないと動きません。

NSMutableURLRequestを受け渡すと、クラッシュします。

requestは明示的に型宣言してないため、コンバートが見逃したということですね。

しかもコンパイル時エラーも起きない。困りますね。

(他のブログだとこれでビルドが通らないらしい。もしかして修正されたのかも?

というか通り抜けてたならAppleさんが悪い・・・俺は悪くない

参考:https://blog.areare.net/archives/8321

URLRequestは、NSMutableURLRequestとほぼ同じように書けます。

というより、内部的にはほぼ同じものらしいので、なんでこれで通らなくなってしまったのかわからん。

let urlRequest = URLRequest(url: requestURL)

これを踏まえて、書きます。

        let urlString = "https://httpbin.org/post"
        var request = URLRequest(url: URL(string:urlString)!)

        // set the method(HTTP-POST)
        request.httpMethod = "POST"
        // set the header(s)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        // set the request-body(JSON)
        let params: [String: Any] = [
            "foo": "bar",
            "baz": [
                "a": 1,
                "b": 20,
                "c": 300
            ]
        ]
        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: [])
        }catch{
            print(error.localizedDescription)
        }
        // use NSURLSessionDataTask
        let task = URLSession.shared.dataTask(with: request, completionHandler: {data, response, error in
            if (error == nil) {
                let result = String(data: data!, encoding: .utf8)!
                print(result)
            } else {
                print(error)
            }
        })
        task.resume()

こんな感じかな。

初期に公開したコードはやっぱり間違っていました。ごめんなさい。

ちゃんとこのソースのままでjsonをpostできていることを確認しました。

後は、他にもいろいろ変わっているところがありました。

NSJSONSerialization.dataWithJSONObject([AnyObject], options: [options], error: [error])は、

JSONSerialization.data(withJSONObject: [Any], options: [options])に置き換わりました。

errorのコールバックがなくなった分、try-catchを行わないとコンパイルエラーとなります。

他にも、微妙に大文字から小文字へと変数名が変わったり。

iOS開発初心者のSwift3の道は険しい。