Factor で http-request の response body が byte-array になった時
あるサービスの API レスポンスで body が byte-array になったので調べた
結論
charset が指定されていなくて byte-array になったら適切な形で decode する
utf-8 なら
http-get drop body>> utf8 decode json> .
詳細
サーバー側で charset が指定されていない場合は content-encoding が binary になるので
response body は byte-array になります.
例えば,以下のようなコードを書いた場合
"http://example.com" http-get drop .
charset が指定されていない場合
T{ response { version "1.1" } { code 200 } { message "OK " } { header H{ { "connection" "close" } { "date" "Sun, 29 Jan 2017 08:48:16 GMT" } { "content-length" "12" } { "x-content-type-options" "nosniff" } { "server" "WEBrick/1.3.1 (Ruby/2.4.0/2016-12-24)" } { "content-type" "application/json" } } } { cookies { } } { content-type "application/json" } { content-encoding binary } { body B{ 123 34 115 116 97 116 117 115 34 58 49 125 } } }
charset が指定されている場合
T{ response { version "1.1" } { code 200 } { message "OK " } { header H{ { "connection" "close" } { "date" "Sun, 29 Jan 2017 08:47:25 GMT" } { "content-length" "12" } { "x-content-type-options" "nosniff" } { "server" "WEBrick/1.3.1 (Ruby/2.4.0/2016-12-24)" } { "content-type" "application/json; charset=utf-8" } } } { cookies { } } { content-type "application/json" } { content-charset "utf-8" } { content-encoding utf8 } { body "{\"status\":1}" } }
charset が指定されていないと,いざ body 取って json として読み込もうとした場合に
Generic word json> does not define a method for the byte-array class.
で怒られます.
こういう場合は
"http://example.com" http-get drop body>> utf8 decode json> .
のように decode して読み込む.
以上,charset 大事という話でした.
Nim の pegs モジュールを使う
この記事は Nim Advent Calendar 2016 24 日目の記事です。
クリスマスとか年末に絡めた何かを考えていたのですが、思いつかなかったので普通に書きます。
PEG とは
PEG について、解説的なものを入れようかと思ったのですが、
Wikipedia がとても詳しく、私が生半可な知識で書くのも良くないのでそちらをご覧ください。
https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar
使ってみる
使い方は PEG に慣れていればなんの抵抗もないはずですが、
私のように正規表現しか書いたことのない人間には馴染みの薄い物です。
では先日使用した json をそのまま流用して試してみます。
使用した json は json.org の JSON Example の一番上のやつです。
http://json.org/example.html
この json を example.json として保存し、使用しています。
"SortAs": "SGML",
json からkey, value (この場合は SortAs:SGML )を得たい場合
PEG ではこのように書くことができます
'"' {( \w )+} '":' \s+ '"' {( \w )+} '"' ','?
"Abbrev": "ISO 8879:1986",
この場合は
'"' {( \w )+} '":' \s+ '"' {( \w / \s / ':' )+} '"' ','?
となります。
これらの規則は非終端記号として定義できます。
grammer <- '"' {( \w )+} '":' \s+ '"' {( \w / \s / ':' )+} '"' ','?
key, value 部分をまとめて以下のような記述にもできます。
grammer <- '"' cap '":' \s+ '"' cap '"' ','? cap <- {( \w / \s / ':' )+}
最後に、json 全体をパースしてみます
rule <- '{' \s+ '"glossary":' \s+ '{' \s+ title '"GlossDiv":' \s+ '{' \s+ title '"GlossList":' \s+ '{' \s+ '"GlossEntry":' \s+ '{' \s+ (glossDef / pair)* glossDef <- '"GlossDef":' \s+ '{' \s+ pair '},' \s+ pair <- '"' cap '":' \s+ '"' cap '"' ','? \s+ title <- '"title":' \s+ '"' cap '",' \s+ cap <- {( \w / \s / [.,-:;] )+}
このままだと nim ではなく PEG の話になるので申し訳程度にコードと結果を載せておきます
ちなみに今回の json でしか機能せず汎用性は全くありませんので...
import pegs const peg = """ rule <- '{' \s+ '"glossary":' \s+ '{' \s+ title '"GlossDiv":' \s+ '{' \s+ title '"GlossList":' \s+ '{' \s+ '"GlossEntry":' \s+ '{' \s+ (glossDef / pair)* glossDef <- '"GlossDef":' \s+ '{' \s+ pair '},' \s+ pair <- '"' cap '":' \s+ '"' cap '"' ','? \s+ title <- '"title":' \s+ '"' cap '",' \s+ cap <- {( \w / \s / [.,-:;] )+} """ let str = "example.json".readFile() if str =~ peg(peg): for i in 0..(matches.len - 1): if i < 2: echo "title: ", matches[i] else: if matches[i] != nil: if i mod 2 == 0: echo "key: ", matches[i] else: echo "value: ", matches[i]
この出力は以下の通りです。
title: example glossary title: S key: ID value: SGML key: SortAs value: SGML key: GlossTerm value: Standard Generalized Markup Language key: Acronym value: SGML key: Abbrev value: ISO 8879:1986 key: para value: A meta-markup language, used to create markup languages such as DocBook. key: GlossSee value: markup
参考
nim で json を扱う話
この記事は Nim Advent Calendar 2016 18 日目の記事です。
さて、ネタが被ったような気がしないでもないのですが
nim で json 扱うのが楽ですよって話をします。
json の扱いの簡単さはとても大切ですね。
今回例として使用した json は json.org の JSON Example の一番上のやつです。
http://json.org/example.html
この json を example.json として保存し、使用しています。
Version
Nim Compiler Version 0.15.2 (2016-12-18) [MacOSX: amd64]
json module を使う
json module を使えば json を簡単に扱うことができます
json -> JsonNode
import json when isMainModule: let jsonStr = "example.json".readFile() jsonObj = parseJson jsonStr echo jsonObj["glossary"]["title"].str
- Result
example glossary
JsonNode -> json
import json when isMainModule: let jsonStr = "example.json".readFile() jsonObj = parseJson jsonStr echo $jsonObj
- Result
{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossL...
簡単ですが、用途によってはどこか物足りません。
やっぱり object に詰めて欲しいですよね。
そこで出てくるのが marshal module です。
marshal module を使う
marshal module を使えば json <-> object が実現できます
準備として example.nim として以下を用意しました
type Base = object of RootObj Example* = object of Base glossary*: Glossary Glossary* = object of Base title*: string GlossDiv*: GlossDiv GlossDiv* = object of Base title*: string GlossList*: GlossList GlossList* = object of Base GlossEntry*: GlossEntry GlossEntry* = object of Base ID*: string SortAs*: string GlossTerm*: string Acronym*: string Abbrev*: string GlossDef*: GlossDef GlossSee*: string GlossDef* = object of Base para*: string GlossSeeAlso*: seq[string]
json -> object
import example, marshal when isMainModule: let jsonStr = "example.json".readFile() ex = to[Example] jsonStr echo ex.glossary.title
- Result
example glossary
object -> json
import example, marshal when isMainModule: let ex = Example() echo $$ex
- Result
{"glossary": {"title": null, "GlossDiv": {"title": null, "GlossL...
自分で object に詰める
さきほどの marshal module はとてもシンプルなもので
例えば json の key と object の key を変えたい場合などに対応することはできません。(できなかったはず...)
そのため、json の key に proc などがあるとつらい感じになります。
そういう場合は、自分で object に詰めてしまうのが楽だと思います。
今回の json は string と array しか出てこないので実装はこの程度でした
import json, sequtils proc `[]`(t: JsonNode, key: string): JsonNode = result = if t.hasKey(key): json.`[]`(t, key) else: nil proc to[T: object](node: JsonNode, data: var T) = for k, v in data.fieldPairs: when v is string: v = node[k].getStr elif v is seq: if node[k] == nil: v = @[] else: v = node[k].elems.map(proc(x: JsonNode): string = x.str) else: node[k].to v
- Usage
import example, json, sequtils ... when isMainModule: let jsonStr = "example.json".readFile() jsonObj = parseJson jsonStr var ex: Example jsonObj.to ex echo ex
実装も簡単ですし、依存も減るし、諸々の仕様に柔軟に対応できるので
状況次第では自分で実装してしまうのも悪くないかなと思っています。
それでは。
Jenkins の Android アプリ "Butler" を作った
Jenkins のクライアントアプリを作成しましたのでご報告。
もちろんオープンソースですが、今回ストアには公開していません。
ストアに公開しなかった理由についてですが、
アプリ的にではなく、API 的に公開できる品質にならなそうだと判断しました。
具体的には、ユーザー一覧など、多すぎるとタイムアウトしてしまう点です。
調べた限りではページング等できなさそうだったので、ここの改善は難しいと考えています。
そこらへんできる方法あれば教えていただけますと幸いです。
特徴
screen shot は GitHub をみてください。
アプリ的特徴
- ビルド、プロジェクト、コンピューター、ユーザーの確認
- ビルドの実行 (一部対応)
特徴
- Kotlin
- Data binding
まとめ
今買うの私的な挑戦は、Kotlin + Data binding の組み合わせを用いることで、
RxKotlin を使用しなかったことです。
Rx は流行りもあって広まっていますし、使用する人も増えていますが、
設計如何では、恐ろしいコードが生成される傾向があります。
そういうのをいくつかみてきたので、最近では Rx を使わない道を模索しており、今回のアプリはその一環です。
とはいえ、今回は MVVM を意識した作り (Data binding を使用しているので当たり前といえば当たり前ですが) なので
Rx を使ってもそれほど酷くはならなかったかもしれません。
あと、Kotlin は Observable とかあるので、使わなくても十分対応できるよねってところがあります。
Mackerel Client もまるごと書き直したい...
ストア公開する予定がないため、使いたい方はビルドして使ってみてください。apk もどこかに置く予定はあります。
Windows で perl5 の DBI モジュール使って嵌った話
perl5 の DBI モジュールを使用する際で
data_source の指定パスに日本語含まれてる場合に嵌った。
環境
- Windows 7, 10
- ActivePerl
>perl --version This is perl 5, version 22, subversion 1 (v5.22.1) built for MSWin32-x64-multi-t hread (with 1 registered patch, see perl -V for more detail) Copyright 1987-2015, Larry Wall Binary build 2201 [299574] provided by ActiveState http://www.ActiveState.com Built Jan 4 2016 12:12:58 Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in the Perl 5 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using "man perl" or "perldoc perl". If you have access to the Internet, point your browser at http://www.perl.org/, the Perl Home Page.
結論
connect する際に utf8 にエンコードするといける
アプローチがあってるかどうかは謎
良いアプローチあったら教えてください
テスト
#!/usr/bin/perl use strict; use warnings; use utf8; use Encode qw/encode decode/; use DBI; my $file_enc = "cp932"; binmode(STDIN,":encoding($file_enc)"); binmode(STDOUT,":encoding($file_enc)"); binmode(STDERR,":encoding($file_enc)"); my $db_loc = $ARGV[0]; my $data_source = "dbi:SQLite:$db_loc"; my $dbh = DBI->connect($data_source); $dbh->disconnect; print "できた";
結果
>perl test.pl ほんとやめて\test.db DBI connect('\x{0082}U\x{0082}n\x{0082}A\x{0082}a\x{0082}s\x{0082}A\test.db','', ...) failed: unable to open database file at test.pl line 21. Can't call method "disconnect" on an undefined value at test.pl line 23.
修正
#!/usr/bin/perl use strict; use warnings; use utf8; use Encode qw/encode decode/; use DBI; my $file_enc = "cp932"; binmode(STDIN,":encoding($file_enc)"); binmode(STDOUT,":encoding($file_enc)"); binmode(STDERR,":encoding($file_enc)"); my $db_loc = $ARGV[0]; my $utf8_db_loc = decode($file_enc, $db_loc); $utf8_db_loc = encode("utf8", $utf8_db_loc); my $data_source = "dbi:SQLite:$utf8_db_loc"; my $dbh = DBI->connect($data_source); $dbh->disconnect; print "できた";
Kotlin + OkHttp3 + Retrofit2 でヘッダの追加とか
Kotlin でアプリ書いてる時にちょっと戸惑った
OkHttpClient のヘッダーの追加は
val httpClient = OkHttpClient.Builder() .addInterceptor { it.proceed(it.request() .newBuilder() .addHeader("Content-Type", "application/json") .build()) } .build()
これを Retrofit で使用する場合
Retrofit.Builder() .baseUrl { HttpUrl.parse(url) } .client(httpClient) .build() .create(HogeHogeApiInterface::class.java, context) .postHuge() ...
Observable 返したい場合や、gson 使いたい場合
ここらへんは java と変わり無いですが
Retrofit.Builder()
.baseUrl { HttpUrl.parse(url) }
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
...
ちなみに HttpLoggingInterceptor 使う場合
val httpClient =
OkHttpClient.Builder()
.addInterceptor {
...
}
.addInterceptor(okhttp3.logging.HttpLoggingInterceptor()
.setLevel(okhttp3.logging.HttpLoggingInterceptor.Level.BASIC))
.build()
実際に使ってるのはここらへんです。
もうちょっと知見が集まってくれると Kotlin 使う人は増えそうな気がする
Blackberry 10 での Json の扱い方について
Blackberry OS 10 のネイティブアプリ開発で Json を扱うことがあったのでメモ
C++ で Json を扱う場合、Cascades framework にある JsonDataAccess を用いるのが簡便だと思います。
Qt の対応バージョンが 4.8 で、Qt5.5? で追加された Json サポートを利用することができないためです。
使おうと思えば Qt5 も使えるようなので、その場合は Qt の Json サポートを利用するのがよいかもしれません。
qml で使用する場合は、JSON.parse() で行けるはず。
Parse
QString jsonData = .... JsonDataAccess jda; QVariant var = jda.loadFromBuffer(jsonData); // map QVariantMap map = var.value<QVariantMap>(); // list QVariantList list = var.value<QVariantList>();
レスポンスとして Json が返ってくる場合、このような使い方になります
QVariantMap ApiRequest::onRequestFinished(QNetworkReply* reply) { if (reply && reply->error() == QNetworkReply::NoError) { const QByteArray buffer(reply->readAll()); JsonDataAccess jda; QString jsonData = "hogehoge"; QVariant var = jda.loadFromBuffer(jsonData); QVariantMap map = var.value<QVariantMap>(); ... } }
Stringify
QVariantMap map; map["hoge"] = "hogehoge"; JsonDataAccess jda; QByteArray jsonString; jda.saveToBuffer(QVariant(map), &jsonString);
POST リクエストとか送る時はこんな感じ
void ApiRequest::request(QString username, QString password, QString factorKey) { QNetworkRequest request; request.setUrl(QUrl("http://cordea.jp")); connect(mManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onRequestFinished(QNetworkReply*))); QVariantMap map; map["hoge"] = "hogehoge"; JsonDataAccess jda; QByteArray body; jda.saveToBuffer(QVariant(map), &body); mManager->post(request, body); }
c++ 的に書き方があっているかは若干不安。