https://heytech.tistory.com/320

https://kodorricoding.tistory.com/16


title: 翻訳: WebAPI 設計のベストプラクティス
tags: api rest 設計 翻訳 WebAPI
author: mserizawa
slide: false


これは Enchant の開発者である Vinay Sahni さんが書いた記事「Best Practices for Designing a Pragmatic RESTful API^1を、ご本人の許可を得て翻訳したものです。

RESTful な WebAPI を設計しようとすると、細かなところで長考したり議論したりすると思います。また、他の API に倣ってやってはみたものの、本当にそれでいいのか、どうしてそうしているのか分からない、何てことも少なくはないと思います。
この記事では、そのようなハマリどころについて Vinay さんなりの答えを提示し、簡潔かつ明快に解説してくれています。
今後 WebAPI を設計される方は、是非参考にしてみてください。

なお、誤訳がありましたら編集リクエストを頂けると幸いです。


まえがき

アプリケーションの開発が進むにつれて、その WebAPI を公開する機会に出くわすかもしれません。ただ、いくらデータ設計がひと段落していたとしても、一度インターフェイスを公開してしまうと、そこからの変更はなかなか自由に出来ず、苦しめられることとなります。
今の時代、ネットで調べれば API 設計に関する情報はいくらでも手に入ります。しかし、幅広く受け入れられているスタンダードかつ万能な設計は存在せず、多くのことに悩むこととなります。フォーマットはどれにするとか、認証はどうやるとか、バージョンはつけるのかとか...

EnchantZendesk に変わるカスタマーサポートツール)の設計を通して、私はこれらの問いに対して実践的な答えを探ってきました。Enchant API を使いやすく、受け入れやすく、また、Enchant の機能を十分に活かせるものにしたかったのです。

API 設計の勘所

ネット上で目にする API 設計に関する議論は、少しアカデミックすぎたり、個々人の曖昧な主張が入り混じっていて、とても現実世界で通用するものではありません。私はこの記事を通して、実用的かつモダンな API 設計について説明しようと思います。誤解を防いだり、理解を早めていただくために、まずは私が思う API の勘所をお伝えします。

  • できる限り Web の標準に従うこと
  • 開発者に親切な作りにすること。また、ブラウザのアドレスバーから叩けるようにすること
  • シンプルで一貫性を持たせ、直感的かつ心地よく使えるようにすること
  • Enchant(本体のサービス)が提供する機能を十分に活用できるものにすること
  • 様々な要件とのバランスを保ちつつ、効率的に開発ができるようにすること

API は開発者のための UI です。他の UI と同じように、UX にもとことんこだわりましょう!

RESTful な URL にしよう

WebAPI の分野で最も市民権を獲得している原則は REST です。REST は、Roy Fieldingネットワークソフトウェア設計に関する論文で言及したのが始まりです。

REST のキーとなる概念は、論理的に分割されたリソースを HTTP メソッド(GET, POST, PUT, PATCH, DELETE)で操作する、ということです。

では、「リソースを論理的に分割する」とはどういうことでしょうか。これは、API 利用者が客観的に見て意味がわかる名詞(動詞じゃありません!)を使って分割する、ということです。内部実装で利用しているモデルがそのままいい具合にリソースに対応するかもしれませんが、これは必ずしも一対一の関係でなくても構いません。重要なのは、内部実装につられて妙なデータ構造を表に晒すべからず、ということです。(Enchant API でもところどころ良くないところはあるのですが...)

リソースが定義できたら、次はそれらに対してどんなアクションが必要で、また WebAPI 上でどのように表現されるかを考えます。REST の原則に従うと、HTTP メソッドを使った CRUD 操作は以下のように定義されます。

  • GET /tickets - チケットのリストを取得する
  • GET /tickets/12 - 指定したチケットの情報を取得する
  • POST /tickets - 新しいチケットを作成する
  • PUT /tickets/12 - チケット #12 を更新する
  • PATCH /tickets/12 - チケット #12 を部分的に更新する
  • DELETE /tickets/12 - チケット #12 を削除する

REST の素晴らしい点は、HTTP メソッドを活用することで /tickets という単独のエンドポイントに対して主要な機能を持たせられることです。命名規則など意識する必要ありませんし、URL 構造は明白かつ整然としたものとなります。これは素晴らしい!

では、エンドポイントの名前は単数形と複数形のどちらを使うのが適切でしょうか。KISS の原則に従えば、答えは「一貫して複数形を使う」です。単数のインスタンスを複数形で表すことに違和感を覚えるかもしれませんが、person/people, goose/geese といった奇妙な単複変換を扱わなくてよくなれば、API 利用者も快適になりますし、API 提供者が実装するのも簡単です(モダンなフレームワークであれば /tickets/tickets/12 を同一のコントローラで扱えますので)。

関連データについてはどうしましょう。もしその関連データが、あるリソースに付随してのみ存在するのであれば、REST によって綺麗に解決されます。以下の例をみてください。Enchant のチケットにはたくさんのメッセージが紐付いているのですが、これらのメッセージは、以下のように表現されるのが自然でしょう。

  • GET /tickets/12/messages - チケット #12 に紐づくメッセージのリストを取得する
  • GET /tickets/12/messages/5 - チケット #12 に紐づくメッセージ #5 を取得する
  • POST /tickets/12/messages - チケット #12 に新しいメッセージを作成する
  • PUT /tickets/12/messages/5 - チケット #12 のメッセージ #5 を更新する
  • PATCH /tickets/12/messages/5 - チケット #12 のメッセージ #5 を部分的に更新する
  • DELETE /tickets/12/messages/5 - チケット #12 のメッセージ #5 を削除する

これとは逆に、関連データがリソースから独立して存在する場合は、リソースのアウトプットに関連データの ID を含めるのが良いです。API 利用者はその内容を見て、関連データのエンドポイントにアクセスすることとなります。
とはいえ、親データと関連データが大体一緒に使われるようであれば、レスポンスに関連データを埋め込めるようにしておいて、何度もリクエストする手間を省いてもいいかもしれません。

最後に、CRUD の概念にフィットしないようなアクションについてはどうしましょうか。

これは難易度の高い問題ではありますが、いくつかのアプローチがあります。

1つは、アクションをリソースの項目の1つとして扱う方法で、これはアクションがパラメータを取らない場合に有効です。例えば、何かをアクティベートするようなアクションであれば、対象となる boolean 項目をサブリソースとして扱って、そこに対して PATCH リクエストを投げると考えます。GitHub API の例を見てみましょう。Gist にスターをつけるアクションを PUT /gists/:id/star としていて、スターを解除するアクションを DELETE /gists/:id/star としています。

一方で、REST の構造にマッチさせられないアクションもあると思います。例えば、複数のリソースを横断的に検索するようなアクションについては、特定リソースのエンドポイントに紐付けるのは、なんとも無理やりな感じがします。このような場合は、/search というエンドポイントを作ることで解決します。ちょっとルール違反な気もしますが、API 利用者から見ておかしくなく、混乱がないようにドキュメントにしっかりと書かれていれば問題ないのです。

いつ何時も、SSL 通信を使おう

通信については、何がなんでも SSL で暗号化しましょう。今の時代、WebAPI は図書館やコーヒーショップ、空港といった様々な場所からアクセスされます。これらのアクセスポイントは必ずしもセキュアではありません。暗号化しないで通信していると、簡単になりすましがされてしまいます。

SSL 通信を使う別のメリットとして、暗号化を保障しておくことで認証処理が簡潔にできることがあります。アクセストークンを使うことで、リクエストごとの認証処理を省略できるのです。

ひとつ気をつけたいのは、API の URL に非 SSL でのアクセスがあるケースです。こうしたアクセスを、対応する SSL の API にリダイレクトしてはいけません。代わりにきちんとしたエラーを投げてください。不適切な設定が行われたクライアントが、暗号化されていないエンドポイントにリクエストを送信して、それが実際の暗号化されているエンドポイントへと暗黙的にリダイレクトされるなんてことは最も避けたい事態でしょう。

かっこいい仕様書を作ろう

API の価値はそのドキュメントの良さが表すと言っても過言ではありません。ドキュメントは誰もが簡単に閲覧できるようにしておく必要があります。ほとんどの開発者は、API の利用を検討する際にまずはドキュメントをチェックしますが、それが PDF だったり、閲覧に何かしらの登録が必要だったりすると、彼らにリーチするのはとても難しくなってしまいます。

また、ドキュメントにはリクエストとそのレスポンスの例を記載しておきましょう。できれば、リクエストはブラウザやターミナルに簡単にペーストできるようにしておくと良いです。GitHubStripe の API 仕様書はこのあたりがとてもよくできています。

一度 API を公開したら、利用者への予告なしに下位互換性のない変更を入れることはできなくなります。ドキュメントには、そういった仕様廃止のスケジュールや、API の更新も掲載しましょう。更新情報については、ブログかメーリングリストでも配信すると良いですね(両方だと最高です!)。

バージョンは URL に含めよう

API は必ずバージョン管理しましょう。バージョン管理することで開発速度を速められますし、廃止された仕様でのリクエストを弾いたり、メジャーアップデートがあるような場合に、移行期間として過去バージョンと共存させることもできます。

API のバージョンに関して議論されることといえば、それを URL に含めるべきか、リクエストヘッダに含めるべきか、ということです。学術的に言えば、ヘッダに含めるのがいいのかもしれません。ただ、ブラウザからバージョンをしたリクエストをすることを考えると、URL に含めるのが妥当といえます(私が冒頭で述べたことを思い出してください)。

バージョン管理について、私は Stripe のアプローチがとても好きです。URL では API のメジャーバージョン(v1)が指定できて、リクエストヘッダでは日付ベースのマイナーバージョンが指定できるのです。これにより、メジャーバージョンで API の安定した動作を担保させつつ、マイナーバージョンで出力項目やエンドポイントの変更といった細かな調整ができるのです。

どんなに頑張っても、API を永らく安定させることは難しく、変更は避けられません。大切なのは、いかにしての変更を管理するかです。何かを廃止したい時は、移行までの期間を十分に確保して利用者に周知すれば、ほとんどの場合は受け入れられるのです。

フィルタ・ソート・検索はリクエストパラメータでやろう

ベースとなる URL はできるだけシンプルにしておくのが良いです。フィルタやソート、検索といった機能はリクエストパラメータで制御するのが良いです(もっとも、これは単一リソースに対するものに限ります)。これらについて、細かく見ていきましょう。

フィルタリング

各フィールドに対して、フィルタリングをするためのパラメータを用意しましょう。例えば、/tickets でチケットのリストを取得する際に、state が open のものだけに絞りたいことがあると思います。このような要望は GET /tickets?state=open のようにして実現させましょう。リソースの項目である state をそのまま、フィルタするためのリクエストパラメータとするのです。

ソート

並び順の指定については、 sort パラメータを用意して処理するようにしましょう。複雑なソートにも応えられるように、ソート対象とする項目をカンマ区切りで指定して、かつ、昇順・降順をネガポジで指定するようにします。いくつか例を挙げてみます。

GET /tickets?sort=-priority - チケットのリストを priority の降順で取得する
GET /tickets?sort=-priority,created_at - チケットのリストを priority の降順、かつ created_at の昇順で取得する

検索

フィルタクエリでは事足りず、全文検索が必要になることもあると思います。おそらく、ElasticSearch や他の Lucene ベースの検索エンジンを使うことになると思いますが、特定リソースに対して投げるクエリには、q パラメータを使いましょう。検索クエリはそのまま全文検索エンジンに伝えられ、API のアウトプットは普段と変わらない形式となります。

以上を組み合わせてみると、以下のような感じでリクエストパラメータが構築できます。

GET /tickets?sort=-updated_at - 最近更新されたチケットを取得する
GET /tickets?state=closed&sort=-updated_at - 最近クローズされたチケットを取得する
GET /tickets?q=return&state=open&sort=-priority,created_at - オープン状態で優先度の高いチケットのうち、「return」という単語を含むものを返す

よく使うクエリのエイリアス

API の UX をより良くするために、よく使われる検索クエリは REST のパスにしてしまうことを考えましょう。例えば、「最近クローズされたチケット」を取得するクエリは、以下のような URL にまとめることができます。

GET /tickets/recently_closed

レスポンスのフィールドを絞れるようにしよう

API 利用者は、常にリソースの全項目を必要としているわけではありません。レスポンスのフィールドを絞る手段を用意することは、API 利用者のネットワーク負荷を下げ、通信速度を向上させることに貢献します。

そのために、出力したいフィールドをカンマ区切りで指定できるパラメータを用意しましょう。例えば、以下のリクエストではオープン状態のチケットを更新日付順で並べて表示するのに必要な、最低限の情報のみを返します。

GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at

作成・更新の後は変更後の情報をフルで返そう

created_at や updated_at といった項目は、こちらが明示的に指定するものではなく、作成・
更新の際にサーバが自動で挿入するものです。API 利用者が作成・更新後のリソース情報を取得するためにもう一度 API を叩くのは大変なので、POST, PATCH, PUT リクエストのレスポンスには変更後のリソースの情報を含めるようにしましょう。

なお、POST で新しくリソースを作成した際には、ステータスコード 201 を返し、Location ヘッダに作成されたリソースへの URL を含めるのが良いです。

HATEOAS を採用するのは待とう

(API のレスポンスを踏まえて)次に進むべきリンクを API 利用者が構築するか、はたまた API が提供するかについては、賛否両論の様々な意見が飛び交っています。REST を拡張する形で提案された HATEOAS という概念では、エンドポイント同士のインタラクションは API のレスポンスに含めるべきだとされています。

確かに、いわゆる Web サイトはこの HATEOAS の原則通りに動いているように見えます。例えば、私たちはウェブサイトのトップページにアクセスしたら、そこに表示されているリンクを押しますよね。ただ、私はこれを API の世界で展開するには時期尚早だと思います。ウェブサイトではどのような遷移がされるかどうかの判断をアプリ自体ができます。一方、API の場合は次にどのようなリクエストが来るかどうかはその API を使ったアプリに依存するため、API レベルで行うことは難しいのです。もちろん、どうにか工夫をしてその判断を遅延させることはできるかもしれませんが、そこまでして得られるメリットはさほどないと思います。このことから、私は HATEOAS はとても前途有望ではあるものの、現時点ではまだ使うには早すぎると思うのです。これが標準化され、メリットが最大限に生かされるようになるためには、まだまだ議論が必要です。

現時点では API 利用者がドキュメントをちゃんと読むことを前提としつつ、リソースの ID からリンクを構築してもらうのが良いでといえるでしょう。ちなみに、ID にこだわるのにはいくつかの理由があります。まず、ネットワーク上で介されるデータ量が小さくなりますし、API 利用者が保存すべきデータも最小化されるからです(ID を含む URL を保存するのとはわけが違います)。

また、前章で URL はバージョン管理されるべきと言いましたが、これを考慮しても API 利用者には ID
のみを保存してもらう方が良いのです。ID を使えばバージョンに依存しない安定したアクセスができますが、URL ごと保存してしまうとそうもいかないからです。

可能な限り JSON で返そう

API レスポンス形式の分野において、XML の時代は終わりました。XML は冗長ですし、読むのもパースするのも大変です。また、XML のデータの持ち方はほとんどのプログラミング言語が扱うモデルと互換性がありませんし、得意としている拡張性についても、内部モデルを表現する上ではほとんど活用することはありません。

私がとやかく理由を述べるよりも、YoutubeTwitter, Box といった大御所の API が XML を廃止し始めているのを見れば、明白だと思います。

ちなみに、Google トレンドで「XML API」と「JSON API」を比較してみるとこんな感じです。

201305-xml-vs-json-api.png

とはいえ、たくさんのエンタープライズの利用者を抱えていると、XML をサポートしなければいけない状況になることも無きにしも非ずです。そうなった時に、おそらく以下の疑問を抱くと思います

「レスポンス形式の指定はヘッダでやるの?それとも URL でやるの?」

例のごとくブラウザでの叩きやすさを考慮して、URL でやりましょう。URL の最後に .json.xml といった拡張子を追加するのが良いと思います。

フィールドの命名規則を考えよう

もし API のレスポンス形式がデフォルトで JSON であれば、フィールドの名前は JavaScript の命名規則に従うのが「正しい」やり方でしょう。つまり、キャメルケースです。ただ、色々なプログラミング言語でクライアントライブラリを作るのであれば、各言語での命名規則に従うのが良いです。Java や C# であればキャメルケースで、Python や Ruby であればスネークケースといった感じです。

ちなみに、私はずっと、キャメルケースよりスネークケースの方が読みやすいと思っていました。この曖昧な感覚を証明する手段がなかったのですが、近年のアイトラッキングシステムを使った研究により、どうやらスネークケースの方が 20% 読みやすいということがわかったそうです。つまり、スネークケースを使った方が、API の利便性が上がったり、ドキュメントの読みやすさが向上したりするかもしれません。

ほとんどの JSON API ではスネークケースが採用されています。私が思うに、これは各々が利用している JSON シリアライズライブラリがその言語の命名規則に依存しているからです。もしかしたら、命名規則の違いに対応出来る JSON シリアライズライブラリを使った方がいいのかもしれません。

JSON はデフォルトで整形しよう

圧縮された状態の JSON をブラウザ上で見るのは、決して気持ちの良いものではありません。?pretty=true のようなパラメータで PrettyPrint することができるかもしれませんが、それであれば初めから PrettyPrint してある方が良いでしょう。gzip 圧縮してしまえば、余分な空白を転送するコストなど取るに足らないものとなります。

いくつかのユースケースを考えてみましょう。API 利用者がデバッグのために取得したデータを出力したとして、もしそれがそのまま読めたら?または、API 利用者が生成されたリクエスト URL をおもむろにブラウザに投げたとして、それがそのまま読めたら?些細なことではありますが、これらは API の UX を高める大切なポイントです。

とはいえ、余分なデータが転送されることは気になりますよね。

ホンモノのデータを使って検証してみましょう。GitHub API のレスポンスを使いますが、この API はデフォルトで PrettyPrint されているので、比較のためにちょっと加工します。

$ curl https://api.github.com/users/veesahni > with-whitespace.txt
$ ruby -r json -e 'puts JSON JSON.parse(STDIN.read)' < with-whitespace.txt > without-whitespace.txt
$ gzip -c with-whitespace.txt > with-whitespace.txt.gz
$ gzip -c without-whitespace.txt > without-whitespace.txt.gz

この結果、それぞれのファイルサイズは以下のようになりました。

  • without-whitespace.txt - 1252 bytes
  • with-whitespace.txt - 1369 bytes
  • without-whitespace.txt.gz - 496 bytes
  • with-whitespace.txt.gz - 509 bytes

この例では、gzip 圧縮をしない状態だと空白の有無で 8.5% の差が生じ、gzip 圧縮をした状態だと 2.6% の差が生じることがわかります。一方で、gzip 圧縮自体が 60% のサイズ削減をすることもわかります。つまり、PrettyPrint により増加するサイズは数字で見ても比較的少なく、「gzip 圧縮かつ PrettyPrint」の状態で返すのがベストであることがわかります!

さらに付け加えると、Twitter の Streaming API では gzip 圧縮をしたことで 80% のサイズダウンに成功した例もありますし、StackExchange の API は圧縮した状態でしか返さない仕様となっています。

要素はラップせずに返そう

多くの API が、以下のように要素をラップした状態で返しています。

{
  "data" : {
    "id" : 123,
    "name" : "John"
  }
}

なぜラップをするのかというと、こうしておくと付加的なメタデータやページングのデータを埋め込みやすいからです。HTTP クライアントの中には、レスポンスヘッダの参照がしづらいものもあるかもしれませんし、JSONP のリクエストではヘッダを参照する術がありません。しかしながら、世の中のスタンダードは CORS になりつつありますし、RFC 5988 では Link というレスポンスヘッダの仕様が提案されています。つまり、要素をラップする必要はなくなりつつあるわけです。

将来的には、デフォルトの状態ではラップをしていなく、特殊なケースに対応するときのみラップするようになるでしょう。

では、ラップが必要な特殊ケースに出くわした時はどうしましょう。

先にも述べましたが、それが必要なのは JSONP を使ってクロスドメイン制約を解決するときと、HTTP クライアントがどうしてもレスポンスヘッダを扱えないときです。

JSONP のリクエストは、callbackjsonp といったリクエストパラメータと一緒に来ます。API はこのパラメータを見つけ次第、ラップモードへとスイッチが切り替わります。どんな状態であれステータスコード 200 を返し、本来のステータスコードはラップした内側に埋め込みます。さらに、従来レスポンスヘッダに入れていたデータも内包させるのです。

callback_function({
  status_code: 200,
  next_page: "https://..",
  response: {
    ... actual JSON response body ... 
  }
})

HTTP クライアントが対応していないときも同様で、?envelop=true のような何かしらのパラメータをトリガーにラップモードを発動させれば良いです(もちろん、この場合 JSONP コールバックは不要です)。

追加・更新時のリクエストボディには JSON を使おう

ここまでの内容で、あなたはきっと全ての API アウトプットが JSON 形式であることには賛同してくれるでしょう。では、JSON をインプットに使うというのはどうでしょうか。

多くの API では、リクエストパラメータ同様、リクエストボディも key, value 形式のデータが詰められてやりとりされています。これはとてもシンプルですし、幅広く使われていて有効な手段です。

しかしながら、この方法には問題点があります。まず、データ型の概念がないため、受け取った側は文字列から integer や boolean といった値に変換する必要があります。また、階層構造を表現する方法もありません。key に対して [ ] をつけて無理やりに階層を表現する方法もありますが、JSON の表現力に比べたら貧弱なものです。

API がシンプルなものであれば、この方法でも問題ないでしょう。しかし、複雑な API に関して言えば、リクエストボディに JSON を使うことにを検討した方が良いです。いずれにせよ、両方採用するようなことはせず、首尾一貫した仕様にしてください。

JSON で POST, PUT, PATCH のリクエストを受け付ける場合は、Content-Typeapplication/json を指定してもらいましょう。さもなくば、応答するのは 415 Unsupported Media Type です。

ページング情報はレスポンスヘッダに入れよう

先にも述べましたが、ラップするのが大好きな API は往々にしてページング情報をその内側に含めます。私は決してそう言った仕様を責めるつもりはありません。なぜなら、つい最近まではそうするのが普通だったからです。ただ、今は RFC 5988 で提案されている Link ヘッダに倣って、レスポンスヘッダに含めるのが良いでしょう。

Link ヘッダには、API 利用者がいちいち URL を組み立てる必要がないよう「すぐに叩ける状態」の URL を含めましょう。そのためには、ページングはカーソルベースで指定できることがポイントとなります。以下に、GitHub API が返す Link ヘッダを例示します。

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next", <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

ただ、これだけでは API が返すページングの情報としては不完全です。たとえば、結果のトータル件数がなければ実装に困るでしょう。そういった付加的なものは、X-Total-Count のようなカスタムヘッダを使って返してあげましょう。

関連データを埋め込む手段を作ろう

API 利用者は、往々にしてレスポンス要素の関連データも必要とします。1つのモデルを完成させるのに何回も API を叩いてもらうのは大変ですし、必要に応じて関連データをレスポンスに含めてしまうことは非常に有用に思われます。

ただし、これは REST の原則に背くことでもあります。このルール違反に対する背徳感を少しでも和らげるべく、embedexpand のようなリクエストパラメータでだしわけするようにしましょう。

次の例では、embed パラメータで埋め込む関連データを複数指定し、また、ドット表記を使って取得する項目の指定もしています。

GET /tickets/12?embed=customer.name,assigned_user

これのレスポンスでは、以下のようにチケットに関連データが付与されます。

{
  "id" : 12,
  "subject" : "I have a question!",
  "summary" : "Hi, ....",
  "customer" : {
    "name" : "Bob"
  },
  "assigned_user": {
   "id" : 42,
   "name" : "Jim",
  }
}

もちろん、このようなことを実現するためには、API の内部実装を少なからず複雑にする必要があります。往々にして発生するのは、N+1 問題です。

HTTP メソッドを上書きしよう

HTTP クライアントの中には、GET と POST しか扱えないものもあります。こういった制約を持つクライアントでも不自由なく使ってもらえるよう、HTTP のメソッドを上書きする手段を用意しておきましょう。ただし、このやり方にはこれといったスタンダードが存在しません。強いて言うなら、X-HTTP-Method-Override というリクエストヘッダで上書きするメソッド(PUT, PATCH, DELETE)を指定するのが一般的です。

気をつける点としては、上書き可能なのは POST リクエストのみに限定しておくべき、ということです。REST の原則上、GET リクエストはいかなる場合でもサーバのデータを書き換えてはいけないからです!

リクエスト制限情報はレスポンスヘッダに入れよう

一般的に、API の乱用を避けるためにリクエストの回数には制限が入れられます。RFC 6585 でも、429 Too Many Request という HTTP ステータスコードが提案されています。

リクエスト制限を入れるとなると、API 利用者が現在の制限状態をチェックする方法が求められます。これについても、これといったスタンダードなやり方は存在しないのですが、多くの場合はレスポンスヘッダが利用されます

最低限、以下の情報をヘッダ含めるようにしましょう(ちなみにこれは Twitter の命名規則に従ってします)。

  • X-Rate-Limit-Limit - 一定期間内でリクエストできる最大回数
  • X-Rate-Limit-Remaining - 次の期間までにリクエストできる回数
  • X-Rate-Limit-Reset - 次の期間が来るまでの秒数

ところで、X-Rate-Limit-Reset を日時形式にしないのはなぜでしょう?

日時形式はとても便利ですが、日付やタイムゾーンといった情報はこの用途では余分です。API 利用者が知りたいのは、あとどれくらいでリクエストができるようになるかであり、これに一番簡単に応えるのが秒数なのです。また、端末時計の誤差を無視するという目的もあります。

また、X-Rate-Limit-Reset に UNIX タイムスタンプを使っている API を見かけるかもしれませんが、これは絶対に真似しないでください!

どうしてこれがダメなのかというと、HTTP の仕様としてすでに RFC 1123 が日付のフォーマットについて提案していて、Date, If-Modified-Since, Last-Modified といった HTTP ヘッダで採用されているからです。もし日付の類を扱うヘッダを定義するのであれば、それは RFC 1123 に従う必要があるのです。

トークン認証を使おう

RESTful な API は状態を持ちません。つまり、認証に関して cookie やセッションを使うことはできず、代わりに認証クレデンシャルのようなものを使う必要があります。

SSL の利用を前提とすることで、このクレデンシャルは非常にシンプルなものになります。ランダムに生成された文字列をアクセストークンとして、BASIC 認証のユーザネームフィールドに入力してもらえば良いのです。この方法の優れている点は、ブラウザで扱いやすいことです。未認証の状態であれば、ブラウザはアクセストークンの入力を求めるプロンプトを表示するでしょう。

しかしながら、この BASIC 認証を使った仕組みは、API 利用者がその管理者からトークンをコピーしてもらえる状況でなければ有効ではありません。もしそれができないのであれば、OAuth2 の認証を使って安全に 3rd パーティ開発者用のトークンが発行できるようにしましょう。ちなみに OAuth2 自体も SSL 通信を前提としている点にご注意ください。

JSONP に認証を使う場合は、また別の方法が必要となります。というのも、JSONP のリクエストでは BASIC 認証も Bearer トークンも使えないためです。この場合は、access_token のようなパラメータを使ってトークンの受け渡しをすることになります。ただし、このやり方では、サーバ上のログにトークンが
残る可能性があり、セキュアな方法とはいえない点にご注意ください。

これら3つの方法はトークンの運び方が違うだけであって、本質的にはどれも「トークン認証」と言えます。

キャッシュの情報をレスポンスヘッダに入れよう

HTTP にはキャッシュのための機構が用意されています。API 開発者は、レスポンスにいくつかのヘッダを含め、リクエストのヘッダをバリデーションするだけで、キャッシュの恩恵に与れます。

これについて、ETag を使うやり方と Last-Modified を使うやり方があります。

ETag

レスポンスを送る際に、レスポンス内容のハッシュかチェックサムを ETag ヘッダに含めます。つまりこのヘッダは、レスポンス内容に変更があった際に変わるものとなります。これを受け取ったクライアントは、レスポンス内容をキャッシュしつつ、今後同一 URL のリクエストを投げる際に、ETag の内容を If-None-Match ヘッダに含めるようにします。API は、ETag 値と If-None-Match の内容が一致していれば、レスポンス内容の代わりに 304 Not Modified というステータスコードを返します。

Last-Modified

これは基本的には ETag と同様の概念ですが、タイムスタンプを使う点が異なります。Last-Modified ヘッダには RFC 1123 で提案された形式の日時データが含まれていて、これを If-Modified-Since ヘッダの内容と比較することでキャッシュを制御します。注意点としては、HTTP の仕様では3つの異なる日時形式が定義されていて、API サーバはそのいずれの形式でも許容できるようにする考慮する必要があるということです。

ちゃんとしたエラーメッセージを返そう

Web サイトのエラーページでは、訪問者にとって有益な情報が表示されます。API もこれと同じで、有益なメッセージを、使いやすい形式で返す必要があります。エラーメッセージだからと言って特別にすることはなく、他のリソースと同じように扱えるようにするのです。

HTTP のステータスコードのうち、エラー関連のものは大まかに2種類に分けられます。400 系のクライアント起因のものと、500 系のサーバ起因のものです。最低限、400 系のエラーについては、一通りそれ用の JSON を返すようにしましょう。可能であれば、500 系のエラーについてもそうすると良いです(もっとも、これはロードバランサやリバースプロキシの仕事ですが)。

エラーの内容には、開発者に向けていくつかの情報を含めるようにします。それは、分かりやすいエラーメッセージと、ユニークなエラーコード(これは、ドキュメントで詳細を検索するためのものです)、そして、可能であれば原因の説明です。これらを含めた JSON は以下のようになります。

{
  "code" : 1234,
  "message" : "Something bad happened :(",
  "description" : "More details about the error here"
}

追加・更新時の入力値バリデーションエラーについては、さらにその内容をブレイクダウンした方が良いです。以下の例は、入力値エラーの原因となったフィールドと理由を含めたものです。

{
  "code" : 1024,
  "message" : "Validation Failed",
  "errors" : [
    {
      "code" : 5432,
      "field" : "first_name",
      "message" : "First name cannot have fancy characters"
    },
    {
       "code" : 5622,
       "field" : "password",
       "message" : "Password cannot be blank"
    }
  ]
}

HTTP ステータスコードを有効活用しよう

HTTP ではたくさんのステータスコードが定義されて、これは API でも有効活用できます。これらを使うことは、API 利用者にとってレスポンスを適切に処理するための手助けとなります。是非とも使うべきステータスコードのリストを載せておきます。

  • 200 OK - GET, PUT, PATCH, DELETE リクエストが成功した場合に応答。もしくは、POST リクエストが結果的に何もリソースを作らなかった場合に応答。
  • 201 Created - POST リクエストがリソース作成に成功した場合に応答。なお、そのリソースへのリンクを Location ヘッダに含める必要がある。
  • 204 No Content - 成功したDELETE リクエストで、ボディを返したくない場合に応答
  • 304 Not Modified - HTTP キャッシュが有効な場合に応答
  • 400 Bad Request - パース不可能なリクエストボディが来た場合に応答
  • 401 Unauthorized - 認証がされていない、もしくは不正なトークンの場合に応答
  • 403 Forbidden - 認証はされているが、認可されていないリソースへのリクエストに応答
  • 404 Not Found - 存在しないリソースへのリクエストに応答
  • 405 Method Not Allowed - 認可されていないメソッドでのリクエストに応答
  • 410 Gone - 今は存在しないリソース(廃止されたAPIなど)で空要素を返す場合などに応答
  • 415 Unsupported Media Type - 対応していない MediaType が指定された場合に応答
  • 422 Unprocessable Entity - バリデーションエラーに対して応答
  • 429 Too Many Requests - 回数制限をオーバーしたリクエストに対して応答

まとめ

冒頭で述べた通り、API は開発者のための UI です。機能性だけでなく、使い心地まで、とことん追求しましょう。


追記(2016/03/29)

@ryo88c さんが、本記事の説明で不足している「なぜそうすべきか」の具体的な根拠をまとめてくださいました!
また、アンチパターンとなっている箇所についても言及されています。
本質的な理解のため、ぜひこちらの記事も合わせてご確認いただければと思います。

「WebAPI 設計のベストプラクティス」に対する所感 - Qiita


追記(2016/07/19)

@howdy39 さんが、本記事の内容と Google の WebAPI の設計を比較してまとめてくださいました!
かなり詳細に照らし合わせて検証されています。
ぜひ、こちらの記事も合わせてご確認くださいませ。

GoogleのWebAPI設計とWebAPI設計のベストプラクティスを比較してみる - Qiita

4 Powerful Python Destructuring Features

Unpacking iterable, avoid indices, omit elements and more

Anupam Chugh

Jul 12, 2020·4 min read

Photo byAlmos BechtoldonUnsplash
 
Python is the most popular programming language largely owing to its pseudo-code like syntax.
 
It has some mighty powerful features and the fairly easy learning curve makes it the first choice language for developers all over the globe.
 
While list comprehensions are still the shiniest tool, destructuring is where the power lies.
 
Destructuring assignments are a shorthand syntax for mapping one or more variables from the left-hand side to the right-hand side counterparts. It’s handy in unpacking values as we shall see shortly.

Swapping And Updating Two Or More Values

Interchanging underlying values of variables is the most basic form of destructuring and it works in the following way:

a, b = b, a

Though at a high level it may seem that both the statementsa = bandb = aare only running simultaneously, there’s actually more going on under the hood.
 
Essentially, the right-hand side is evaluated first, convertingb,ainto a tuple. Subsequently, the tuple is unpacked and assigned to the left-hand side in the order left to right.
 
Consider the following snippet that updates the values ofaandb.

a = 1  
b = 2  
a, b = a + b, a

Itwon’tgive you a value 3 for bothaandb. Instead, the value ofbafter the assignment would be 1 whileabecomes 3.
 
The destructuring assignment allows us to swap more than two variables as well:

a,b,c = c,a,b

Unpacking Tuples And Iterables

Now, from what we’ve explored in the previous section, multiple assignments are fairly straightforward in Python. We can do it in a few ways:

a = b = c = 10  
a, b = 2, 3

The latter one is a destructuring assignment that works by creating a tuple of the right-hand side.
 
So, taking a cue from it we can opt to unpack values by using a tuple on the left-hand side as shown below:

a, b = (2, 3)  
(a, b) = 2, 3

xy = 1, 2  
a, b = xy

Destructuring assignment isn’t just restricted to tuples. In fact, you could use it on lists, strings, and pretty much any iterable.

a, b = [2,3]
names = "How are you?"  
a,b,c = names.split()

Using destructuring to split a string is really powerful but you need to ensure that the number of values on the left side must exactly match the number of values on the right side to avoidValueError

Recursive unpacking to access nested elements

We can unpack nested elements from lists or tuples with the same destructuring assignment syntax.

(a, b) = ([1, 2, 3], [1, 2])
([a, b], c) = ([1, 2], 3)
[a, b] = [[1,2],[3,4]]

Trailing comma

Trailing commas are optional for two or more element tuples. In case you’re using a single element tuple or you need to unpack only one element you can use the trailing comma in the following ways:

x = (1) #prints 1
x = (1,) #prints (1,)
a, = 1,
a, = [1]
[a] = [1]
#all print a == 1
a, = [1,2]
#ValueError too many elements

While parentheses are just syntactic sugar, it’s the comma that actually tells Python that the data type is a tuple. At times trailing comma can be missed which makes[a] = myLista better bet in terms of readability.

Access Elements Without Indices

Another place where the destructuring assignment is incredibly useful is when accessing values from a list. Without destructuring, more often than not, you’d end up accessing elements using indexes which can be needless.
 
By using the destructuring syntax in theenumeratefunction below, we’re able to access elements in a way that’s more concise and easy to comprehend.

myList = ["a", "b", "c"]  

for i, element in enumerate(myList):  
print(counter, element)

By following the same approach we can destructure dictionaries into key-value pairs or a list of tuples as shown below.

listOfTuples = [(1,0),(0,1)]
for i, (p1, p2) in enumerate(listOfTuples):
      #do your magic here

Select Or Omit Certain Values

By using the * operator (introduced in Python 3), we can choose to pick an optional unknown number of elements as shown below:

*a, b = 1, 2, 3, 4
# a is [1,2,3]
#b is 4

The asterisk operator creates a new list of all but the last element and puts them ina. This is useful when you need to extract sublists in Python.
 
We can also use the asterisk operator for getting the first and last elements from a list or even packing values and pass them into a function — which helps us get rid of long parameter lists in functions.

def sum(a, b, c):
    return a + b + c

x = (1, 2, 3)
print(result(*x))

Note: Dictionary unpacking requires**.
 
On the contrary, if you’re looking to drop or ignore the value in destructuring assignments using the_syntax is the way to go.*_lets you omit an unknown number of values.

a, _ = [1, 2]
first, *_, last = "Hello"
#first is 'H'
#last is 'o'

Conclusion

So, we saw how destructuring syntax helps in swapping and multi-assignments as well as in unpacking tuples, iterable and accessing elements without indexes in a for a loop.
 
I hope you make good use of the destructuring syntax in Python.
 
That’s it for this one. Thanks for reading.

프로그래밍 공부를 하다보면 오픈 API에 요청보내고 응답받는 일을 굉장히 많이 하게 된다.

 

그러다보면 CORS 에러도 필연적으로 만나게 되는데, 대체 CORS란 무엇인가?

 

 

 

 

이미지 출처: https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS

 

 

 

Cross-Origin Resource Sharing (CORS)

많은 웹 어플리케이션들이 이미지 파일이나 폰트, css 파일 같은 리소스들을 각각의 출처로부터 읽어온다. 만약에 웹 어플리케이션이 자기 자신이 속하지 않은 다른 도메인, 다른 프로토콜, 혹은 다른 포트에 있는 리소스를 요청하면 웹 어플리케이션은 cross-origin HTTP 요청을 실행한다. 만약에 프론트엔드 자바스크립트 코드가 https://domain-a.com에서 https://domain-b.com/data.json으로 json 데이터 요청을 보낸다면 이것이 바로 cross-origin HTTP 요청이 된다.

 

이 때 브라우저는 보안상의 이유로 스크립트 안에서 시작되는 cross-origin HTTP 요청들을 제한한다. 예를 들어서 XMLHttpRequest와 Fetch API는 동일 출처 원칙이 적용된다. 이 말은 웹 어플리케이션이 자기가 로드되었던 그 origin과 동일한 origin으로 리소스를 요청하는 것만 허용한다는 뜻이다. 다른 origin으로부터의 응답이 올바른 CORS header를 포함하지 않는다면 말이다.

 

 


 

정리해서 말하면 이렇다.

 

다른 도메인의 img파일이나 css파일을 가져오는 것은 모두 가능하다. 그러나 <script></script>로 감싸여진 script에서 생성된 Cross-site HTTP Request는 Same-origin policy를 적용받아 Cross-site HTTP Request가 제한된다.

 

동일 출처로 요청을 보내는 것(Same-origin requests)은 항상 허용되지만 다른 출처로의 요청(Cross-origin requests)은 보안상의 이유로 제한된다는 뜻이다. 그러나 AJAX가 널리 퍼지면서 <script></script> 안의 스크립트에서 생겨난 XMLHTTPRequest에서도 Cross-site HTTP Requests의 필요성이 매우 커졌다. 그러나 W3C에서 CORS라는 이름으로 새로운 표준을 내놓게되었다.

 

 

즉, 기존에는 보안상의 이유로 XMLHttpRequest가 자신과 동일한 도메인으로만 HTTP 요청을 보내도록 제한하였으나 웹 개발에서 다른 도메인으로의 요청이 꼭 필요하게 되었다. 그래서 XMLHttpRequest가 cross-domain을 요청할 수 있도록 CORS라는 것이 만들어졌다!

 


 

Preflight request

 

이미지 출처: https://www.popit.kr/cors-preflight-%EC%9D%B8%EC%A6%9D-%EC%B2%98%EB%A6%AC-%EA%B4%80%EB%A0%A8-%EC%82%BD%EC%A7%88/

 

 

W3C 명세에 의하면 브라우저는 먼저 서버에 Preflight request(예비 요청)를 전송하여 실제 요청을 보내는 것이 안전한지 OPTIONS method로 확인한다. 그리고 서버로부터 유효하다는 응답을 받으면 그 다음 HTTP request 메소드와 함께 Actual request(본 요청)을 보낸다. 만약 유효하지 않다면 에러를 발생시키고 실제 요청은 서버로 전송하지 않는다.

 

이러한 예비 요청과 본 요청에 대한 서버의 응답은 프로그래머가 구분지어 처리하는 것은 아니다. 프로그래머가 Access-Control- 계열의 Response Header만 적절히 정해주면 Options 요청으로 오는 예비 요청과 GET, POST, PUT, DELETE 등으로 오는 본 요청의 처리는 서버가 알아서 처리한다.

 

 

Preflight request는 GET, HEAD, POST외 다른 방식으로도 요청을 보낼 수 있고 application/xml처럼 다른 Content-type으로 요청을 보낼 수도 있으며, 커스텀 헤더도 사용할 수 있다.

 

 

 

Simple Request

어떤 요청들은 CORS Preflight Request(사전 요청)을 발생시키지 않습니다. 보통 이런 요청들을 Simple Request라고 한다. 

Simple Request의 경우에는 아래 3가지 조건이 모두 만족되는 경우를 말한다.

 

  • GET / HEAD / POST 중 한 가지 메소드를 사용해야 한다.
  • User agent에 의해 자동으로 설정되는 헤더(Connection, User-Agent 또는 Fetch spec에서 '금지된 헤더 이름(forbidden header name)'으로 정의된 이름들을 가진 헤더들)을 제외하고, Fetch spec에서 'CORS-safelisted request-header'라고 정의되어있고 수동 설정이 허용된 헤더들은 다음과 같다.
  • 오직 아래의 Content Type만 지정해야 한다.
    • application / x-www-form-urlencoded
    • multipart/form-data
    • text/plain

 

 

 

Request with Credential

HTTP Cookie와 HTTP Authentication 정보를 인식할 수 있게 해주는 요청이다. 기본적으로 브라우저는 Non credential로 설정되어있기 때문에 credentials 전송을 위해선 설정을 해주어야 한다. 

 

Simple Credential Request 요청 시에는 xhr.withCredentials = true를 지정해서 Credential 요청을 보낼 수 있고, 서버는 Response Header에 반드시 Access-Control-Allow-Credentials: true를 포함해야한다.

 

 

또한 서버는 credential 요청에 응답할 때 반드시 Access-Control-Allow-Origin 헤더의 값으로 "*"와 같은 와일드카드 대신 구체적인 도메인을 명시해야한다.

 

 

 

 

CORS 에러를 피하는 방법

MDN 문서를 읽어보면 다음과 같은 문장이 있다.

 

  • Modern browsers use CORS in a APIs such as XMLHttpRequest or Fetch to mitigate the risks of cross-origin HTTP requests.
  • 모던 브라우저들은 cross-origin HTTP 요청들의 위험성을 완화하기 위해 XMLHttpRequest 또는 Fetch와 같은 APIs에서 CORS를 사용한다.

 

즉, CORS는 브라우저가 사용하는 것이다. 그러므로 서버에서 서버로 보내는 요청은 CORS가 적용되지 않는다.

그러므로 프록시 서버를 추가로 만들어서 클라이언트에서 우리가 새로 만든 프록시 서버로 요청을 보내고 프록시 서버에서 원하는 타겟 서버에 요청을 보내면 브라우저가 개입되지 않았기 때문에 CORS 오류를 회피할 수 있다.

 

출처

https://im-developer.tistory.com/165 [Code Playground]

What do the three dots (…) mean in JavaScript?

Adrian Oprea

Aug 28, 2018·3 min read


 
By OtterGFDLorCC-BY-SA-3.0, from Wikimedia Commons  

Originally published athttps://oprea.rocks/blog/what-do-the-three-dots-mean-in-javascript/
 
The title of the article is froma question I was asked to answer on Quora. Below, is my attempt to explain what do the three dots do in JavaScript. Hopefully, this removes the fog around the concept, for people who will find this article in the future and have the same question.

Array/Object spread operator

Assume you have the following object:

const adrian = { 
    fullName: 'Adrian Oprea', 
    occupation: 'Software developer', 
    age: 31, 
    website: 'https://oprea.rocks'
};

 
Let’s assume you want to create a new object(person) with a different name and website, but the same occupation and age.
 
You could do this byspecifying only the properties you want and use the spread operator for the rest, like below:  

const bill = { 
    ...adrian,
    fullName: 'Bill Gates', 
    website: 'https://microsoft.com'
};

 
What the code above does, is to spread over theadrianobject and get all its properties, then overwrite the existing properties with the ones we're passing. Think of this spread thing as extracting all the individual properties one by one and transferring them to the new object.
 
In this case, since we specified thefullNameandwebsiteproperties after the spread operator kicked in, the JavaScript engine is smart enough to know that we want to overwrite the original values of those properties that are coming from the original object.
 
Simply saying, three dots operator is deep-cloning object. Let's compare shallow cloning and deep-cloning
 


 
The above code shows that {...adrian} = JSON.parse(JSON.stringfy(adrian))
 
It’s a similar situation with arrays. Except that instead of spreading over keys and values, the operator spreads indexes and values. Unlike object spread, where you won’t have duplicate properties because that’s just how JavaScript objects work (you can’t have an object with twofullNameproperties), with arrays you may end up with duplicate values, if you plan to implement something similar to our object example.
 
This means that the code below will result in you having an array with duplicate elements.  

const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [ ...numbers1, 1, 2, 6,7,8]; // this will be [1, 2, 3, 4, 5, 1, 2, 6, 7, 8]

 
Think of it as a replacement forArray.prototype.concat.

Rest operator

When used within the signature of a function, where the function’s arguments should be, either replacing the arguments completely or alongside the function’s arguments, the three dots are also called the rest operator.
 
When it is used like that, the rest operator enables the developer to create functions that can take an indefinite number of arguments, also called functions of variable arity or variadic functions.
 
Here’s the simplest example of such a function. Let’s assume you want to create a function that calculates the sum of all its arguments. Note that it’s not the sum of two, three or four numbers but the sum of all the numbers the function would receive as arguments.
 
Here is a naive implementation, using the rest operator  

function sum(...numbers) { 
    return numbers.reduce((accumulator, current) => { 
        return accumulator += current });
    };

sum(1, 2) // 3
sum(1, 2, 3, 4, 5) // 15

 
The simplest explanation would be that the rest operator takes the arguments that a function receives and dumps them into a real array that you could later use.
 
You might argue that you can do this by requesting the user to pass an array of numbers. That’s technically doable but poor UX for your API, since users expect to call a sum function with plain numbers, not a list of numbers.
 
You might also argue that you could use theargumentsarray. That’s also true but be careful,argumentsis not a real array but an array-like object(an object with a length property). It actually looks like this, for the first call of our sum function, in the previous example:  

{ '0': 1, '1': 2, 'length': 2}

 
To manipulate this object and use array methods on it such as reduce, from my previous example, you’d have to do theArray.prototype.slice.call(arguments, 0)thing. That doesn’t perform well, in terms of speed and memory usage and is not elegant. It’s just smartass code that’s prone to confuse your junior colleagues.
 
This should be everything you need to know to be productive with the rest/spread operator in JavaScript.
 
If you have any advice on how to improve the article, or you need some help wrapping your head around the concept, leave a comment, below.
 
Keep an open mind!
 
Adrian.

'C Lang > JS Basic' 카테고리의 다른 글

자바스크립트에서 물음표? 의 활용  (0) 2020.05.25
javascript error문법  (1) 2020.03.04
Arrow function사용법, 주의사항  (0) 2020.02.13
함수형 프로그래밍이란 무엇인가?  (0) 2020.02.03
함수형 프로그래밍?  (0) 2020.02.03

intro

This is a quick one about how to configure Python’s build in logging system using an ini file.
Our application needs to import logging and logging.config to be able to use ini files.

import logging, logging.config
    # set up logging
    logging.config.fileConfig("log.ini")
    logger = logging.getLogger('sLogger')

    # log something
    logger.debug('debug message')
    logger.info('info message')
    logger.warn('warn message')
    logger.error('error message')
    logger.critical('critical message')

log.ini

log.ini looks like this:

[loggers]
keys=root,sLogger

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=fileFormatter,consoleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_sLogger]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=sLogger
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=consoleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=fileFormatter
args=('logfile.log',)

[formatter_fileFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

[formatter_consoleFormatter]
format=%(levelname)s - %(message)s
datefmt=

output

This config will write warning or above out to stdout, whilst writing everything to a logfile. e.g.

$ python app.py
WARNING - warn message
ERROR - error message
CRITICAL - critical message

And in the logfile:

$ cat logfile.log
2015-11-18 15:23:39,071 - sLogger - DEBUG - debug message
2015-11-18 15:23:39,071 - sLogger - INFO - info message
2015-11-18 15:23:39,071 - sLogger - WARNING - warn message
2015-11-18 15:23:39,071 - sLogger - ERROR - error message
2015-11-18 15:23:39,071 - sLogger - CRITICAL - critical message

quatation

+ Recent posts