要ログインのWebページにコマンドラインからアクセスしてみる - Webログインの簡単な解説 -

# はじめに

この記事ではcurlというコマンドをを使用して、LinuxのコマンドラインでWebページから情報を取得する方法を紹介します。

また、ログインが必要なページにおいて、情報を取得したり、送信したりする方法も合わせて紹介したいと想います。

私自身、Webの技術については初心者ですが、この内容を調べていく中で得るものが多かったので、Webの基礎知識を学ぶきっかけになれば幸いです。

# 環境

この記事では、Flask という Webフレームワークを使用した簡単なWebアプリをローカルで作成し、そのページに対して`curl`コマンドを投げていきます。

[Welcome | Flask (A Python Microframework)] (http://flask.pocoo.org/)

その他に、ブラウザも使用します。私が使用した環境は以下となります。

- Python 3.6.2
- virtualenv （お好みで）
- Firefox 57
- curl 7.54.0

## サンプルWebアプリの起動と終了

`curl` のアクセス先となるサンプルのWebアプリをローカルで立ち上げます。ソースはGithubにアップしています（Flask の Tutorial そのまんまです…手抜きですみません）。

[eup42/Flaskr: flask tutorial application](https://github.com/eup42/Flaskr)

`virtualenv` を使用するとシステムの環境を汚さずにWebアプリを立ち上げられます（無い場合はインストールしてください）。以下のコマンドでWebアプリのインストールと起動が行なえます。

```bash
# Install
git clone git@github.com:eup42/Flaskr.git
cd Flaskr
virtualenv .
source ./bin/activate
pip install --editable .

# Run Application
flask initdb
export FLASK_APP=flaskr
flask run
```

http://localhost:5000/ にアクセスするとこんな画面が表示されるはず。

![Screenshot-2018-1-16 Flaskr.png](https://qiita-image-store.s3.amazonaws.com/0/96666/32355ed9-112a-d050-d611-ff71d361ac63.png)

<dl>
  <dt>Username:</dt> <dd>admin</dd>
  <dt>Password:</dt> <dd>default</dd>
</dl>

でログインできるので、遊んでみてください（記事の投稿みたいなことしかできませんが…）


# Webの基本的な仕組みと curl について
準備が一通り終わったところで、Webの基本的な仕組みに触れたいと思います。

私達が普段ブラウザからWebページにアクセスしたときには、**HTTP**という通信方式でデータのやり取りがなされます。どういう通信方式かというと

1. クライアントからサーバーに**リクエスト**を送る
2. サーバーからクライアントに**レスポンス**を返す

という、非常に単純なものです。ブラウザを使っているとあまり意識はしませんが、`curl`を使ってみるとこの**リクエスト**と**レスポンス**のやり取りが良く分かります。

以下のコマンドを実行してGoogleにアクセスしてみます。`-v`は詳細表示をするオプションです。

```
curl -v www.google.com                                                                        
```

コマンドを実行すると下のような結果になると思います。

```
* Rebuilt URL to: www.google.com/                          
*   Trying 216.58.197.4...                                 
* TCP_NODELAY set                                          
* Connected to www.google.com (216.58.197.4) port 80 (#0)  
> GET / HTTP/1.1                                           
> Host: www.google.com                                     
> User-Agent: curl/7.54.0                                  
> Accept: */*                                              
>                                                          
< HTTP/1.1 302 Found                                       
< Cache-Control: private                                   
< Content-Type: text/html; charset=UTF-8                   
< Referrer-Policy: no-referrer                             
< Location: http://www.google.co.jp/?gfe_rd=cr&dcr=0&ei=h2FnWu3KF-fZ8Af9q5qwCQ                                         
< Content-Length: 271                                      
< Date: Tue, 23 Jan 2018 16:23:35 GMT                      
<                                                          
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">                                         
<TITLE>302 Moved</TITLE></HEAD><BODY>                      
<H1>302 Moved</H1>                                         
The document has moved                                     
<A HREF="http://www.google.co.jp/?gfe_rd=cr&amp;dcr=0&amp;ei=h2FnWu3KF-fZ8Af9q5qwCQ">here</A>.                         
</BODY></HTML>                                             
* Connection #0 to host www.google.com left intact
```

結果のうち、以下の部分が`www.google.com`に対する**リクエスト**になります。

```
> GET / HTTP/1.1                                           
> Host: www.google.com                                     
> User-Agent: curl/7.54.0                                  
> Accept: */*                                              
>       
```

**GET**と書いてあるのは、リクエストの種類のことで、他には**POST**や**PUT**などがあり、一般に**メソッド**と呼ばれます。2-4行目はヘッダフィールドと呼ばれます。

次に以下の部分がサーバーからのレスポンスになります。

```
< HTTP/1.1 302 Found                                       
< Cache-Control: private                                   
< Content-Type: text/html; charset=UTF-8                   
< Referrer-Policy: no-referrer                             
< Location: http://www.google.co.jp/?gfe_rd=cr&dcr=0&ei=h2FnWu3KF-fZ8Af9q5qwCQ                                         
< Content-Length: 271                                      
< Date: Tue, 23 Jan 2018 16:23:35 GMT                      
<                                                          
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">                                         
<TITLE>302 Moved</TITLE></HEAD><BODY>                      
<H1>302 Moved</H1>                                         
The document has moved                                     
<A HREF="http://www.google.co.jp/?gfe_rd=cr&amp;dcr=0&amp;ei=h2FnWu3KF-fZ8Af9q5qwCQ">here</A>.                         
</BODY></HTML>
```
1行目の数字3桁はステータスコードと呼ばれ、リクエストの結果を示しています。ここの例の**304**とは、「ページは別のところに移動しちゃってもうありませんよ」を表しています。他のコード例としては、**200**の**リクエスト成功**や**404**の**ページが見つかりません**などがあります。

2-7行目はヘッダフィールドになります。例えば、**Location:**にはページの移動先が記載されています。

9行目以降が、ボディ部で実際に送られてきたデータになります。この部分がブラウザによって解釈され画面に表示されます。

さて、ここでもう一つオプションを紹介します。ステータスコードで**304**が帰ってきたときに、**Location:**に記載されているアドレスに自動で**GET**リクエストを投げてくれるオプションがあります。

```
curl -v -L www.google.com
```

上記を実行すると、結果が変わり、2回リクエストとレスポンスのやり取りがなされていることがわかると思います。

さて、実はブラウザでも同様にHTTP通信のやり取りを見ることができます。Firefoxの場合、*右上メニュー > ウェブ開発 > ネットワーク* とクリックすると、ブラウザ下に開発ツールが開かれます。

この状態で、`www.google.com`にアクセスしてみると以下のようになるかと思います（画像は１つのリクエストをクリックした状態）。

<img width="1440" alt="スクリーンショット 2018-01-24 02.11.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/96666/98d507b4-dffb-7213-b2ac-a1bbf14aa0a1.png">

ここで、左下の表の１行は「1つのリクエストとそれに対するレスポンス」に当たります。それぞれにステータスコードが振られていることが分かります。

たくさんのリクエストが送信されているのは、１つ目のレスポンスの中のHTMLに画像やjavascriptを取得する命令があったため、ブラウザがそれを解釈して次々とリクエストを送信しているためです。

それぞれのリクエストをクリックするとヘッダ情報などの詳細を見ることができます。


# `curl` を使ってWebページにログイン・記事の投稿をしてみる

さて、遅くなりましたがやっと本題に入ります。今までやってきたように`curl`でWebページの情報を取ってくるだけであれば比較的簡単にできます。

では、ログインが必要なページから情報を取ってきたりするにはどうしたら良いのでしょうか。実際に、サンプルのWebアプリを使って、`curl`からログイン、記事の投稿までやってみたいと思います。


## ログインとは何か
実施に開発者ツールを開きながらログインを実行してみましょう。

先程の開発者ツールのネットワークを開いた状態で、Flaskr のログインページ（ http://localhost:5000/login ）にて、Username, Password を入力して Login ボタンを押してみます。すると以下の画像のようになるかと思います。

<img width="1552" alt="スクリーンショット 2018-01-27 21.32.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/96666/6c965d95-e4ca-110d-4f5d-61880dbfc89e.png">

大事なのはLoginをクリックした直後の通信ですので、一番上の通信を見てみましょう。クリックしてみると詳細が出てきます。

<img width="566" alt="スクリーンショット 2018-01-27 21.37.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/96666/6578cb6f-39a2-f65f-a521-3c86d5a7992c.png">

- リクエスト要求URL は http://localhost:5000/login
- リクエストメソッドは POST
- レスポンスステータスは 302 （リダイレクト）

となっています。ということは、POST メソッドで、 localhost:5000/login にリクエストを投げれば、ログインできそうです。また、POSTメソッドのときには**パラメーター**タブを見てみましょう。以下のようになっているはずです。

<img width="557" alt="スクリーンショット 2018-01-27 21.43.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/96666/c1aad2c3-f694-85cd-324f-d960502be83b.png">

**:** の左側が **name** 、右側が **value** と呼ばれます。今回は

|  name      |    value  |
|:----------:|----------:|
| usename    |     admin |
| password   |   default |

というデータがPOSTメソッドで送られていることになります。


## `curl` でログインをしてみる

では、ログインを `curl` を使ってやってみます。基本的には上で見てきたことをやればOKです。

```
curl -X POST -d username=admin -d password=default  localhost:5000/login
```

`-X` はメソッドを指定するオプション、`-d` はPOSTメソッドで送信するデータの指定になります（実は`-X` オプションなくても`curl`が `-d` のオプションを見てPOSTメソッドと判断してくれるので、`-X` はなくてもok）。

結果は以下のようになるはずです。

```
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 5000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST /login HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 31
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 31 out of 31 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 302 FOUND
< Content-Type: text/html; charset=utf-8
< Content-Length: 209
< Location: http://localhost:5000/
< Set-Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsibWVzc2FnZSIsIllvdSB3ZXJlIGxvZ2dlZCBpbiJdfV0sImxvZ2dlZF9pbiI6dHJ1ZX0.DU75rA.BuEZtq3Qel26kKYqDiN5LdTNolk; HttpOnly; Path=/
< Server: Werkzeug/0.14.1 Python/3.6.2
< Date: Sun, 28 Jan 2018 06:05:32 GMT
< 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
* Closing connection 0
<p>You should be redirected automatically to target URL: <a href="/">/</a>.  If not click the link.
```

ログインの成功有無が分かり辛いですが、リダイレクトが発生しているので成功していそうです。パスワードを間違えると再度ログインページが表示されます。これはブラウザで見たときの挙動とも合致しています。


## `curl` でログインした状態を保持する

さて、Flaskr にログインしたあとで、トップページにアクセスすると以下の画像のように投稿画面が表示されます。右上に　*log in* と表示されていたものも *log out* になっています。

<img width="1552" alt="スクリーンショット 2018-01-28 15.08.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/96666/290827d8-440b-f41c-d8ed-6f250a1ffc75.png">

`curl` で先述のログインするコマンドを打った後で、トップページにアクセスしてみるとどうでしょうか。

```
curl -X POST -d username=admin -d password=default  -v localhost:5000/login && curl -v localhost:5000
```

結果は...

```html
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="/static/style.css">
<div class=page>
  <h1>Flaskr</h1>
  <div class=metanav>
  
    <a href="/login">log in</a>
  
  </div>
  
  
  
  <ul class=entries>
  
    <li><em>Unbelievable.  No entries here so far</em>
  
  </ul>

* Closing connection 0
</div>%
```
レスポンスで帰ってきた、HTMLを見る限り何も変わっていません。

さて、何故か。実は、HTTP のプロトコルは**ステートレス・プロトコル**と呼ばれるものの一つで、「一つのリクエストとレスポンスの組」が前後の組とは独立しており、サーバー側にログインの状態などが保存されません。

[ステートレス・プロトコル - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E3%83%AC%E3%82%B9%E3%83%BB%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB)

そこで、**セッション** という考え方を使って、ログイン状態を管理します。**セッション**の詳しい説明は他の記事におまかせしたいともいます。

[Cookieとセッションをちゃんと理解する - Qiita](https://qiita.com/hththt/items/07136ad74127999df271)
[今さら聞けないセッションとCookie、ログイン・ログアウト(Rails編) - Qiita](https://qiita.com/SpicyCoffee/items/de9de9a5427adf81817a)

簡単に言うと **Cookie** という仕組みを使用し、ログイン時にクライアント側にセッションIDを保存し、次のアクセス時に、そのセッションIDをサーバーに渡して、ログインしているかどうかを管理しているのです。

では、`curl` で Cookie を使ってFlaskrにログインしてみましょう。

```shell
# Flaskr にログインし、Cookie をローカルに保存
curl -X POST -d username=admin -d password=default -c cookie -v localhost:5000/login

# 保存したCookie をサーバーに渡してアクセスする
curl -b cookie -v localhost:5000
```

1つめのコマンドでは、ログインのコマンドに `-c <filename>` のオプションを追加しました。これは cookie を `<filename>` で保存するオプションです。

2つめのコマンドは、トップページにアクセスするものに `-b <filename>` を追加しました。トップページにアクセスする際に、セッションを維持するためcookieの情報を渡します。

上記のコマンドを打つと、以下のようなhtmlが帰ってきます。

```html
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="/static/style.css">
<div class=page>
  <h1>Flaskr</h1>
  <div class=metanav>
  
    <a href="/logout">log out</a>
  
  </div>
  
    <div class=flash>You were logged in</div>
  
  
  
    <form action="/add" method=post class=add-entry>
      <dl>
        <dt>Title:
        <dd><input type=text size=30 name=title>
        <dt>Text:
        <dd><textarea name=text rows=5 cols=40></textarea>
        <dd><input type=submit value=Share>
      </dl>
    </form>
  
  <ul class=entries>
  
    <li><em>Unbelievable.  No entries here so far</em>
  
  </ul>

* Closing connection 0
</div>
```

なんかログインできてそうですね！


## おまけ: ログインして記事を投稿

ログインはできたところで、この記事の目的は達成です。おまけとして、Flaskr に `curl` を使って記事を投稿してみます。

コマンドのみの紹介になりますが、Firefoxの開発者メニューで記事投稿の際にどういうデーターが送られているのかを解析した上でコマンド化しています。

```shell
# Flaskr にログインし、Cookie をローカルに保存
curl -X POST -d username=admin -d password=default -c cookie -v localhost:5000/login

# 記事を投稿する
curl -X POST -d title=test -d text=test -b cookie -v localhost:5000/add
```

# おわりに
今回、`curl` から始まりログインの仕組みまで調べてみて、普段ブラウザがやってくれていることを少し理解できたんじゃないかと思いました。いやはや、`curl` ってすごい...。

最後に、この記事を通してWebの仕組みに関する知識が少しでも深まるきっかけになれば幸いです。