Skip to content

Commit

Permalink
docs: Initial iteration of a help page for REST libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
jskeet committed Jul 4, 2023
1 parent 2836b1b commit dc94e7b
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 1 deletion.
5 changes: 4 additions & 1 deletion docs/devsite-help/library-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,7 @@ Examples include:
- Google.Apis.YouTube.v3

(In general, packages starting "Google.Apis" followed by a product
name and a version number fall into this category.)
name and a version number fall into this category.)

More information about various aspects of these libraries can be found in the
[dedicated page](rest-libraries.md).
165 changes: 165 additions & 0 deletions docs/devsite-help/rest-libraries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# HTTP/1.1 + REST libraries

As described in the [library types page](library-types.md), the
HTTP/1.1 + REST libraries are API-specific libraries with source code in
the [google-api-dotnet-client repository](https://github.com/googleapis/google-api-dotnet-client),
generated from [Discovery documents](https://developers.google.com/discovery/v1/getting_started)
instead of API protobuf representations. API calls are made over HTTP/1.1 with
JSON content.

These libraries depend heavily on the support libraries of Google.Apis and
Google.Apis.Core, which have their source code in the same repository.

These libraries are still supported and automatically updated as the API
surfaces change, but the underlying code and generator is not being actively worked
on, and only high-impact feature requests are considered.

Some features are not as discoverable as we'd like them to be; this page
describes the use of these features.

## Service object reuse

Service client classes (e.g. `DriveService`) are thread-safe and reusable.
Typically your application should create a single instance and use it for all operations.

By default, every instantiation of a service client class creates a new instance of
[HttpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient),
and creating many of these is, in general, a bad idea. See, for example,
[this post](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/) for why.

Sometimes it *is* required to create an instance per request. In this case make sure
your code calls `Dispose` on each instance when it has finished with it.
This will call `Dispose` on the `HttpClient`.

### GoogleWebAuthorizationBroker

[GoogleWebAuthorizationBroker](https://github.com/googleapis/google-api-dotnet-client/blob/main/Src/Support/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs) requires interaction with the end-user, so can only be used in client-side code.

OAuth authentication requires the end-user to interact with a web browser, which can only be done client-side.

It is possible to use OAuth authentication for end users in a web application, but not using
`GoogleWebAuthorizationBroker`. For these purposes, you can use one of the ASP.NET integration packages
we provide, such as [`Google.Apis.Auth.AspNetCore3`](https://www.nuget.org/packages/Google.Apis.Auth.AspNetCore3).

You can find general information about Google APIs and OAuth 2.0 in
[Using OAuth 2.0 to Access Google APIs](https://developers.google.com/identity/protocols/oauth2)
and about integration with .NET client libraries in the [.NET guide for OAuth 2.0 and
Google APIs](https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth).

## Specifying a retry policy

By default, clients will only retry requests which result in an HTTP 503 response. These are retried with exponential backoff, starting with 250ms and doubling on each retry, for a maximum of 3 attempts.
Retry policies can be configured on a per-client basis, as shown below:

```csharp
using Google.Apis.Drive.v3;
using Google.Apis.Http;
using Google.Apis.Util;
using System.Net;

BackOffHandler handler = new BackOffHandler(new BackOffHandler.Initializer(new ExponentialBackOff())
{
HandleUnsuccessfulResponseFunc = response => response.StatusCode == HttpStatusCode.InternalServerError,
HandleExceptionFunc = exception => exception is HttpRequestException
});

DriveService service = new DriveService(new Google.Apis.Services.BaseClientService.Initializer
{
// Prevent the default retry handling from also retrying failures.
DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.None,
...
});

service.HttpClient.MessageHandler.NumTries = 5;
service.HttpClient.MessageHandler.AddExceptionHandler(handler);
service.HttpClient.MessageHandler.AddUnsuccessfulResponseHandler(handler);
```

## Integration with IHttpClientFactory

For historical reasons, there are two (incompatible) `IHttpClientFactory` interfaces:

- [`Google.Apis.Http.IHttpClientFactory`](https://github.com/googleapis/google-api-dotnet-client/blob/main/Src/Support/Google.Apis.Core/Http/IHttpClientFactory.cs) in the REST support library
- [`System.Net.Http.IHttpClientFactory`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.ihttpclientfactory) in .NET

The .NET interface is widely used, and supported in the community with projects such as
[Polly](https://github.com/App-vNext/Polly). Unfortunately, the REST support library predates this
interface and it can't be directly retro-fitted without breaking changes.

The `HttpClientFromMessageHandlerFactory` provides a bridge between the two interfaces. Integration is still
relatively complex, but the [integration test for this class](https://github.com/googleapis/google-api-dotnet-client/blob/main/Src/Support/IntegrationTests/HttpClientFromMessageHandlerFactoryTests.cs)
serves as a living example.

## GoogleApiHttpBody properties

Some libraries (e.g. Cloud Machine Learning Engine) require un-typed
JSON to be passed through to the server, ignoring the strongly-typed
generated code. This un-typed JSON is represented via the `GoogleApiHttpBody`
type, usually for a property called `HttpBody`.

Setting the data in this property directly does not work as
expected. The workaround is to use the service `HttpClient` directly. For example:

```csharp
DemoService service = new DemoService(...);

// The JSON we want in our HTTP request body
string json = "{ \"property\": 5 }";

// Create an empty body part for the request
DemoRequestBody emptyBody = new DemoRequestBody { HttpBody = new GoogleApiHttpBody() };

// Create the service request as normal
DemoRequest demoRequest = service.Demo(emptyBody);

// Prepare to send the request using the service HttpClient directly
Uri uri = demoRequest.CreateRequest().RequestUri;
HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
// Execute the request
HttpResponseMessage response = service.HttpClient.PostAsync(uri, content).Result;
// Check the response status-code, to see if the request completed successfully.
// If so, then JSON response data can now be read directly from the response
```

See issue [#1068](https://github.com/googleapis/google-api-dotnet-client/issues/1068) for more details.

## DateTime/DateTimeOffset properties

Resource properties specified with the "date-time" format in Discovery document are generated as three
properties in the C# code. For a property named `timestamp` in the Discovery document,
the generated properties are:

```csharp
public string TimestampRaw { get; set; }
[Obsolete]
public DateTime Timestamp { get; set; }
public DateTimeOffset TimestampDateTimeOffset { get; set; }
```

The three properties are all implemented in terms of the "raw" property, which is just a plain string. When fetching the properties with types `DateTime` or `DateTimeOffset`, the raw string is parsed to the appropriate type. When setting the properties, the raw string is obtained by formatting the value.

The `DateTime`-based property is obsolete as its parsing code has some undesirable behavior, including
conversion to local time whereas the underlying value is always expressed in UTC. This behavior can't be
changed without breaking backward compatibility, so instead the `DateTimeOffset`-based property was
introduced. We recommend migrating code using the `DateTime`-based property to the new property to avoid
this behavior. Likewise, code using the raw string property in order to work around the `DateTime` behavior
can now migrate to use the new property (avoiding code clutter) - but it's fine to continue using the raw
property for other reasons, such as to log the precise payload.

## Object/DateTimeOffset properties

Resource properties specified with the "google-datetime" format in Discovery document are also generated as three properties in the C# code. For a property named `timestamp` in the Discovery document,
the generated properties are:

```csharp
public string TimestampRaw { get; set; }
[Obsolete]
public object Timestamp { get; set; }
public DateTimeOffset TimestampDateTimeOffset { get; set; }
```

This situation is more complex than the "date-time" scenario above, as one property has a type of just
`object`, preventing proxying all properties through just the raw string property.
The generated code maintains backward compatibility with early versions which *only* contained the
`object`-typed property as far as possible, but we recommend that all existing code using that property
now migrates to use the `DateTimeOffset`-based property if possible, or the raw string property otherwise.
2 changes: 2 additions & 0 deletions docs/devsite-help/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
href: emulators.md
- name: Library types
href: library-types.md
- name: REST libraries
href: rest-libraries.md
- name: Protocol buffers
href: protobuf.md
- name: Supported platforms
Expand Down

0 comments on commit dc94e7b

Please sign in to comment.