MultipartパッケージのFileHeaderの
プライベートフィールドに
アクセスする機会があったので、
その時のメモになります。
この手法はFileHeaderだけでなく、
他の構造体でも適用できるので便利です。
まずはFileHeaderの確認です。
1 2 3 4 5 6 7 8 9 10 |
// A FileHeader describes a file part of a multipart request. type FileHeader struct { Filename string Header textproto.MIMEHeader Size int64 // contains filtered or unexported fields content []byte tmpfile string } |
下2つのcontentとtmpfileは、
プライベートフィールドで、
コーディングしている段階では
アクセスできません。
アクセスしようとすると、
未定義でコンパイルエラーになります。
プログラムが動いているときにしか
アクセス出来ないということです。
「ではどうすればいいん?」
という話ですが、
そこでReflectパッケージの登場です。
そう。これを使えば、
何でもアクセス出来てしまいます。
とりあえず全体のコードを見てみましょう。
クライアントが複数のファイルを
サーバ(Go)に送信してきて、
そのファイル情報を取得しようとする
というシナリオです。
クライアント側(HTML)
1 2 3 4 5 6 7 8 9 10 11 |
<html> <head> <title>Upload</title> </head> <Body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="img" multiple> <input type="submit"> </form> </Body> </html> |

サーバ(Go)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
package main import ( "bytes" "fmt" "io" "log" "mime/multipart" "net/http" "net/textproto" "os" "reflect" ) const defaultMaxMemory = 32 << 20 //32MB type FileHeader2 struct { Filename string Header textproto.MIMEHeader Size int64 content []byte tmpfile string } func UploadedHandler(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(defaultMaxMemory) if err != nil { io.WriteString(w, err.Error()) log.Println(err) } fileHeaders := r.MultipartForm.File["img"] for _, fhd := range fileHeaders { file, err := wrapFileHeader(fhd).Open() defer file.Close() if err != nil { log.Println(err) } /* ファイル操作 */ /*contentやtmpfileにアクセス出来ようになっている*/ } } func main() { http.HandleFunc("/upload", UploadedHandler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "root.html") }) if err := http.ListenAndServe("localhost:8080", nil); err != nil { log.Println(err) } } func wrapFileHeader(fhd *multipart.FileHeader) *FileHeader2 { fhd2 := &FileHeader2{ Filename: fhd.Filename, Header: fhd.Header, Size: fhd.Size, } /* content, tmpfile are private field */ rv := reflect.ValueOf(fhd) contentRV := rv.Elem().FieldByName("content") fhd2.content = contentRV.Bytes() tmpfileRV := rv.Elem().FieldByName("tmpfile") fhd2.tmpfile = tmpfileRV.String() fmt.Printf("%+v\n\n", fhd2) return fhd2 } func (fh *FileHeader2) Open() (multipart.File, error) { if b := fh.content; b != nil { r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))) return sectionReadCloser{r}, nil } return os.Open(fh.tmpfile) } type sectionReadCloser struct { *io.SectionReader } func (rc sectionReadCloser) Close() error { return nil } |
まずReflectパッケージの
Valueof()にFileHeader構造体を渡します。
そうすると、
Value型構造体となって戻ってきます。

Value構造体は、ValueOf()に渡した
引数(FileHeader)のコピーみたいなものです。
格納されている値だけでなく、
型情報も含まれています。
次に、Elem().FieldByName("content")で
FileHeaderの中の"content"を指定します。
contentはバイトのスライスですので、
.Bytes()で実際の中身が取得できます。
tmpFileもcontentと同様ですが、
string型ですので最後の部分だけ、
.string() になりますのでご注意下さい。
実はたったこれだけで、
unexpected Field にアクセス出来ます。
その他の記述は、FileHeaderを
自分で再定義しているだけですので、
今回のポイントとは関係ないです。