Moved common HTTP body fetch code into a shared helper function.
This commit is contained in:
55
internal/sources/common/http.go
Normal file
55
internal/sources/common/http.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// maxResponseBodyBytes is a hard safety limit on HTTP response bodies.
|
||||
// API responses should be small, so this protects us from accidental
|
||||
// or malicious large responses.
|
||||
const maxResponseBodyBytes = 2 << 21 // 4 MiB
|
||||
|
||||
func FetchBody(ctx context.Context, client *http.Client, url, userAgent, accept string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userAgent != "" {
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
}
|
||||
if accept != "" {
|
||||
req.Header.Set("Accept", accept)
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("HTTP %s", res.Status)
|
||||
}
|
||||
|
||||
// Read at most maxResponseBodyBytes + 1 so we can detect overflow.
|
||||
limited := io.LimitReader(res.Body, maxResponseBodyBytes+1)
|
||||
|
||||
b, err := io.ReadAll(limited)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return nil, fmt.Errorf("empty response body")
|
||||
}
|
||||
|
||||
if len(b) > maxResponseBodyBytes {
|
||||
return nil, fmt.Errorf("response body too large (>%d bytes)", maxResponseBodyBytes)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/feedkit/config"
|
||||
"gitea.maximumdirect.net/ejr/feedkit/event"
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common"
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards"
|
||||
)
|
||||
|
||||
@@ -133,30 +133,9 @@ type observationMeta struct {
|
||||
}
|
||||
|
||||
func (s *ObservationSource) fetchRaw(ctx context.Context) (json.RawMessage, observationMeta, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.url, nil)
|
||||
b, err := common.FetchBody(ctx, s.client, s.url, s.userAgent, "application/geo+json, application/json")
|
||||
if err != nil {
|
||||
return nil, observationMeta{}, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", s.userAgent)
|
||||
req.Header.Set("Accept", "application/geo+json, application/json")
|
||||
|
||||
res, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, observationMeta{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, observationMeta{}, fmt.Errorf("nws_observation %q: HTTP %s", s.name, res.Status)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, observationMeta{}, err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil, observationMeta{}, fmt.Errorf("nws_observation %q: empty response body", s.name)
|
||||
return nil, observationMeta{}, fmt.Errorf("nws_observation %q: %w", s.name, err)
|
||||
}
|
||||
|
||||
raw := json.RawMessage(b)
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/ejr/feedkit/config"
|
||||
"gitea.maximumdirect.net/ejr/feedkit/event"
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common"
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards"
|
||||
)
|
||||
|
||||
@@ -126,30 +126,9 @@ type openMeteoMeta struct {
|
||||
}
|
||||
|
||||
func (s *ObservationSource) fetchRaw(ctx context.Context) (json.RawMessage, openMeteoMeta, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.url, nil)
|
||||
b, err := common.FetchBody(ctx, s.client, s.url, s.userAgent, "application/json")
|
||||
if err != nil {
|
||||
return nil, openMeteoMeta{}, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", s.userAgent)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
res, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, openMeteoMeta{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, openMeteoMeta{}, fmt.Errorf("openmeteo_observation %q: HTTP %s", s.name, res.Status)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, openMeteoMeta{}, err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil, openMeteoMeta{}, fmt.Errorf("openmeteo_observation %q: empty response body", s.name)
|
||||
return nil, openMeteoMeta{}, fmt.Errorf("openmeteo_observation %q: %w", s.name, err)
|
||||
}
|
||||
|
||||
raw := json.RawMessage(b)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
|
||||
"gitea.maximumdirect.net/ejr/feedkit/config"
|
||||
"gitea.maximumdirect.net/ejr/feedkit/event"
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common"
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards"
|
||||
)
|
||||
|
||||
@@ -139,30 +139,9 @@ type openWeatherMeta struct {
|
||||
}
|
||||
|
||||
func (s *ObservationSource) fetchRaw(ctx context.Context) (json.RawMessage, openWeatherMeta, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.url, nil)
|
||||
b, err := common.FetchBody(ctx, s.client, s.url, s.userAgent, "application/json")
|
||||
if err != nil {
|
||||
return nil, openWeatherMeta{}, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", s.userAgent)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
res, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, openWeatherMeta{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, openWeatherMeta{}, fmt.Errorf("openweather_observation %q: HTTP %s", s.name, res.Status)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, openWeatherMeta{}, err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil, openWeatherMeta{}, fmt.Errorf("openweather_observation %q: empty response body", s.name)
|
||||
return nil, openWeatherMeta{}, fmt.Errorf("openweather_observation %q: %w", s.name, err)
|
||||
}
|
||||
|
||||
raw := json.RawMessage(b)
|
||||
|
||||
Reference in New Issue
Block a user