Support inputstream for request and response bodies#138
Conversation
…m-for-request-and-response-bodies
…-bodies # Conflicts: # .codegen/_openapi_sha
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(method, url, query, body); | ||
| return Objects.hash(method, url, query, debugBody); |
There was a problem hiding this comment.
leave comment for testing only
| } | ||
| // check if object is a primitive type or a collection of some kind | ||
| if (primitiveTypes.contains(f.getType()) || Iterable.class.isAssignableFrom(f.getType())) { | ||
| if (primitiveTypes.contains(f.getType()) || Iterable.class.isAssignableFrom(f.getType()) || f.getType().isEnum()) { |
| // If the API call fails, the server will respond with an application/json response, which | ||
| // the client reads fully and closes, even if the request Accept header is not application/json. | ||
| // The error is then thrown as an exception in ApiClient. |
There was a problem hiding this comment.
This needs to be implemented properly
There was a problem hiding this comment.
Change to if expected not json && got not json <=> stream
| } | ||
|
|
||
| private static String convert(InputStream in) { | ||
| try (BufferedReader br = |
There was a problem hiding this comment.
Put into ByteArrayOutputStream
| return impl.getStatus(request); | ||
| } | ||
|
|
||
| public void uploadFile(InputStream contents, String filePath) { |
There was a problem hiding this comment.
Move body parameter to the end
|
Need to split into two PRs. |
pietern
left a comment
There was a problem hiding this comment.
Approach of making API client be streaming aware LGTM.
| return String.format("[unable to marshal: %s]", e.getMessage()); | ||
| // Unable to unmarshal means the body isn't JSON | ||
| if (body.length() > maxBytes) { | ||
| return body.substring(0, maxBytes - 3) + "..."; |
There was a problem hiding this comment.
This value should be escaped, seeing as it ends up in log files. If we log a files API response it can be any type of binary data and it can mess up your terminal.
There was a problem hiding this comment.
For now, I think this is safe-ish. The main value that triggers this case is "", since that is what we set debugBody to when streaming a response. Otherwise, it behaves the same as existing logging. I'll change this impl to reuse onlyNBytes, and we can pick this up as a follow-up task.
There was a problem hiding this comment.
Also for future reference: the implementation currently splits the debug body string by newlines and prepend the > prefix, so it should not be possible for attackers to trigger printing out specific log files that don't have that prefix.
…m-for-request-and-response-bodies
…m-for-request-and-response-bodies
…m-for-request-and-response-bodies
tanmay-db
left a comment
There was a problem hiding this comment.
Went over the PR with Miles, LGTM.
| } | ||
|
|
||
| private static String convert(InputStream in) { | ||
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| } | ||
|
|
||
| try (InputStream inputStream = entity.getContent()) { | ||
| String body = IOUtils.toString(inputStream, Charset.defaultCharset()); |
| } | ||
|
|
||
| Matcher messageMatcher = HTML_ERROR_REGEX.matcher(response.getBody()); | ||
| String body = convert(response.getBody()); |
There was a problem hiding this comment.
Comment that error responses are fully consumed so don't need to be closed.
* Added support for propagating Request Headers through API Client ([#135](#135)). * Added support for InputStream for streaming request and response bodies ([#138](#138)). * Fixed query param serialization for requests with enums ([#140](#140)). Breaking API Changes: * Changed `list()` method for `accountClient.storageCredentials()` service to return `com.databricks.sdk.service.catalog.StorageCredentialInfoList` class. * Removed `workspaceClient.securableTags()` service and all related classes. * Removed `workspaceClient.subentityTags()` service and all related classes. * Renamed `provisioningState` field for `com.databricks.sdk.service.catalog.ConnectionInfo` to `provisioningInfo`. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.CreateInstancePool`. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.EditInstancePool`. * Removed `com.databricks.sdk.service.compute.FleetLaunchTemplateOverride` class. * Removed `com.databricks.sdk.service.compute.FleetOnDemandOption` class. * Removed `com.databricks.sdk.service.compute.FleetOnDemandOptionAllocationStrategy` class. * Removed `com.databricks.sdk.service.compute.FleetSpotOption` class. * Removed `com.databricks.sdk.service.compute.FleetSpotOptionAllocationStrategy` class. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.GetInstancePool`. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.InstancePoolAndStats`. * Removed `com.databricks.sdk.service.compute.InstancePoolFleetAttributes` class. * Changed `getByName()` method for `workspaceClient.experiments()` service to return `com.databricks.sdk.service.ml.GetExperimentResponse` class. * Changed `getExperiment()` method for `workspaceClient.experiments()` service to return `com.databricks.sdk.service.ml.GetExperimentResponse` class. * Renamed `com.databricks.sdk.service.ml.GetExperimentByNameResponse` class to `com.databricks.sdk.service.ml.GetExperimentResponse`. API Changes: * Added `workspaceClient.modelVersions()` service. * Added `workspaceClient.registeredModels()` service. * Added `browseOnly` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `fullName` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `provisioningInfo` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `securableKind` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `securableType` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `options` field for `com.databricks.sdk.service.catalog.CreateCatalog`. * Added `options` field for `com.databricks.sdk.service.catalog.UpdateCatalog`. * Added `com.databricks.sdk.service.catalog.CatalogInfoSecurableKind` class. * Added `com.databricks.sdk.service.catalog.CreateRegisteredModelRequest` class. * Added `com.databricks.sdk.service.catalog.DeleteAliasRequest` class. * Added `com.databricks.sdk.service.catalog.DeleteModelVersionRequest` class. * Added `com.databricks.sdk.service.catalog.DeleteRegisteredModelRequest` class. * Added `com.databricks.sdk.service.catalog.GetByAliasRequest` class. * Added `com.databricks.sdk.service.catalog.GetModelVersionRequest` class. * Added `com.databricks.sdk.service.catalog.GetRegisteredModelRequest` class. * Added `com.databricks.sdk.service.catalog.ListModelVersionsRequest` class. * Added `com.databricks.sdk.service.catalog.ListModelVersionsResponse` class. * Added `com.databricks.sdk.service.catalog.ListRegisteredModelsRequest` class. * Added `com.databricks.sdk.service.catalog.ListRegisteredModelsResponse` class. * Added `com.databricks.sdk.service.catalog.ModelVersionInfo` class. * Added `com.databricks.sdk.service.catalog.ModelVersionInfoStatus` class. * Added `com.databricks.sdk.service.catalog.ProvisioningInfo` class. * Added `com.databricks.sdk.service.catalog.ProvisioningInfoState` class. * Added `com.databricks.sdk.service.catalog.RegisteredModelAlias` class. * Added `com.databricks.sdk.service.catalog.RegisteredModelInfo` class. * Added `com.databricks.sdk.service.catalog.SetRegisteredModelAliasRequest` class. * Added `com.databricks.sdk.service.catalog.UpdateModelVersionRequest` class. * Added `com.databricks.sdk.service.catalog.UpdateRegisteredModelRequest` class. * Added `volumes` field for `com.databricks.sdk.service.compute.InitScriptInfo`. * Added `com.databricks.sdk.service.compute.VolumesStorageInfo` class. * Added `workspaceClient.files()` service. * Added `com.databricks.sdk.service.files.DeleteFileRequest` class. * Added `com.databricks.sdk.service.files.DownloadRequest` class. * Added `com.databricks.sdk.service.files.DownloadResponse` class. * Added `com.databricks.sdk.service.files.UploadRequest` class. * Added `customTags` field for `com.databricks.sdk.service.provisioning.CreateWorkspaceRequest`. * Added `customTags` field for `com.databricks.sdk.service.provisioning.UpdateWorkspaceRequest`. * Added `customTags` field for `com.databricks.sdk.service.provisioning.Workspace`. * Added `com.databricks.sdk.service.provisioning.CustomTags` class. * Added `parameters` field for `com.databricks.sdk.service.sql.ExecuteStatementRequest`. * Added `rowLimit` field for `com.databricks.sdk.service.sql.ExecuteStatementRequest`. * Added `com.databricks.sdk.service.sql.StatementParameterListItem` class. OpenAPI SHA: 5d0ccbb790d341eae8e85321a685a9e9e2d5bf24, Date: 2023-08-29
* Added support for propagating Request Headers through API Client ([#135](#135)). * Added support for InputStream for streaming request and response bodies ([#138](#138)). * Fixed query param serialization for requests with enums ([#140](#140)). Breaking API Changes: * Changed `list()` method for `accountClient.storageCredentials()` service to return `com.databricks.sdk.service.catalog.StorageCredentialInfoList` class. * Removed `workspaceClient.securableTags()` service and all related classes. * Removed `workspaceClient.subentityTags()` service and all related classes. * Renamed `provisioningState` field for `com.databricks.sdk.service.catalog.ConnectionInfo` to `provisioningInfo`. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.CreateInstancePool`. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.EditInstancePool`. * Removed `com.databricks.sdk.service.compute.FleetLaunchTemplateOverride` class. * Removed `com.databricks.sdk.service.compute.FleetOnDemandOption` class. * Removed `com.databricks.sdk.service.compute.FleetOnDemandOptionAllocationStrategy` class. * Removed `com.databricks.sdk.service.compute.FleetSpotOption` class. * Removed `com.databricks.sdk.service.compute.FleetSpotOptionAllocationStrategy` class. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.GetInstancePool`. * Removed `instancePoolFleetAttributes` field for `com.databricks.sdk.service.compute.InstancePoolAndStats`. * Removed `com.databricks.sdk.service.compute.InstancePoolFleetAttributes` class. * Changed `getByName()` method for `workspaceClient.experiments()` service to return `com.databricks.sdk.service.ml.GetExperimentResponse` class. * Changed `getExperiment()` method for `workspaceClient.experiments()` service to return `com.databricks.sdk.service.ml.GetExperimentResponse` class. * Renamed `com.databricks.sdk.service.ml.GetExperimentByNameResponse` class to `com.databricks.sdk.service.ml.GetExperimentResponse`. API Changes: * Added `workspaceClient.modelVersions()` service. * Added `workspaceClient.registeredModels()` service. * Added `browseOnly` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `fullName` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `provisioningInfo` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `securableKind` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `securableType` field for `com.databricks.sdk.service.catalog.CatalogInfo`. * Added `options` field for `com.databricks.sdk.service.catalog.CreateCatalog`. * Added `options` field for `com.databricks.sdk.service.catalog.UpdateCatalog`. * Added `com.databricks.sdk.service.catalog.CatalogInfoSecurableKind` class. * Added `com.databricks.sdk.service.catalog.CreateRegisteredModelRequest` class. * Added `com.databricks.sdk.service.catalog.DeleteAliasRequest` class. * Added `com.databricks.sdk.service.catalog.DeleteModelVersionRequest` class. * Added `com.databricks.sdk.service.catalog.DeleteRegisteredModelRequest` class. * Added `com.databricks.sdk.service.catalog.GetByAliasRequest` class. * Added `com.databricks.sdk.service.catalog.GetModelVersionRequest` class. * Added `com.databricks.sdk.service.catalog.GetRegisteredModelRequest` class. * Added `com.databricks.sdk.service.catalog.ListModelVersionsRequest` class. * Added `com.databricks.sdk.service.catalog.ListModelVersionsResponse` class. * Added `com.databricks.sdk.service.catalog.ListRegisteredModelsRequest` class. * Added `com.databricks.sdk.service.catalog.ListRegisteredModelsResponse` class. * Added `com.databricks.sdk.service.catalog.ModelVersionInfo` class. * Added `com.databricks.sdk.service.catalog.ModelVersionInfoStatus` class. * Added `com.databricks.sdk.service.catalog.ProvisioningInfo` class. * Added `com.databricks.sdk.service.catalog.ProvisioningInfoState` class. * Added `com.databricks.sdk.service.catalog.RegisteredModelAlias` class. * Added `com.databricks.sdk.service.catalog.RegisteredModelInfo` class. * Added `com.databricks.sdk.service.catalog.SetRegisteredModelAliasRequest` class. * Added `com.databricks.sdk.service.catalog.UpdateModelVersionRequest` class. * Added `com.databricks.sdk.service.catalog.UpdateRegisteredModelRequest` class. * Added `volumes` field for `com.databricks.sdk.service.compute.InitScriptInfo`. * Added `com.databricks.sdk.service.compute.VolumesStorageInfo` class. * Added `workspaceClient.files()` service. * Added `com.databricks.sdk.service.files.DeleteFileRequest` class. * Added `com.databricks.sdk.service.files.DownloadRequest` class. * Added `com.databricks.sdk.service.files.DownloadResponse` class. * Added `com.databricks.sdk.service.files.UploadRequest` class. * Added `customTags` field for `com.databricks.sdk.service.provisioning.CreateWorkspaceRequest`. * Added `customTags` field for `com.databricks.sdk.service.provisioning.UpdateWorkspaceRequest`. * Added `customTags` field for `com.databricks.sdk.service.provisioning.Workspace`. * Added `com.databricks.sdk.service.provisioning.CustomTags` class. * Added `parameters` field for `com.databricks.sdk.service.sql.ExecuteStatementRequest`. * Added `rowLimit` field for `com.databricks.sdk.service.sql.ExecuteStatementRequest`. * Added `com.databricks.sdk.service.sql.StatementParameterListItem` class. OpenAPI SHA: 5d0ccbb790d341eae8e85321a685a9e9e2d5bf24, Date: 2023-08-29
## Changes #138 changed the CommonsHttpClient implementation to use `InputStreamEntity` for all request bodies sent using the Java SDK. As a result, requests are sent with a `Transfer-Encoding: chunked` header, and the request body is chunked accordingly. However, the Databricks REST API only tolerates chunked requests for certain APIs; for others, it ignores the request body if Content-Length is not specified. Auth falls under that category, which caused #154. To fix this, Request will keep track of string entities and input stream entities separately. Previously, Request had separate fields for the body and debugBody, but they were both always set, so it wasn't possible to distinguish between these two cases. Now, HTTP clients can have different behavior based on whether the request body can be fully materialized as a string or is lazily read as with input streams. As part of this, I have removed `SimpleHttpServer`. This was used before we were sure whether it was possible to use the HttpServer built into the JDK. Now that we are confident that we can (see usage in ExternalBrowserCredentialsProvider.java), I've replaced it with HttpServer and a custom HttpHandler in FixtureServer (the only place where it is used now). One other small change: I've updated the logging configuration and dependency for the cli auth demo app. This ensures that users can see debug output (which I used when debugging this). Closes #154. ## Tests I've refactored FixtureServer to support more advanced validation on the HTTP requests sent by clients for testing purposes. Now, users can assert that a request has a specific method, path, headers, body, and check for headers that should not be present. One downside of this change is that users need to call the `.with()` method once per API call, rather than one time, at the start of each test. Unit tests cover both cases (request body should be chunked when specified as a String; request body should not be chunked when specified as an InputStream).
Changes
This PR adds support for non-application/json requests and responses. This PR is divided into two parts:
Support setting headers per request. The main methods of the ApiClient interface are expanded to accept one new parameter,
Map<String, String> headers, and all callers are expected to pass a map of headers to be included in the request. As the allowed content types for requests and responses are known from the OpenAPI specification, impls must construct the request header map in code generation and pass it to the ApiClient. The default "Content-Type: application/json" header is removed, as each request should specify its own Content-Type and Accept headers. (This is merged in #135.)Add support for streaming request and response bodies to support non-application/json requests/responses. Today, serialization of requests is done in the ApiClient. This implies that ApiClient needs to be able to serialize a request purely based on the parameters provided to it via headers, the request body, etc. In this proposal, the serialization is coupled to the request type: InputStream indicates that the client has already serialized the request, so it can be provided directly to the underlying HttpClient library, and any other type will be passed through Jackson's ObjectMapper for serialization to JSON. Internally, request and response bodies are modeled as InputStreams rather than byte arrays to support streaming requests and responses, though for non-streaming requests and responses, a string value for the body is captured in the
debugBodyfield of Request and Response, respectively.One important caveat about streaming responses is that callers are required to close the streams when they are done using them. This releases the underlying HTTP connection back to the connection pool for use in subsequent requests. Otherwise, customers could encounter deadlocks waiting for connections. For this, we recommend that users use the try-with-resources model as demonstrated in integration test FilesIT.java.
Tests