-
Notifications
You must be signed in to change notification settings - Fork 543
support/datastore: add read-only http datastore. #5809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,245 @@ | ||||||||||||||||||||||||||||
| package datastore | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||
| "net/url" | ||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| "github.com/stellar/go-stellar-sdk/support/log" | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // HTTPDataStore implements DataStore for HTTP(S) endpoints. | ||||||||||||||||||||||||||||
| // This is designed for read-only access to publicly available files. | ||||||||||||||||||||||||||||
| type HTTPDataStore struct { | ||||||||||||||||||||||||||||
| client *http.Client | ||||||||||||||||||||||||||||
| baseURL string | ||||||||||||||||||||||||||||
| headers map[string]string | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // NewHTTPDataStore creates a new HTTP-based DataStore for read-only access to files. | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // The datastoreConfig.Params map supports the following keys: | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // - "base_url" (required): The base HTTP or HTTPS URL for the datastore. | ||||||||||||||||||||||||||||
| // Must include the scheme (http:// or https://) and will have a trailing | ||||||||||||||||||||||||||||
| // slash appended if not present. | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // - "timeout" (optional): HTTP client timeout as a duration string (e.g., "30s", "1m"). | ||||||||||||||||||||||||||||
| // Defaults to 30 seconds if not specified. Parsed using time.ParseDuration. | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // - "header_<name>" (optional): Custom HTTP headers to include in all requests. | ||||||||||||||||||||||||||||
| // The header name is derived by stripping the "header_" prefix from the key. | ||||||||||||||||||||||||||||
| // For example, "header_Authorization" sets the "Authorization" header. | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // Example TOML configuration: | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // [datastore] | ||||||||||||||||||||||||||||
| // type = "HTTP" | ||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||
| // [datastore.params] | ||||||||||||||||||||||||||||
| // base_url = "https://example.com/data/" | ||||||||||||||||||||||||||||
| // timeout = "60s" | ||||||||||||||||||||||||||||
| // header_Authorization = "Bearer token123" | ||||||||||||||||||||||||||||
| // header_X-Custom-Header = "custom-value" | ||||||||||||||||||||||||||||
| func NewHTTPDataStore(datastoreConfig DataStoreConfig) (DataStore, error) { | ||||||||||||||||||||||||||||
| baseURL, ok := datastoreConfig.Params["base_url"] | ||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||
| return nil, errors.New("invalid HTTP config, no base_url") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| parsedURL, err := url.Parse(baseURL) | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| return nil, fmt.Errorf("invalid base_url: %w", err) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { | ||||||||||||||||||||||||||||
| return nil, errors.New("base_url must use http or https scheme") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if !strings.HasSuffix(baseURL, "/") { | ||||||||||||||||||||||||||||
| baseURL = baseURL + "/" | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| timeout := 30 * time.Second | ||||||||||||||||||||||||||||
| if timeoutStr, ok := datastoreConfig.Params["timeout"]; ok { | ||||||||||||||||||||||||||||
| parsedTimeout, err := time.ParseDuration(timeoutStr) | ||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||
| return nil, fmt.Errorf("invalid timeout: %w", err) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| timeout = parsedTimeout | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| headers := make(map[string]string) | ||||||||||||||||||||||||||||
| for key, value := range datastoreConfig.Params { | ||||||||||||||||||||||||||||
| if strings.HasPrefix(key, "header_") { | ||||||||||||||||||||||||||||
| headerName := strings.TrimPrefix(key, "header_") | ||||||||||||||||||||||||||||
| headers[headerName] = value | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| client := &http.Client{ | ||||||||||||||||||||||||||||
| Timeout: timeout, | ||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+87
|
||||||||||||||||||||||||||||
| client := &http.Client{ | |
| Timeout: timeout, | |
| transport := &http.Transport{ | |
| MaxIdleConns: 100, | |
| MaxIdleConnsPerHost: 10, | |
| IdleConnTimeout: 90 * time.Second, | |
| } | |
| client := &http.Client{ | |
| Timeout: timeout, | |
| Transport: transport, | |
| CheckRedirect: func(req *http.Request, via []*http.Request) error { | |
| return http.ErrUseLastResponse | |
| }, |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging the full error message may expose sensitive information like internal URLs or authentication details. Consider sanitizing the error message before logging or reducing the log level.
| log.Debugf("Error retrieving file '%s': %v", filePath, err) | |
| log.Debugf("Error retrieving file '%s'", filePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be convenient to provide some some comment docs on this method to describe the expected toml datastore config params structure such as for the keys expected like 'timeout' and headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ad73020
There are no similar comments in s3 and gcp either. If you would like me to add comments for them in this PR as well, please let me know, thank you.