【Golang】net/httpのPath Parameterパース
net/http
の handleFunc()
handle()
に登録できるパスは Path Parameter を認識しない。例えば、他の Web フレームワークのように、下記 /products/:id
エンドポイント中の :id
を変数として取得することができない。
// :idを変数としてパースできない http.handleFunc("/products/:id", f)
Path Parameter を取得するためには、Path Parameter 抜きのエンドポイントへのリクエストを一旦ハンドルし、そのハンドラの中で Path Parameter をパースする必要がある。
func main() { http.handleFunc("/products/", handleProducts) http.ListenAndServe(":8000", nil) } func handleProducts(r *http.Request, w http.ResponseWriter) { sub := strings.TrimPrefix(r.URL.Path, "/products") _, id := filepath.Split(sub) if id != "" { // :idを使う... } }
/products/:id
リクエストへのハンドラを設定する際には、 /products
ではなく /products/
へハンドラを設定する。 /products
リクエストハンドラは /products/:id
リクエストを受け付けられないため。ただし /products/hoge
/products/hoge/fuga
などの適当なエンドポイントも受け付けてしまうので、404 を返すなど適宜処理する。
備考
Handle()
HandleFunc()
に登録できるパスパターンは、 net/http
中の構造体 ServeMux
のルールに従う。
パターンは、「/ favicon.ico」のような固定されたルート化されたパス、または「/ images /」のようなルート化されたサブツリーに名前を付けます(末尾のスラッシュに注意してください)。長いパターンは短いパターンよりも優先されるため、「/ images /」と「/ images / thumbnails /」の両方にハンドラーが登録されている場合、「/ images / thumbnails /」で始まるパスと前者のハンドラーが呼び出されます。 「/ images /」サブツリー内の他のパスのリクエストを受け取ります。
参考文献
【Golang】Golangの並列実行時の競合状態検出
-race
オプション
Go バイナリ実行時、 -race
オプションを指定することで競合状態のテストを実施することができる。具体的には
go -race run
go -race build
のようにコード実行時、バイナリビルド時に指定できる。
ただし実行可能な環境は linux/amd64、freebsd/amd64、darwin/amd64、windows/amd64 のみ。
Example
Golang の map の非スレッドセーフ性と排他制御の記事で掲載した、map に対する並行アクセスを実行する。
main.go
package main func main() { kvs := NewKeyValueStore() for i := 0; i < 10; i++ { go func(kvs *KeyValueStore) { kvs.set("key", "value") kvs.get("key") }(kvs) } } type KeyValueStore struct { m map[string]string } func NewKeyValueStore() *KeyValueStore { return &KeyValueStore{m: make(map[string]string)} } func (s *KeyValueStore) set(k, v string) { s.m[k] = v } func (s *KeyValueStore) get(k string) (string, bool) { v, ok := s.m[k] return v, ok }
上記コードは map がスレッドセーフで無いために、値書き込み時のデータの競合状態が発生する。このコードを -race
オプションとともに実行すると、競合状態が発生しうることに対する警告が吐き出される。
実行結果
$ go run -race main.go ================== WARNING: DATA RACE Write at 0x00c000088000 by goroutine 7: runtime.mapassign_faststr() /usr/local/go/src/runtime/map_faststr.go:202 +0x0 main.main.func1() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x71 Previous write at 0x00c000088000 by goroutine 6: runtime.mapassign_faststr() /usr/local/go/src/runtime/map_faststr.go:202 +0x0 main.main.func1() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x71 Goroutine 7 (running) created at: main.main() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c Goroutine 6 (finished) created at: main.main() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c ================== ================== WARNING: DATA RACE Write at 0x00c00008c088 by goroutine 7: main.main.func1() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x86 Previous write at 0x00c00008c088 by goroutine 6: main.main.func1() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x86 Goroutine 7 (running) created at: main.main() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c Goroutine 6 (finished) created at: main.main() /Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c ================== Found 2 data race(s) exit status 66
競合が発生しうる場合、上記のように競合が発生するコード上の箇所が表示される。競合検出は実行された処理のみに対して実施され、実行されない処理に競合の可能性があっても検出はされない。
競合状態にならない場合、特に出力はなく実行は終了する。
main.go
package main import ( "fmt" "sync" ) func main() { kvs := NewConcurrentKeyValueStore() for i := 0; i < 10; i++ { go func(kvs *ConcurrentKeyValueStore) { kvs.set("key", "value") kvs.get("key") }(kvs) } } type ConcurrentKeyValueStore struct { m map[string]string mu sync.RWMutex } func NewConcurrentKeyValueStore() *ConcurrentKeyValueStore { return &ConcurrentKeyValueStore{m: make(map[string]string)} } func (s *ConcurrentKeyValueStore) set(k, v string) { s.mu.Lock() defer s.mu.Unlock() s.m[k] = v } func (s *ConcurrentKeyValueStore) get(k string) (string, bool) { s.mu.RLock() defer s.mu.RUnlock() v, ok := s.m[k] return v, ok }
実行結果
$ go run -race main.go // 何も出力されない
パッケージインストール時の競合検出
コード実行・ビルド時だけでなく、外部パッケージをインストールするときにも競合検出を実施することができる。
go get -race [package]
go install -race [package]
参考文献
【Golang】Golang : `runtime` パッケージを使用したモニタリング
Golang のメモリ使用量やゴルーチン数など、実行時ランタイムに関係するステータスを観測するには、 runtime
パッケージを使用する。
Memory
var m runtime.MemStats runtime.ReadMemStats(&m) // ヒープ上に割り当てられたオブジェクト累積メモリ量 fmt.Printf("MAlloc : %v\n", humanize.Bytes(m.Mallocs)) // ヒープ上から開放されたオブジェクト数 fmt.Printf("Frees : %v\n", m.Frees) // ヒープ上に割り当てられたオブジェクトメモリ量 fmt.Printf("Alloc : %v\n", humanize.Bytes(m.Alloc)) fmt.Printf("HeapAlloc : %v\n", humanize.Bytes(m.HeapAlloc)) // ヒープ上に割り当てられたオブジェクトメモリ量。ただし開放されたオブジェクト分も含む fmt.Printf("TotalAlloc : %v\n", humanize.Bytes(m.TotalAlloc)) // OSから割り当てられたプロセスの総メモリ量 // ヒープ + スタック + その他 fmt.Printf("Sys : %v\n", humanize.Bytes(m.Sys)) // ポインタのルックアップ数 fmt.Printf("Lookups : %v\n", m.Lookups) // 到達可能、あるいはGCによって解放されていないヒープオブジェクトメモリ量 fmt.Printf("HeapAlloc : %v\n", humanize.Bytes(m.HeapAlloc)) // 未使用ヒープ領域メモリ量 fmt.Printf("HeapIdle : %v\n", humanize.Bytes(m.HeapIdle)) // 使用中ヒープ領域メモリ量 fmt.Printf("HeapInuse : %v\n", humanize.Bytes(m.HeapInuse)) // OSに返却される物理メモリ量 fmt.Printf("HeapReleased : %v\n", humanize.Bytes(m.HeapReleased)) // ヒープに割り当てられたオブジェクト量 fmt.Printf("HeapObjects : %v\n", m.HeapObjects) // 使用中スタック領域メモリ量 fmt.Printf("StackInuse : %v\n", humanize.Bytes(m.StackInuse)) // OSから割り当てられたスタック領域メモリ量 fmt.Printf("StackSys : %v\n", humanize.Bytes(m.StackSys)) // 割り当てられたmspan構造体バイト数 fmt.Printf("MSpanInuse : %v\n", humanize.Bytes(m.MSpanInuse)) // OSから取得したmspan構造体バイト数 fmt.Printf("MSpanSys : %v\n", humanize.Bytes(m.MSpanSys)) // 割り当てられたmcache構造体バイト数 fmt.Printf("MCacheInuse : %v\n", humanize.Bytes(m.MCacheInuse)) // OSから取得したmcache構造体バイト数 fmt.Printf("MCacheSys : %v\n", humanize.Bytes(m.MCacheSys))
MAlloc : 271 B Frees : 4 Alloc : 179 kB HeapAlloc : 179 kB TotalAlloc : 179 kB Sys : 70 MB Lookups : 0 HeapAlloc : 179 kB HeapIdle : 66 MB HeapInuse : 418 kB HeapReleased : 66 MB HeapObjects : 267 StackInuse : 229 kB StackSys : 229 kB MSpanInuse : 6.1 kB MSpanSys : 16 kB MCacheInuse : 21 kB MCacheSys : 33 kB
Goroutine 数
var m runtime.MemStats runtime.ReadMemStats(&m) // 3 goroutine go func() { time.Sleep(time.Second * 10) }() go func() { time.Sleep(time.Second * 10) }() go func() { time.Sleep(time.Second * 10) }() // 呼び出し時存在するゴルーチン数(main + 3) fmt.Printf("Goroutine : %v\n", runtime.NumGoroutine()) // n-GoroutineProfile : 4
ゴルーチン別メモリプロファイル
ゴルーチン毎のスタックトレースを uint32 配列で返す。
var m runtime.MemStats runtime.ReadMemStats(&m) sr := []runtime.StackRecord{ {}, {}, {}, {}, } n, ok := runtime.GoroutineProfile(sr) fmt.Printf("n-GoroutineProfile : %d\n", n) if ok { for i, p := range sr { fmt.Printf("GoroutineProfile-%d: %v\n", i, p.Stack0) } } // GoroutineProfile-0: [17609957 17607648 16957534 17123361 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] // GoroutineProfile-1: [17610480 17123361 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] // GoroutineProfile-2: [17610560 17123361 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] // GoroutineProfile-3: [17610640 17123361 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
参考文献
【Golang】Golang の map の非スレッドセーフ性と排他制御
Golang の map はスレッドセーフでない
Golang の map はスレッドセーフ、もといゴルーチンセーフではない。そのため、複数のゴルーチンからの同時アクセスによって整合性が保たれない状態になることがある。
例えば、以下のように map に対して複数のゴルーチンからの書き込みが発生すると、たまに非同期書き込み失敗のエラーが発生する。
package main func main() { kvs := NewKeyValueStore() for i := 0; i < 10; i++ { go func(kvs *KeyValueStore) { kvs.set("key", "value") kvs.get("key") }(kvs) } } type KeyValueStore struct { m map[string]string } func NewKeyValueStore() *KeyValueStore { return &KeyValueStore{m: make(map[string]string)} } func (s *KeyValueStore) set(k, v string) { s.m[k] = v } func (s *KeyValueStore) get(k string) (string, bool) { v, ok := s.m[k] return v, ok }
実行結果(たまに起こる)
fatal error: concurrent map writes
map がなぜデフォルトで非スレッドセーフになっているかというと、単純にパフォーマンス上不利であるためと思われる。この仕様のため、複数御ルーチンから map アクセスを実施するには明示的に排他制御を行う必要がある。1
map の排他制御
Go で排他制御を実現するには、sync.Mutex
を使用する。
package main import ( "fmt" "sync" ) func main() { kvs := NewConcurrentKeyValueStore() for i := 0; i < 10; i++ { go func(kvs *ConcurrentKeyValueStore) { kvs.set("key", "value") kvs.get("key") }(kvs) } } type ConcurrentKeyValueStore struct { m map[string]string mu sync.RWMutex } func NewConcurrentKeyValueStore() *ConcurrentKeyValueStore { return &ConcurrentKeyValueStore{m: make(map[string]string)} } func (s *ConcurrentKeyValueStore) set(k, v string) { s.mu.Lock() defer s.mu.Unlock() s.m[k] = v } func (s *ConcurrentKeyValueStore) get(k string) (string, bool) { s.mu.RLock() defer s.mu.RUnlock() v, ok := s.m[k] return v, ok }
上記のコードでは sync.RWMutex
を使用している。sync.RWMutex
は sync.Mutex
が提供する Lock()/Unlock()
のほか、 RLock()/RUnlock()
を提供する。
Lock()/Unlock()
は占有ロック。ロック時に対象への書き込み、読み込みを禁止する。RLock()/RUnlock()
は共有ロック。ロック時に対象への書き込みのみ禁止し、読み込みは禁止しない。
読み込み処理の場合、他ゴルーチンからの読み込み処理も考慮して RLock
を使用していくと良い。
Go の競合検出
Go ではコードの実行・ビルド時、あるいはパッケージインストール時に -race
オプションを指定することで、実行中の競合検出を行うことができる。
参考文献
【Golang】goimportsでプロジェクト配下のimport文すべてをパッケージ種類別に整形する
TL;DR
下記コマンドで、カレントディレクトリ配下のすべての .go
ファイルの import 文を整形してくれる。
$ find . -print | grep --regex '.*\.go' | xargs goimports -w -local "github.com/your/package"
goimports
の -w
オプションでファイルを直接書き換える。
また -local <string>
は指定した string
を prefix にもつパッケージの整形済み import 文の上部に、他の整形済み import 文が置かれる。
すなわち、string
が prefix の import 文のかたまりが最後にくるようになる。
よって、import 文整形順は 標準パッケージ
-> Publicパッケージ
-> 自パッケージ
の順となる。
あとは、find . -print | grep --regex '.*\.go'
で引っ掛けた .go
ファイル全てに上記 import 整形を適用する。
example
before
import ( "fmt" "github.com/rennnosuke/gih/domain/model/entity" "github.com/rennnosuke/gih/domain/service/git/issue" "github.com/urfave/cli/v2" "regexp" "strconv" "unicode/utf8" )
after
import ( // 標準パッケージ "fmt" "regexp" "strconv" "unicode/utf8" // Publicパッケージ "github.com/urfave/cli/v2" // 自パッケージ "github.com/rennnosuke/gih/domain/model/entity" "github.com/rennnosuke/gih/domain/service/git/issue" )
参考 : goimports
Go は標準でソースの import 文を整形するツール goimports
を提供している。
goimports
を Go ソースファイルに実行することで、ソースをコンパイルするのに必要なパッケージの import 文を自動挿入したり、インデントを追加したり、不要な import 文を削除してくれる。
before
package main func main() { s1 := "Hello" s2 := "goimports." s := strings.join(s1, s2, ",") fmt.Println(s) }
$ goimports -w main.go
after
package main import ( "fmt" "strings" ) func main() { s1 := "Hello" s2 := "goimports." s := strings.Join(s1, s2, ",") fmt.Println(s) }
参考文献
【Golang】Context
Golang における Context とは
大事なことは全部 Document に書いてある。おしまい。
context - The Go Programming Language
・・・と言ってしまうと元も子もないので、自分なりに整理してみる。
Go の Context を要約すると、
Web アプリケーションで横断的に使用するリクエストスコープ変数や処理を取り回す役割を持つもの
といえる。例えば、
が Context 内で管理される。
Go の net/http
ライブラリによって起動する Web サーバーは、ひとつのリクエストにひとつのゴルーチンを割り当てて処理を開始する。リクエストを受けて実行される処理の中で、ゴルーチンは更に増えていく。Context はそのように増えていくゴルーチン間で容易に値や処理を共有できる仕組みとして提供される。
Context=文脈といった意味だが、それこそアプリケーションの文脈では「あるスコープ内で横断的に共有されるモノ」といった意味合いで使用される。Go の Context も「1 リクエスト内で共有されるデータ・処理」といった意味合いで扱われている気がする。
Context の中身
Context
は以下のインタフェースによって定義されている。
type Context interface { Done() <-chan struct{} Err() error Deadline() (deadline time.Time, ok bool) Value(key interface{}) interface{} }
Done()
キャンセルかタイムアウトを検知できるチャネルを返す。このチャネルは Context
に対してキャンセルが通知されたか、タイムアウトが通知されたタイミングで閉じる。キャンセルは後述する WithCancel()
WithDeadline()
WithTimeout()
から返る関数を呼び出すと通知できる。タイムアウトは後述する WithDeadline()
WithTimeout()
関数で Context に設定できる。
Context を使う側は、この Done()
から返るチャネルを経由してリクエストの状態を知ることになる。
Err()
Context がキャンセルされた理由を含む error
値が返る。
キャンセルされない間、 Err()
は nil を返す。
Deadline()
Context がタイムアウトになるまでの時間と、Deadline が設定されているかどうかを返す。
Value(key interface{})
Context が保持する、 key
に紐づいた値を返す。
Value()
が返すべき値の管理はそれぞれの具象 Context 構造体にて定義される。
Context 単体だけではタイムアウトやキャンセルの通知はできない。Context は上記インタフェースの具象値だけでなく、後述する context
パッケージ内関数と組み合わせて使う。
Context のルール
保持する変数はリクエストスコープである
Value()
で習得できる値は、リクエストスコープ内で完結する必要がある。すなわち、複数リクエストから参照可能な値であってはならない。リクエストスコープはゴルーチン安全である リクエストスコープの値は、複数ゴルーチンから同時に参照されても安全、すなわちゴルーチンセーフ(?)である必要がある。
Context の仕組み
Context Tree
Context を使う場合、単一の Context だけをずっと使い回すわけではない。Context を使うアプリ内では、最初に生成した Context から次々と子 Context を派生させ、一つの Context Tree を築いていく。
context
パッケージには、 Context から子コンテキストを派生する関数が用意されている。
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context
これらの関数は引数の Context parent
から子 Context を派生させる。また同時に、リクエストがキャンセルされたことを通知するための関数も返す。このキャンセル関数を呼び出すと、生成された Context とその 子 Context にキャンセルが通知される。
キャンセルが Context に通知されると、Context が持つ Done()
チャネルが閉じ、 Err()
が error 値を返すようになる。返す error 値は関数生成元の関数による。
「子コンテキストの派生」「キャンセルの通知関数の生成」、そして子コンテキストへの性質の付与を同時に行う理由は、アプリケーションの境界によって渡す値や伝播するイベントを制御しやすくするため。例えば、データアクセス層へ渡す Context を WithTimeout()
で派生した子 Context にすることで、キャンセルイベントの伝播をデータアクセス層に閉じることができる。親 Context へはイベントは伝播しないため、上位層の Context について考えなくても良くなる。
Context を仕組みを助ける関数たち
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
引数に取る Context から新しい子 Context を生成・返戻し、加えてキャンセル関数を返す。
キャンセル関数は呼びだすと ctx
とその子 Context すべてにキャンセルが通知され、各 Context の Done()
チャネルが閉じ、 Err()
で error 値が得られるようになる。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
基本的には WithCancel
と同じ。異なる点は、指定した d
の時刻を経過したかどうかが Deadline()
から得られるようになるところ。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
基本的には WithDeadline
と同じ。異なる点は、Deadline を現在時刻からの経過時間で指定する点( WithDeadline(parent, time.Now().Add(timeout))
)。
func WithValue(parent Context, key, val interface{}) Context
生成する子 Context に値を設定する関数。子 Context は新たなリクエストスコープ値を持つことになる。
Context のつかいかた
実際に Context を使ってみた。下記コードの全体はGithub 上に置いてある。
Context の生成
下記コードでは、 Product
構造体配列を永続化層から取得して、標準出力へ書き出す処理を行っている。
func main() { r := repository.ProductRepositoryImpl{} s := service.ProductService{Repo: &r} ctx := context.Background() prods, err := s.GetProducts(ctx) if err != nil { panic(err) } fmt.Println(prods) }
type Product struct { Name string Price int }
Context の生成は context.Background()
で行う。生成した Context をアプリ内で呼び出す関数に渡していく。必要であれば、 context
パッケージ上の関数で子 Context に派生したり、タイムアウトを設けたり、新たなリクエストスコープ値を作ったりする。
type ProductRepositoryImpl struct{} // ProductService::GetProducts()が呼び出す、 // ProductRepository::GetProducts()関数の実装 func (r *ProductRepositoryImpl) GetProducts(ctx context.Context) ([]entity.Product, error) { c := make(chan []entity.Product, 1) childCtx, cancel := context.WithTimeout(ctx, time.Second*5) defer cancel() go func(ctx context.Context) { c <- r.FindProducts(ctx) }(childCtx) select { case <-childCtx.Done(): return nil, errors.New("query canceled") case prods := <-c: return prods, nil } }
上記関数では、引数で受け取った Context からタイムアウトつき子 Context を派生して使用している。この Context から得られる Done()
チャネルを監視することで、キャンセルあるいはタイムアウトしたかどうかを検知することができる。
Context を運用する上で気をつけること
1. フレームワーク独自の Context との共用を避ける
フレームワークが独自に実装している Context でも標準パッケージ context
とほぼ同様のことができるが、そのような Context で Wrap したりすると、フレームワークに強く依存した実装になってしまう。
2. Value は不変とする
Context が保持するリクエストスコープ値は、複数ゴルーチンから参照されても安全でなければならない。
3.Context を他の構造体フィールドにしない
2.
より Context の保持する値はリクエストスコープで完結させたほうがよい。Context を別の構造体のフィールドとすると、その構造体の扱い次第ではアプリケーションスコープになりえるため、特に深刻な理由がなければ構造体フィールド化を避ける。
RootContext はひとつ
リクエストスコープ変数・シグナルを管理する機構が 2 つになるため、Context Tree が 2 つ以上になる状況は厳しい。
Context は nil で渡さない
関数で渡される Context は非 nil とする。どうしても渡す Context がない時のために、そのことを明示する Context を生成する context.Todo()
があるので、それを利用する。
参考文献
【Golang】APIMATIC で Postman Collection を OpenAPI Specification に変換する
PostmanはOpenAPIと互換性があり、OpenAPI Specification(以下OAS)に沿って書かれたAPIドキュメントはPostmanへimportできる。一方で、PostmanからOpenAPIへのexportはサポートされていない。これはPostmanへのFeature requestとしても挙がっており、そこそこ支持を得ている様子ではあるものの、3年前に提案されてからも未だに導入されていない。
Postmanを使ってAPIクライアントとのコミュニケーションを取りたいが、ドキュメントとして見た場合一部痒いところに手が届かない部分がある(BodyのDescription/Schemaを記述できないなど)。そのため、Postmanで記述したAPI仕様をOASに落として、管理は煩雑になるが仕様をより詳細に載せたverを作成できないかと考えた。
そこで、3rd partyから変換可能なものがないか探してみた。
Postman Collection -> OAS 変換可能な3rd partyのツール/サービス
stoplightio/api-spec-converter
Spotlight.ioでも使用されているライブラリ。Postman Collection v1のみ対応。
JSライブラリのため、変換用のコードをJSで記述する必要がある。ただ実際に変換を検証したところ、v1形式のPostman Collectionを変換しようとしたが、うまく変換できなかった。。。
また、3年前ほど前から保守がストップしている。そのため今回は使用を見送り。
APIMATIC
APIMATIC社が提供するSaaS。Postman Collection v2の変換に対応している。
アカウント登録が必要だが、結果から言うとドキュメント変換がきれいに実行できた。今回はこのツールを使用してみる。
APIMATICで Postman Collection を OASに変換する
はじめに、Postman上に定義されたAPI仕様をexportする。
exoprtしたいCollectionの三点リーダをクリックし、出現したリストの Export
をクリックする。
するとexportするファイルのバージョンを選択できるので、ここではv2.1を選択。
これで、 Postman Collection
と呼ばれる json
ファイル形式のAPIドキュメントが出力される。
このファイルはPostman間の定義や設定共有に使用されるものだが、今回はその中のAPI定義部分を抽出し、OASとして変換する。
次に、 APIMANICのWebページを開き、 Sign Up for free
をクリック。
新規アカウント登録画面に遷移するので、必要な情報を入力し、 Sign Up
をクリック。
しばらくすると登録したメールアドレスにconfirmメールが来るので、メール内のconfirmボタンをクリックし、 Click here
をクリックする。
APIMATICダッシュボード画面に遷移する。 Transform API
をクリック。
先程exportした Postman Collection
jsonファイルを選択し、 好きな形式を選択してexportできる。今回はOAS3.0を選択。Convertボタンをクリック。
最後にうまく変換できたかどうかが表示されるので、確認した上でProceedボタンを押して変換後ファイルをダウンロード。
完了。
以下のようなOAS3.0形式ファイルが出力される。
openapi: 3.0.0 info: title: test contact: {} version: '1.0' servers: - url: http://hoge.api.com variables: {} paths: /test: get: tags: - Misc summary: http://hoge.api.com/test description: http://hoge.api.com/test operationId: http://hoge.api.com/test parameters: [] responses: 200: description: '' headers: {} deprecated: false tags: - name: Misc description: ''
これで、 OpenAPI形式への変換が完了した。
これでOASに合わせて記述を追加したり、OAS対応のサービスでおしゃれにレンダリングしたりできる。