LoginSignup
1
0

More than 3 years have passed since last update.

net/httpメモ

Last updated at Posted at 2021-02-16

net/httpメモ 

目次

net/httpの概要

サーバとしての機能を備えている必須パッケージ。
ハンドラは勿論、
リクエストからデータを取り出すためのリクエストを表す各種構造体を提供してくれていたり、レスポンスを返すためのインターフェースも実装している。

Request編

httpリクエスト、レスポンスの構造について

  • リクエストもレスポンスも構造は基本的に同一で、順に
    • リクエスト行、またはレスポンス行
    • 0行以上のヘッダ
    • 空行
    • 任意指定のメッセージボディ

で構成されている

  • curl --verbose https://www.kyoto-u.ac.jp/jaでリクエストヘッダを見ると

GET /ja HTTP/1.1
Host: www.kyoto-u.ac.jp
User-Agent: curl/7.55.1
Accept: /
(空行)

となっており確かに基本構造に則っている

Request構造体について

  • httpパッケージにはHTTPリクエストについての情報を保持するための、そしてそれらを解析するための構造体Requestが存在している

  • Requestの重要な構成要素は次のようなものがある

    • URL
      • リクエスト行のURLに関する情報を保持する構造体
    • Header(ヘッダ)
      • リクエストヘッダ内のキーとバリューを保持
      • type Header map[string][]stringで定義されたマップ
    • Body(ボディ)
      • リクエストボディ内のキーとバリューを保持
      • io.ReadCloserであり、ReadメソッドとCloseメソッドを実装している
    • Form、PostForm、MultipartForm
      • URLかボディ、またはその両方からのパラメータを保持する(ただし読み取るには専用のメソッドでこれらのフィールドを更新する必要がある)
      • mapである(ただしMultipartFormはmapを二つ要素に持つ構造体)

パラメータを受け取る様々なメソッドとそれらの特徴

既にRequest構造体の重要な構成要素としてForm,PostForm,MultipartFormという三つのフィールドを伝えた。
Form,PostFormはキーが文字列、バリューが文字列のスライスであるマップで、
後で分かることだが前者はURLパラメータとボディパラメータの両方を含み、後者は(名前から分かることだが)ボディパラメータのみを含むことになる。
MultipartFormはForm,PostFormと同様のマップと、それに加えてファイルを格納するためのマップも備えた構造体だ。
マルチパートという特殊なコンテンツタイプを持つボディパラメータにのみ対応している。

  • 以上三つのフィールドにパラメータを格納するには、それぞれ特定のメソッドが必要になってくる

  • またそれらとは別に、一つずつパラメータを取り出すのに特化したメソッドが二つ存在するのでそれにも触れていく

補足

  • ボディパラメータのコンテンツタイプ、具体的には、実際のHTMLフォームのenctypeで指定されているのは通常URLエンコードかマルチパートの二種類

ParseForm

  • func (r *Request) ParseForm() error
  • ParseForm は URL から生のクエリを解析し、r.Form を更新する

  • ボディにパラメータが存在すればボディも読み込み対象となり結果を r.PostForm と r.Form の両方に入れる。

  • r.Formにおいて、ボディのパラメータは URLパラメータよりもよりも優先される。
    つまり、(r.Formは文字列:文字列のスライス のマップなので)同じキー名のバリューがURL、ボディそれぞれに存在していたらそのキーに対応するバリューのスライスの先頭にくるのはボディパラメータの方ということ

  • このことはFormValueで効いてくる

  • でもキー名が同名のパラメータがGET、POSTで同時に渡される機会なんてあるの?無さそう

ParseMultipartForm

  • func (r *Request) ParseMultipartForm(maxMemory int64) error

  • 引数で指定したバイト数分だけマルチパートのボディパラメータを取得し、MultipartForm構造体の一つ目のマップに格納する

  • MultipartForm構造体の二番目のマップにはファイルを格納することになる

FormValue

  • func (r *Request) FormValue(key string) string

  • これまでのパラメータを取り出す例では、

    1. ParseForm等のメソッドを呼び出し
    2. Form等にパラメータを格納する
  • といった二段階の処理をこちらで行っていたが、
    FormValueメソッド、そして次に紹介するPostFormValueメソッドではその必要がない

  • FormValueは必要に応じてParseMultipartFormとParseFormを呼び出し、 Formからキーに対応したバリューを一つだけ返す

PostFormValue

  • func (r *Request) PostFormValue(key string) string

  • PostFormValueは必要に応じてParseMultipartFormとParseFormを呼び出し、 PostFormからキーに対応したバリューを一つだけ返す

ファイルをアップロードする

  • ボディパラメータのコンテンツタイプがURLエンコードかマルチパートの二種類であることは説明した

  • 一般的にはURLエンコードが多いが、特定の場合にマルチパートが使われる

  • その特定の場合とはファイルのアップロードを目的としたリクエストの場合である

  • ファイルのアップロードができるHTMLは次のようになっている

<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html;
charset=utf-8" />
 <title>Go Web Programming</title>
 </head>
 <body>
 <form action="http://localhost:8080/process"
method="post" enctype="multipart/form-data">
 <input type="file" name="uploaded">
 <input type="submit">
 </form>
 </body>
</html>
  • http://localhost:8080/processにPOSTメソッドで(method="post")コンテンツタイプをマルチパートとして(enctype="multipart/form-data")ファイルをsubmitしている

  • ハンドラの方は次の通り

package main
import (
 "fmt"
 "io/ioutil"
 "net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
 r.ParseMultipartForm(1024)
 fileHeader := r.MultipartForm.File["uploaded"][0]
 file, err := fileHeader.Open()
 if err == nil {
    data, err := ioutil.ReadAll(file)
    if err == nil {
        fmt.Fprintln(w, string(data))
        }
    }
}
func main() {
 server := http.Server{
 Addr: "127.0.0.1:8080",
  }
 http.HandleFunc("/process", process)
 server.ListenAndServe()
}
  • 何度も出ていることだがMultipartFormは構造体で、マルチコンテンツのパラメータを保持するマップとは別にファイルを保持するマップを要素に持っている

  • r.ParseMultipartForm(1024)
    fileHeader := r.MultipartForm.File["uploaded"][0]
    file, err := fileHeader.Open()
    メソッドParseMultipartFormを呼び出すと、MultipartFormフィールドのFileフィールドからFileHeaderを取得し、そのOpenメソッドを呼び出してファイルを取得している

  • data, err := ioutil.ReadAll(file)
    if err == nil {
    fmt.Fprintln(w, string(data))
    }
    サーバにファイルを送るとハンドラがそのファイルを受け取ってバイト配列に読み込み、それをブラウザ上に表示する

image.png

image.png

  • さて、MultipartFormをより具体的に見るとこんな構造をしている
type Form struct {
    Value map[string][]string
    File  map[string][]*FileHeader

type FileHeader struct {
    Filename string
    Header   textproto.MIMEHeader
    Size     int64 // Go 1.9
    // contains filtered or unexported fields
}
  • 試しにFileHeaderの値が見たいのでハンドラ関数を変更する
func process(w http.ResponseWriter, r *http.Request) {
    r.ParseMultipartForm(1024)
    fileHeader := r.MultipartForm.File["uploaded"][0]
    fmt.Fprintln(w, fileHeader)
}
  • アクセスするとこんな感じになる

image.png

  • 送られるファイルが一つの時は、FormValueメソッドのみたくFormFileという簡単なメソッドがあるのでそちらがおすすめ
func process(w http.ResponseWriter, r *http.Request) {
    file, _, err := r.FormFile("uploaded")
    if err == nil {
        data, err := ioutil.ReadAll(file)
            if err == nil {
            fmt.Fprintln(w, string(data))
            }
    }
}

Response編

http.ResponseWriterと各メソッド

  • ハンドラ関数は必ずfunc(w http.ResponseWriter, r *http.Request)という形をとる。これは今まで例で見てきたとおり

  • Responseを返すにはハンドラ関数に第一引数として渡るhttp.ResponseWriterに内容を書き込んでいることが分かる

  • ResponseWriterは名前の通り、Writerメソッドを実装し、io.Writer型である。これはfmt.Fprint()が使えることからも分かる

  • また、ResponseWriterはio.Writer型であると同時に、独自のメソッドを要素に持つインターフェースである。

type ResponseWriter interface {

    Header() Header
    // type Header map[string][]string

    Write([]byte) (int, error)

    WriteHeader(statusCode int)
}
  • ここで説明する ResponseWriter interfaceのメソッドは

    • Write([]byte) (int, os.Error)
    • Header() Header
    • WriteHeader(statusCode int) の三種
  • Write([]byte) (int, os.Error)

    • 既に述べたとおりio.Writerのメソッド。 ResponseWriter.Write(バイトスライス)の形でレスポンス内容を流し込むのにつかえる。 そのうえio.Writer型を実装できるのでその他便利関数も使えるようになる
    • 内容はレスポンスのボディに書き込まれる
  • Header() Header

    • こちらで変更可能なヘッダのマップを返す(ヘッダはキー:バリューのマップに相応しい形式)
    • 返値のマップ.set("キー名":"バリュー")とすることでヘッダに追加できる
  • WriteHeader(statusCode int)

    • ステータスコードを引数にとり、それをレスポンスのステータスコードへ書き込む

参考

Goプログラミング実践入門 ―標準ライブラリでゼロからWebアプリを作る―
gihyo.jp - 第4章 標準パッケージ―JSON,ファイル,HTTP,HTMLを扱う
公式doc

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0