Skip to content
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

adds support for http middlewares #330

Merged
merged 19 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Better client configuration #268
- Request builders constructors for data validation #322
- Adds middleware support for http clients #330

## [0.0.5] - 2021-06-10

Expand Down
7 changes: 7 additions & 0 deletions abstractions/dotnet/src/IMiddlewareOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Kiota.Abstractions {
/// <summary>
/// Represents a middleware option.
/// </summary>
public interface IMiddlewareOption {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net5.0</TargetFramework>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<RepositoryUrl>https://github.com/microsoft/kiota</RepositoryUrl>
<Version>1.0.13</Version>
<Version>1.0.14</Version>
</PropertyGroup>

</Project>
25 changes: 25 additions & 0 deletions abstractions/dotnet/src/RequestInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using Microsoft.Kiota.Abstractions.Serialization;
Expand Down Expand Up @@ -30,6 +31,30 @@ public class RequestInfo
/// The Request Body.
/// </summary>
public Stream Content { get; set; }
private Dictionary<string, IMiddlewareOption> _middlewareOptions = new Dictionary<string, IMiddlewareOption>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets the middleware options for this request. Options are unique by type. If an option of the same type is added twice, the last one wins.
/// </summary>
public IEnumerable<IMiddlewareOption> MiddlewareOptions { get { return _middlewareOptions.Values; } }
/// <summary>
/// Adds a middleware option to the request.
/// </summary>
/// <param name="middlewareOption">The middleware option to add.</param>
public void AddMiddlewareOptions(params IMiddlewareOption[] options) {
if(!options?.Any() ?? false) return; // it's a no-op if there are no options and this avoid having to check in the code gen.
foreach(var option in options.Where(x => x != null))
if(!_middlewareOptions.TryAdd(option.GetType().FullName, option))
_middlewareOptions[option.GetType().FullName] = option;
}
/// <summary>
/// Removes given middleware options from the current request.
/// </summary>
/// <param name="options">Middleware options to remove.</param>
public void RemoveMiddlewareOptions(params IMiddlewareOption[] options) {
if(!options?.Any() ?? false) throw new ArgumentNullException(nameof(options));
foreach(var optionName in options.Where(x => x != null).Select(x => x.GetType().FullName))
_middlewareOptions.Remove(optionName);
}
private const string binaryContentType = "application/octet-stream";
private const string contentTypeHeader = "Content-Type";
/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion abstractions/java/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ publishing {
publications {
gpr(MavenPublication) {
artifactId 'kiota-abstractions'
version '1.0.13'
version '1.0.14'
from(components.java)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.microsoft.kiota;

/** Represents a middleware option. */
public interface MiddlewareOption {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.net.URI;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
import java.util.function.Function;
Expand Down Expand Up @@ -31,6 +32,32 @@ public class RequestInfo {
/** The Request Body. */
@Nullable
public InputStream content;
private HashMap<String, MiddlewareOption> _middlewareOptions = new HashMap<>();
/**
* Gets the middleware options for this request. Options are unique by type. If an option of the same type is added twice, the last one wins.
* @return the middleware options for this request.
*/
public Collection<MiddlewareOption> getMiddlewareOptions() { return _middlewareOptions.values(); }
/**
* Adds a middleware option to this request.
* @param option the middleware option to add.
*/
public void addMiddlewareOptions(@Nullable final MiddlewareOption... options) {
if(options == null || options.length == 0) return;
for(final MiddlewareOption option : options) {
_middlewareOptions.put(option.getClass().getCanonicalName(), option);
}
}
/**
* Removes a middleware option from this request.
* @param option the middleware option to remove.
*/
public void removeMiddlewareOptions(@Nullable final MiddlewareOption... options) {
if(options == null || options.length == 0) return;
for(final MiddlewareOption option : options) {
_middlewareOptions.remove(option.getClass().getCanonicalName());
}
}
private static String binaryContentType = "application/octet-stream";
private static String contentTypeHeader = "Content-Type";
/**
Expand Down
2 changes: 1 addition & 1 deletion abstractions/typescript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion abstractions/typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@microsoft/kiota-abstractions",
"version": "1.0.13",
"version": "1.0.14",
"description": "Core abstractions for kiota generated libraries in TypeScript and JavaScript",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
3 changes: 2 additions & 1 deletion abstractions/typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from "./nativeResponseHandler";
export * from "./nativeResponseWrapper";
export * from './serialization';
export * from './utils';
export * from './store';
export * from './store';
export * from './middlewareOption';
5 changes: 5 additions & 0 deletions abstractions/typescript/src/middlewareOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** Represents a middleware option. */
export interface MiddlewareOption {
/** Gets the option key for when adding it to a request. Must be unique. */
getKey(): string;
}
17 changes: 17 additions & 0 deletions abstractions/typescript/src/requestInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HttpMethod } from "./httpMethod";
import { ReadableStream } from 'web-streams-polyfill/es2018';
import { Parsable } from "./serialization";
import { HttpCore } from "./httpCore";
import { MiddlewareOption } from "./middlewareOption";

/** This class represents an abstract HTTP request. */
export class RequestInfo {
Expand All @@ -15,6 +16,22 @@ export class RequestInfo {
public queryParameters: Map<string, object> = new Map<string, object>(); //TODO: case insensitive
/** The Request Headers. */
public headers: Map<string, string> = new Map<string, string>(); //TODO: case insensitive
private _middlewareOptions = new Map<string, MiddlewareOption>(); //TODO: case insensitive
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use something like [MiddlewareControl](https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/src/middleware/MiddlewareControl.ts instead of using _middlewareOptions. Please move addMiddlewareOptions and getMiddlewareOptions options to the MiddlewareControl.

  • A separate class for managing the properties as an object allows extensibility. A user can extend the MiddlewareControl class and flexibly customize their get and add middleware options or any property of the requestinfo class.
  • We can observe Single Responsibility Principle if we neatly segregate some functionalities into specific classes.
  • Avoids bulking up the RequestInfo class with too many methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was looking at the dotnet sdk which uses options but have uses control too. I'm not sure why we have a difference between languages today. @darrelmiller any guidance on options or control?
https://www.github.com/microsoftgraph/msgraph-sdk-dotnet-core/tree/dev/src%2FMicrosoft.Graph.Core%2FRequests%2FMiddleware%2FOptions%2FIMiddlewareOption.cs

We're attaching the options to request info to avoid passing multiple parameters between the layers. Request info is already being passed between layers. And the options or controls are related to the request info, which is why it felt natural to add them there. I'm not sure how you'd do it differently without having to pass additional parameters to the http core service?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both JS and .NET use the term Options. It looks like JS has a container for for middlewareOptions. .NET just uses a dictionary. I'm not sure why JS has the extra layer of indirection. Part of the confusion might be because the original requirement document talked about middleware controls. That notion got renamed to middleware options during implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the context here. So I'm guessing we're sticking with options then.

/** Gets the middleware options for the request. */
public getMiddlewareOptions() { return this._middlewareOptions.values(); }
public addMiddlewareOptions(...options: MiddlewareOption[]) {
if(!options || options.length === 0) return;
options.forEach(option => {
this._middlewareOptions.set(option.getKey(), option);
});
}
/** Removes the middleware options for the request. */
public removeMiddlewareOptions(...options: MiddlewareOption[]) {
if(!options || options.length === 0) return;
options.forEach(option => {
this._middlewareOptions.delete(option.getKey());
});
}
private static binaryContentType = "application/octet-stream";
private static contentTypeHeader = "Content-Type";
/**
Expand Down
49 changes: 49 additions & 0 deletions http/dotnet/httpclient/src/HttpClientBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using Microsoft.Kiota.Abstractions;

namespace Microsoft.Kiota.Http.HttpClient {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the likelihood of this namespace being used in the same context as System.Net.Http.HttpClient? If they show up in the same context, the naming collision can be confusing for the customers. Could we rename the last part or drop it altogether to bump the namespace one level up to Microsoft.Kiota.Http?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baywet, I think @nikithauc is referring to this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aaah we had a long naming debate with @darrelmiller a long time ago.
I wanted to name this Microsoft.Kiota.Transport.Http leaving room for other transport implementations potentially in the future. Thoughts on that name? And for typescript we'd have something like @microsoft/kiota-transport-http.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are transport implementations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well one of them is HTTP of course, another one could be gRPC, etc...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think another common thing between HTTP and gRPC is that they are both protocols. So we can also have Microsoft.Kiota.Protocol.Http or Microsoft.Kiota.Protocols.Http as the namespace alternatives. I am OK with Microsoft.Kiota.Transport.Http too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think transport is correct here. Protocol is more accurate. For example, if we introduced WebSockets, we'd have:
Microsoft.Kiota.Protocol.Http
Microsoft.Kiota.Protocol.WebSocket

They use the same transport.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Microsoft.Kiota.Protocol.Http
Microsoft.Kiota.Protocol.WebSocket

The namespace looks meaningful.

Hypertext Transfer Protocol (HTTP) is an application-layer protocol for transmitting hypermedia documents, such as HTML. It was designed for communication between web browsers and web servers, but it can also be used for other purposes.

https://developer.mozilla.org/en-US/docs/Web/HTTP

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, protocol works for me as well. As this is a discussion on existing naming pattern, and as I'd like Darrel's "blessing" before changing the naming, I'm going to create a separate issue for that. Thanks for all the great suggestions!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// <summary>
/// This class is used to build the HttpClient instance used by the core service.
/// </summary>
public static class HttpClientBuilder {
/// <summary>
/// Initializes the <see cref="HttpClient"/> with the default configuration and middlewares including a authentention middleware using the <see cref="IAuthenticationProvider"/> if provided.
/// </summary>
/// <param name="authenticationProvider">The <see cref="IAuthenticationProvider"/> to use for authentention.</param>
/// <returns>The <see cref="HttpClient"/> with the default middlewares.</returns>
public static System.Net.Http.HttpClient Create(IAuthenticationProvider authenticationProvider = default) {
var defaultHandlers = CreateDefaultHandlers(authenticationProvider);
var handler = ChainHandlersCollectionAndGetFirstLink(defaultHandlers.ToArray());
return handler != null ? new System.Net.Http.HttpClient(handler) : new System.Net.Http.HttpClient(); //TODO configure the default client options
}
/// <summary>
/// Creates a default set of middleware to be used by the <see cref="HttpClient"/>.
/// </summary>
/// <param name="authenticationProvider">The <see cref="IAuthenticationProvider"/> to authenticate requests.</param>
/// <returns>A list of the default handlers used by the client.</returns>
public static IList<DelegatingHandler> CreateDefaultHandlers(IAuthenticationProvider authenticationProvider = default)
{
return new List<DelegatingHandler>(); //TODO add the default middlewares when they are ready
}
/// <summary>
/// Creates a <see cref="DelegatingHandler"/> to use for the <see cref="HttpClient" /> from the provided <see cref="DelegatingHandler"/> instances. Order matters.
/// </summary>
/// <param name="handlers">The <see cref="DelegatingHandler"/> instances to create the <see cref="DelegatingHandler"/> from.</param>
/// <returns>The created <see cref="DelegatingHandler"/>.</returns>
public static DelegatingHandler ChainHandlersCollectionAndGetFirstLink(params DelegatingHandler[] handlers) {
if(handlers == null || !handlers.Any()) return default;
var handlersCount = handlers.Count();
for(var i = 0; i < handlersCount; i++) {
var handler = handlers[i];
var previousItemIndex = i - 1;
if(previousItemIndex >= 0) {
var previousHandler = handlers[previousItemIndex];
previousHandler.InnerHandler = handler;
}
}
return handlers.First();
}
}
}
5 changes: 4 additions & 1 deletion http/dotnet/httpclient/src/HttpCore.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -28,7 +29,7 @@ public HttpCore(IAuthenticationProvider authenticationProvider, IParseNodeFactor
{
authProvider = authenticationProvider ?? throw new ArgumentNullException(nameof(authenticationProvider));
createdClient = httpClient == null;
client = httpClient ?? new System.Net.Http.HttpClient();
client = httpClient ?? HttpClientBuilder.Create(authProvider);
pNodeFactory = parseNodeFactory ?? ParseNodeFactoryRegistry.DefaultInstance;
sWriterFactory = serializationWriterFactory ?? SerializationWriterFactoryRegistry.DefaultInstance;
}
Expand Down Expand Up @@ -128,6 +129,8 @@ private HttpRequestMessage GetRequestMessageFromRequestInfo(RequestInfo requestI
string.Empty)),

};
if(requestInfo.MiddlewareOptions.Any())
requestInfo.MiddlewareOptions.ToList().ForEach(x => message.Options.Set(new HttpRequestOptionsKey<IMiddlewareOption>(x.GetType().FullName), x));
if(requestInfo.Headers?.Any() ?? false)
requestInfo.Headers.Where(x => !contentTypeHeaderName.Equals(x.Key, StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => message.Headers.Add(x.Key, x.Value));
if(requestInfo.Content != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<TargetFramework>net5.0</TargetFramework>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<RepositoryUrl>https://github.com/microsoft/kiota</RepositoryUrl>
<Version>1.0.3</Version>
<Version>1.0.4</Version>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Kiota.Abstractions" Version="1.0.13" />
<PackageReference Include="Microsoft.Kiota.Abstractions" Version="1.0.14" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions http/java/okhttp/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ dependencies {
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:30.1.1-jre'
api 'com.squareup.okhttp3:okhttp:4.9.1'
api 'com.microsoft.kiota:kiota-abstractions:1.0.13'
api 'com.microsoft.kiota:kiota-abstractions:1.0.14'
}

publishing {
Expand All @@ -53,7 +53,7 @@ publishing {
publications {
gpr(MavenPublication) {
artifactId 'kiota-http-okhttp'
version '1.0.3'
version '1.0.4'
from(components.java)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package com.microsoft.kiota.http;

import java.io.IOException;
Expand All @@ -18,6 +15,7 @@

import com.microsoft.kiota.ApiClientBuilder;
import com.microsoft.kiota.RequestInfo;
import com.microsoft.kiota.MiddlewareOption;
import com.microsoft.kiota.ResponseHandler;
import com.microsoft.kiota.AuthenticationProvider;
import com.microsoft.kiota.serialization.ParseNodeFactoryRegistry;
Expand Down Expand Up @@ -55,7 +53,7 @@ public HttpCore(@Nonnull final AuthenticationProvider authenticationProvider, @N
public HttpCore(@Nonnull final AuthenticationProvider authenticationProvider, @Nullable final ParseNodeFactory parseNodeFactory, @Nullable final SerializationWriterFactory serializationWriterFactory, @Nullable final OkHttpClient client) {
this.authProvider = Objects.requireNonNull(authenticationProvider, "parameter authenticationProvider cannot be null");
if(client == null) {
this.client = new OkHttpClient.Builder().build();
this.client = OkHttpClientBuilder.Create(this.authProvider).build();
} else {
this.client = client;
}
Expand Down Expand Up @@ -210,6 +208,9 @@ public void writeTo(BufferedSink sink) throws IOException {
for (final Map.Entry<String,String> header : requestInfo.headers.entrySet()) {
requestBuilder.addHeader(header.getKey(), header.getValue());
}
for(final MiddlewareOption option : requestInfo.getMiddlewareOptions()) {
requestBuilder.tag(option);
}
return requestBuilder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.microsoft.kiota.http;

import com.microsoft.kiota.AuthenticationProvider;

import okhttp3.OkHttpClient;

import javax.annotation.Nullable;

/** This class is used to build the HttpClient instance used by the core service. */
public class OkHttpClientBuilder {
private OkHttpClientBuilder() { }
/**
* Creates an OkHttpClient Builder with the default configuration and middlewares including a authentention middleware using the {@link AuthenticationProvider} if provided.
* @param authenticationProvider the authentication provider used to authenticate the requests.
* @return an OkHttpClient Builder instance.
*/
public static OkHttpClient.Builder Create(@Nullable final AuthenticationProvider authenticationProvider) {
return new OkHttpClient.Builder(); //TODO configure the default client options.
//TODO add the default middlewares when they are ready
}
}
2 changes: 1 addition & 1 deletion http/typescript/fetch/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions http/typescript/fetch/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@microsoft/kiota-http-fetch",
"version": "1.0.3",
"version": "1.0.4",
"description": "Kiota HttpCore implementation with fetch",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -29,7 +29,7 @@
"registry": "https://npm.pkg.github.com"
},
"dependencies": {
"@microsoft/kiota-abstractions": "^1.0.13",
"@microsoft/kiota-abstractions": "^1.0.14",
"cross-fetch": "^3.1.4",
"web-streams-polyfill": "^3.1.0"
},
Expand Down
Loading