DiscoveryHandler serves the embedded OpenAPI spec.
(_ context.Context, prefix string, topt otelhttp.Option)
| 45 | |
| 46 | // DiscoveryHandler serves the embedded OpenAPI spec. |
| 47 | func DiscoveryHandler(_ context.Context, prefix string, topt otelhttp.Option) http.Handler { |
| 48 | allow := []string{ |
| 49 | `application/openapi+json`, `application/openapi+yaml`, // New types: https://datatracker.ietf.org/doc/draft-ietf-httpapi-rest-api-mediatypes/ |
| 50 | `application/json`, `application/yaml`, // Format types. |
| 51 | `application/vnd.oai.openapi+json`, `application/vnd.oai.openapi+yaml`, // Older vendor-tree types. |
| 52 | } |
| 53 | // These functions are written back-to-front. |
| 54 | var inner http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 55 | ctx := r.Context() |
| 56 | if r.Method != http.MethodGet { |
| 57 | apiError(ctx, w, http.StatusMethodNotAllowed, "endpoint only allows GET") |
| 58 | } |
| 59 | switch err := pickContentType(w, r, allow); { |
| 60 | case errors.Is(err, nil): |
| 61 | case errors.Is(err, ErrMediaType): |
| 62 | apiError(ctx, w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow) |
| 63 | default: |
| 64 | apiError(ctx, w, http.StatusInternalServerError, "unexpected error: %v", err) |
| 65 | } |
| 66 | h := w.Header() |
| 67 | kind := h.Get(`Content-Type`) |
| 68 | var src *bytes.Reader |
| 69 | switch kind[len(kind)-4:] { |
| 70 | case "json": |
| 71 | src = bytes.NewReader(compactOpenapiJSON()) |
| 72 | case "yaml": |
| 73 | src = bytes.NewReader(openapiYAML) |
| 74 | default: |
| 75 | apiError(ctx, w, http.StatusInternalServerError, "unexpected error: unknown content-type kind: %q", kind) |
| 76 | } |
| 77 | h.Set("Etag", openapiEtag) |
| 78 | var err error |
| 79 | defer writerError(w, &err)() |
| 80 | _, err = io.Copy(w, src) |
| 81 | }) |
| 82 | inner = otelhttp.NewHandler( |
| 83 | compress.Handler(discoverywrapper.wrap(prefix, inner)), |
| 84 | "discovery", |
| 85 | otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents), |
| 86 | topt, |
| 87 | ) |
| 88 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 89 | start := time.Now() |
| 90 | r = withRequestID(r) |
| 91 | ctx := r.Context() |
| 92 | var status int |
| 93 | var length int64 |
| 94 | w = httputil.ResponseRecorder(&status, &length, w) |
| 95 | defer func() { |
| 96 | switch err := http.NewResponseController(w).Flush(); { |
| 97 | case errors.Is(err, nil): |
| 98 | case errors.Is(err, http.ErrNotSupported): |
| 99 | // Skip |
| 100 | default: |
| 101 | zlog.Warn(ctx). |
| 102 | Err(err). |
| 103 | Msg("unable to flush http response") |
| 104 | } |