├── .gitignore
├── .travis.yml
├── Aliyun.Serverless.Core.Http
├── Aliyun.Serverless.Core.Http.csproj
├── FcHttpEntrypoint.cs
├── FcHttpServer.cs
├── FcWebHostBuilderExtensions.cs
└── InvokeFeatures.cs
├── Aliyun.Serverless.Core.Mock
├── .DS_Store
├── Aliyun.Serverless.Core.Mock.csproj
├── ConsoleLogger.cs
├── Credentials.cs
├── FcContext.cs
├── FunctionParameter.cs
└── ServiceMeta.cs
├── Aliyun.Serverless.Core
├── Aliyun.Serverless.Core.csproj
├── FcSerializerAttribute.cs
├── ICredentials.cs
├── IFcContext.cs
├── IFcLogger.cs
├── IFcSerializer.cs
├── IFunctionParameter.cs
├── IServiceMeta.cs
└── JsonSerializerException.cs
├── Aliyun.Serverless.Events
├── Aliyun.Serverless.Events.csproj
└── OSSEvent.cs
├── Aliyun.Serverless.sln
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vs
3 | bin
4 | obj
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | solution: Aliyun.Serverless.sln
3 |
4 | mono: none
5 |
6 | dotnet: 2.1.4
7 |
8 | script:
9 | - dotnet build Aliyun.Serverless.sln
10 |
--------------------------------------------------------------------------------
/Aliyun.Serverless.Core.Http/Aliyun.Serverless.Core.Http.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Alibaba Cloud
6 | The SDK for developing http invoke functions of Alibaba Cloud Function Compute
7 | Aliyun.Serverless.Core.Http
8 | Aliyun;Alibaba Cloud;Serverless
9 | 1.0.3
10 | Aliyun Serverless .NET Core support - Core package.
11 | Aliyun.Serverless.Core.Http
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Aliyun.Serverless.Core.Http/FcHttpEntrypoint.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.AspNetCore.Hosting.Internal;
6 | using Microsoft.AspNetCore.Http.Features;
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Primitives;
11 | using System.Collections.Generic;
12 | using System.IO;
13 | using System.Linq;
14 | using System.Net;
15 | using System.Reflection;
16 | using System.Security.Claims;
17 | using System.Text;
18 | using System.Text.Encodings.Web;
19 | using Microsoft.AspNetCore.Http;
20 | using Microsoft.Extensions.FileProviders;
21 | namespace Aliyun.Serverless.Core.Http
22 | {
23 | public abstract class FcHttpEntrypoint
24 | {
25 | ///
26 | /// Key to access the ILambdaContext object from the HttpContext.Items collection.
27 | ///
28 | public const string FC_CONTEXT = "FcContext";
29 |
30 | private FcHttpServer _server;
31 |
32 | protected IWebHost _host;
33 |
34 | private static string _pathBase;
35 |
36 | private static readonly object startLock = new object();
37 |
38 | ///
39 | /// Should be called in the derived constructor
40 | ///
41 | protected void Start()
42 | {
43 | var builder = CreateWebHostBuilder();
44 | Init(builder);
45 |
46 | _host = builder.Build();
47 | this.PostInit(_host);
48 | _host.Start();
49 |
50 | _server = _host.Services.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as FcHttpServer;
51 | if (_server == null)
52 | {
53 | throw new Exception("Failed to find the implementation FcHttpServer for the IServer registration. This can happen if UseFcServer was not called.");
54 | }
55 | }
56 |
57 | ///
58 | /// Gets the logger.
59 | ///
60 | /// The logger.
61 | public IFcLogger Logger { get; private set; }
62 |
63 | public static string PathBase
64 | {
65 | get
66 | {
67 | return _pathBase;
68 | }
69 | }
70 |
71 | public static string AppCodePath
72 | {
73 | get; set;
74 | }
75 |
76 | ///
77 | /// Gets a value indicating whether this is started.
78 | ///
79 | /// true if is started; otherwise, false.
80 | private bool IsStarted
81 | {
82 | get
83 | {
84 | return _server != null;
85 | }
86 | }
87 |
88 | ///
89 | /// Method to initialize the web builder before starting the web host. In a typical Web API this is similar to the main function.
90 | /// Setting the Startup class is required in this method.
91 | ///
92 | ///
93 | ///
94 | /// protected override void Init(IWebHostBuilder builder)
95 | /// {
96 | /// builder
97 | /// .UseStartup<Startup>();
98 | /// }
99 | ///
100 | ///
101 | ///
102 | protected abstract void Init(IWebHostBuilder builder);
103 |
104 | ///
105 | /// action after init.
106 | ///
107 | /// Host.
108 | protected virtual void PostInit(IWebHost host) { }
109 |
110 | ///
111 | /// Creates the IWebHostBuilder similar to WebHost.CreateDefaultBuilder but replacing the registration of the Kestrel web server with a
112 | /// registration for ApiGateway.
113 | ///
114 | ///
115 | protected virtual IWebHostBuilder CreateWebHostBuilder()
116 | {
117 | var builder = new WebHostBuilder()
118 | .ConfigureAppConfiguration((hostingContext, config) =>
119 | {
120 | var env = hostingContext.HostingEnvironment;
121 |
122 | config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
123 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
124 | PhysicalFileProvider fileProvider = config.Properties["FileProvider"] as PhysicalFileProvider;
125 | if (fileProvider != null)
126 | {
127 | Logger.LogInformation("FileProvider Root: {0}", fileProvider.Root);
128 | }
129 |
130 | if (env.IsDevelopment())
131 | {
132 | var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
133 | if (appAssembly != null)
134 | {
135 | config.AddUserSecrets(appAssembly, optional: true);
136 | }
137 | }
138 |
139 | config.AddEnvironmentVariables();
140 | })
141 | .ConfigureLogging((hostingContext, logging) =>
142 | {
143 | logging.ClearProviders();
144 | })
145 | .UseDefaultServiceProvider((hostingContext, options) =>
146 | {
147 | options.ValidateScopes = hostingContext.HostingEnvironment.IsDevelopment();
148 | })
149 | .UseFcServer();
150 |
151 | return builder;
152 | }
153 |
154 | public virtual async Task HandleRequest(HttpRequest request, HttpResponse response, IFcContext fcContext)
155 | {
156 | Logger = fcContext.Logger;
157 | lock (startLock)
158 | {
159 | if (!IsStarted)
160 | {
161 | _pathBase = request.PathBase;
162 | Logger.LogInformation("Setting global PathBase {0}", _pathBase);
163 | Start();
164 | }
165 | }
166 |
167 | Logger.LogDebug("Incoming {0} requests Path: {1}, Pathbase {2}", request.Method, request.Path, request.PathBase);
168 |
169 | InvokeFeatures features = new InvokeFeatures();
170 | MarshallRequest(features, request, fcContext);
171 | Logger.LogDebug($"ASP.NET Core Request PathBase: {((IHttpRequestFeature)features).PathBase}, Path: {((IHttpRequestFeature)features).Path}");
172 |
173 | var httpContext = this.CreateContext(features);
174 |
175 | if (request?.HttpContext?.User != null)
176 | {
177 | httpContext.HttpContext.User = request.HttpContext.User;
178 | }
179 |
180 | // Add along the Lambda objects to the HttpContext to give access to FC to them in the ASP.NET Core application
181 | httpContext.HttpContext.Items[FC_CONTEXT] = fcContext;
182 |
183 | // Allow the context to be customized before passing the request to ASP.NET Core.
184 | PostCreateContext(httpContext, request, fcContext);
185 |
186 | await this.ProcessRequest(fcContext, httpContext, features, response);
187 |
188 | return response;
189 | }
190 |
191 | ///
192 | /// This method is called after the FcHttpEntrypoint has marshalled the incoming API request
193 | /// into ASP.NET Core's IHttpRequestFeature. Derived classes can overwrite this method to alter
194 | /// the how the marshalling was done.
195 | ///
196 | /// ASP net core request feature.
197 | /// Request.
198 | /// Fc context.
199 | protected virtual void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, HttpRequest request, IFcContext fcContext)
200 | {
201 |
202 | }
203 |
204 |
205 | ///
206 | /// This method is called after the FcHttpEntrypoint has marshalled the incoming API Gateway request
207 | /// into ASP.NET Core's IHttpConnectionFeature. Derived classes can overwrite this method to alter
208 | /// the how the marshalling was done.
209 | ///
210 | /// ASP net core connection feature.
211 | /// Request.
212 | /// Fc context.
213 | protected virtual void PostMarshallConnectionFeature(IHttpConnectionFeature aspNetCoreConnectionFeature, HttpRequest request, IFcContext fcContext)
214 | {
215 |
216 | }
217 |
218 |
219 | ///
220 | /// This method is called after the FcHttpEntrypoint has marshalled IHttpResponseFeature that came
221 | /// back from making the request into ASP.NET Core into API Gateway's response object HttpResonse. Derived classes can overwrite this method to alter
222 | /// the how the marshalling was done.
223 | ///
224 | /// ASP net core response feature.
225 | /// Response.
226 | /// Fc context.
227 | protected virtual void PostMarshallResponseFeature(IHttpResponseFeature aspNetCoreResponseFeature, HttpResponse response, IFcContext fcContext)
228 | {
229 |
230 | }
231 |
232 |
233 | ///
234 | /// This method is called after the HostingApplication.Context has been created. Derived classes can overwrite this method to alter
235 | /// the context before passing the request to ASP.NET Core to process the request.
236 | ///
237 | /// Context.
238 | /// Request.
239 | /// Fc context.
240 | protected virtual void PostCreateContext(HostingApplication.Context context, HttpRequest request, IFcContext fcContext)
241 | {
242 |
243 | }
244 |
245 | ///
246 | /// Creates a object using the field in the class.
247 | ///
248 | /// implementation.
249 | protected HostingApplication.Context CreateContext(IFeatureCollection features)
250 | {
251 | return _server.Application.CreateContext(features);
252 | }
253 |
254 | ///
255 | /// Convert the JSON document received from API Gateway into the InvokeFeatures object.
256 | /// InvokeFeatures is then passed into IHttpApplication to create the ASP.NET Core request objects.
257 | ///
258 | /// Features.
259 | /// Request.
260 | /// Fc context.
261 | protected void MarshallRequest(InvokeFeatures features, HttpRequest request, IFcContext fcContext)
262 | {
263 | {
264 | var requestFeatures = (IHttpRequestFeature)features;
265 | requestFeatures.Scheme = "https";
266 | requestFeatures.Method = request.Method;
267 |
268 | requestFeatures.Path = request.Path; // the PathString ensures it starts with "/";
269 | requestFeatures.PathBase = request.PathBase;
270 | requestFeatures.QueryString = request.QueryString.Value;
271 | requestFeatures.Headers = request.Headers;
272 | requestFeatures.Body = request.Body;
273 |
274 | // Call consumers customize method in case they want to change how API request
275 | // was marshalled into ASP.NET Core request.
276 | PostMarshallRequestFeature(requestFeatures, request, fcContext);
277 | }
278 |
279 |
280 | {
281 | // set up connection features
282 | var connectionFeatures = (IHttpConnectionFeature)features;
283 | connectionFeatures.RemoteIpAddress = request?.HttpContext?.Connection?.RemoteIpAddress;
284 | if (request?.HttpContext?.Connection?.RemotePort != null)
285 | {
286 | connectionFeatures.RemotePort = (request?.HttpContext?.Connection?.RemotePort).Value;
287 | }
288 |
289 | if (request?.Headers?.ContainsKey("X-Forwarded-Port") == true)
290 | {
291 | connectionFeatures.RemotePort = int.Parse(request.Headers["X-Forwarded-Port"]);
292 | }
293 |
294 | // Call consumers customize method in case they want to change how API Gateway's request
295 | // was marshalled into ASP.NET Core request.
296 | PostMarshallConnectionFeature(connectionFeatures, request, fcContext);
297 | }
298 | }
299 |
300 | ///
301 | /// Convert the response coming from ASP.NET Core into APIGatewayProxyResponse which is
302 | /// serialized into the JSON object that API Gateway expects.
303 | ///
304 | /// The response.
305 | /// Response features.
306 | /// Fc context.
307 | /// Status code if not set.
308 | protected HttpResponse MarshallResponse(IHttpResponseFeature responseFeatures, IFcContext fcContext, HttpResponse response, int statusCodeIfNotSet = 200)
309 | {
310 | response.StatusCode = responseFeatures.StatusCode != 0 ? responseFeatures.StatusCode : statusCodeIfNotSet;
311 | string contentType = null;
312 | if (responseFeatures.Headers != null)
313 | {
314 | foreach (var kvp in responseFeatures.Headers)
315 | {
316 | response.Headers[kvp.Key] = kvp.Value;
317 |
318 | // Remember the Content-Type for possible later use
319 | if (kvp.Key.Equals("Content-Type", StringComparison.CurrentCultureIgnoreCase))
320 | contentType = response.Headers[kvp.Key];
321 | }
322 | }
323 |
324 | if (contentType == null)
325 | {
326 | response.Headers["Content-Type"] = StringValues.Empty;
327 | }
328 |
329 | response.Body = responseFeatures.Body;
330 |
331 | PostMarshallResponseFeature(responseFeatures, response, fcContext);
332 |
333 | return response;
334 | }
335 |
336 | ///
337 | /// Processes the current request.
338 | ///
339 | /// implementation.
340 | /// The hosting application request context object.
341 | /// An instance.
342 | ///
343 | /// If specified, an unhandled exception will be rethrown for custom error handling.
344 | /// Ensure that the error handling code calls 'this.MarshallResponse(features, 500);' after handling the error to return a to the user.
345 | ///
346 | protected async Task ProcessRequest(IFcContext fcContext, HostingApplication.Context context, InvokeFeatures features, HttpResponse response, bool rethrowUnhandledError = false)
347 | {
348 | var defaultStatusCode = 200;
349 | Exception ex = null;
350 | try
351 | {
352 | await this._server.Application.ProcessRequestAsync(context);
353 | }
354 | catch (AggregateException agex)
355 | {
356 | ex = agex;
357 | Logger.LogError($"Caught AggregateException: '{agex}'");
358 | var sb = new StringBuilder();
359 | foreach (var newEx in agex.InnerExceptions)
360 | {
361 | sb.AppendLine(this.ErrorReport(newEx));
362 | }
363 |
364 | Logger.LogError(sb.ToString());
365 | defaultStatusCode = 500;
366 | }
367 | catch (ReflectionTypeLoadException rex)
368 | {
369 | ex = rex;
370 | Logger.LogError($"Caught ReflectionTypeLoadException: '{rex}'");
371 | var sb = new StringBuilder();
372 | foreach (var loaderException in rex.LoaderExceptions)
373 | {
374 | var fileNotFoundException = loaderException as FileNotFoundException;
375 | if (fileNotFoundException != null && !string.IsNullOrEmpty(fileNotFoundException.FileName))
376 | {
377 | sb.AppendLine($"Missing file: {fileNotFoundException.FileName}");
378 | }
379 | else
380 | {
381 | sb.AppendLine(this.ErrorReport(loaderException));
382 | }
383 | }
384 |
385 | Logger.LogError(sb.ToString());
386 | defaultStatusCode = 500;
387 | }
388 | catch (Exception e)
389 | {
390 | ex = e;
391 | if (rethrowUnhandledError) throw;
392 | Logger.LogError($"Unknown error responding to request: {this.ErrorReport(e)}");
393 | defaultStatusCode = 500;
394 | }
395 | finally
396 | {
397 | this._server.Application.DisposeContext(context, ex);
398 | }
399 |
400 | if (features.ResponseStartingEvents != null)
401 | {
402 | await features.ResponseStartingEvents.ExecuteAsync();
403 | }
404 |
405 | this.MarshallResponse(features, fcContext, response, defaultStatusCode);
406 |
407 | if (features.ResponseCompletedEvents != null)
408 | {
409 | await features.ResponseCompletedEvents.ExecuteAsync();
410 | }
411 |
412 | return response;
413 | }
414 |
415 | ///
416 | /// Formats an Exception into a string, including all inner exceptions.
417 | ///
418 | /// instance.
419 | protected string ErrorReport(Exception e)
420 | {
421 | var sb = new StringBuilder();
422 | sb.AppendLine($"{e.GetType().Name}:\n{e}");
423 |
424 | Exception inner = e;
425 | while (inner != null)
426 | {
427 | // Append the messages to the StringBuilder.
428 | sb.AppendLine($"{inner.GetType().Name}:\n{inner}");
429 | inner = inner.InnerException;
430 | }
431 |
432 | return sb.ToString();
433 | }
434 | }
435 | }
436 |
--------------------------------------------------------------------------------
/Aliyun.Serverless.Core.Http/FcHttpServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Hosting.Internal;
5 | using Microsoft.AspNetCore.Hosting.Server;
6 | using Microsoft.AspNetCore.Http.Features;
7 |
8 | namespace Aliyun.Serverless.Core.Http
9 | {
10 | public class FcHttpServer : IServer
11 | {
12 | ///
13 | /// The application is used by the Fc function to initiate a web request through the ASP.NET Core framework.
14 | ///
15 | public IHttpApplication Application { get; set; }
16 |
17 | ///
18 | /// Gets the features.
19 | ///
20 | /// The features.
21 | public IFeatureCollection Features { get; } = new FeatureCollection();
22 |
23 | public void Dispose()
24 | {
25 | }
26 |
27 | public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken)
28 | {
29 | this.Application = application as IHttpApplication;
30 | return Task.CompletedTask;
31 | }
32 |
33 | public Task StopAsync(CancellationToken cancellationToken)
34 | {
35 | return Task.CompletedTask;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Aliyun.Serverless.Core.Http/FcWebHostBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.AspNetCore.Hosting.Server;
8 | using Aliyun.Serverless.Core.Http;
9 |
10 | namespace Microsoft.AspNetCore.Hosting
11 | {
12 | public static class FcWebHostBuilderExtensions
13 | {
14 | public static IWebHostBuilder UseFcServer(this IWebHostBuilder builder)
15 | {
16 | return builder.ConfigureServices(services =>
17 | {
18 | var serviceDescription = services.FirstOrDefault(x => x.ServiceType == typeof(IServer));
19 | if (serviceDescription != null)
20 | {
21 | // If Fc server has already been added the skip out.
22 | if (serviceDescription.ImplementationType == typeof(FcHttpServer))
23 | return;
24 | // If there is already an IServer registered then remove it. This is mostly likely caused
25 | // by leaving the UseKestrel call.
26 | else
27 | services.Remove(serviceDescription);
28 | }
29 |
30 | services.AddSingleton();
31 | });
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/Aliyun.Serverless.Core.Http/InvokeFeatures.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Threading.Tasks;
8 |
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.AspNetCore.Http.Features;
11 |
12 | #pragma warning disable 1591
13 |
14 | namespace Aliyun.Serverless.Core.Http
15 | {
16 | public class InvokeFeatures : IFeatureCollection,
17 | IHttpRequestFeature,
18 | IHttpResponseFeature,
19 | IHttpConnectionFeature
20 | {
21 |
22 | public InvokeFeatures()
23 | {
24 | _features[typeof(IHttpRequestFeature)] = this;
25 | _features[typeof(IHttpResponseFeature)] = this;
26 | _features[typeof(IHttpConnectionFeature)] = this;
27 | }
28 |
29 | #region IFeatureCollection
30 | public bool IsReadOnly => false;
31 |
32 | IDictionary _features = new Dictionary();
33 |
34 | public int Revision => 0;
35 |
36 | public object this[Type key]
37 | {
38 | get
39 | {
40 | object feature;
41 | if (_features.TryGetValue(key, out feature))
42 | {
43 | return feature;
44 | }
45 |
46 | return null;
47 | }
48 |
49 | set
50 | {
51 | _features[key] = value;
52 | }
53 | }
54 |
55 | public TFeature Get()
56 | {
57 | object feature;
58 | if (_features.TryGetValue(typeof(TFeature), out feature))
59 | {
60 | return (TFeature)feature;
61 | }
62 |
63 | return default(TFeature);
64 | }
65 |
66 | public IEnumerator> GetEnumerator()
67 | {
68 | return this._features.GetEnumerator();
69 | }
70 |
71 | public void Set(TFeature instance)
72 | {
73 | if (instance == null)
74 | return;
75 |
76 | this._features[typeof(TFeature)] = instance;
77 | }
78 |
79 | IEnumerator IEnumerable.GetEnumerator()
80 | {
81 | return this._features.GetEnumerator();
82 | }
83 |
84 | #endregion
85 |
86 | #region IHttpRequestFeature
87 | string IHttpRequestFeature.Protocol { get; set; }
88 |
89 | string IHttpRequestFeature.Scheme { get; set; }
90 |
91 | string IHttpRequestFeature.Method { get; set; }
92 |
93 | string IHttpRequestFeature.PathBase { get; set; }
94 |
95 | string IHttpRequestFeature.Path { get; set; }
96 |
97 | string IHttpRequestFeature.QueryString { get; set; }
98 |
99 | string IHttpRequestFeature.RawTarget { get; set; }
100 |
101 | IHeaderDictionary IHttpRequestFeature.Headers { get; set; } = new HeaderDictionary();
102 |
103 | Stream IHttpRequestFeature.Body { get; set; } = new MemoryStream();
104 |
105 | #endregion
106 |
107 | #region IHttpResponseFeature
108 | int IHttpResponseFeature.StatusCode
109 | {
110 | get;
111 | set;
112 | }
113 |
114 | string IHttpResponseFeature.ReasonPhrase
115 | {
116 | get;
117 | set;
118 | }
119 |
120 | bool IHttpResponseFeature.HasStarted
121 | {
122 | get;
123 | }
124 |
125 | IHeaderDictionary IHttpResponseFeature.Headers
126 | {
127 | get;
128 | set;
129 | } = new HeaderDictionary();
130 |
131 | Stream IHttpResponseFeature.Body
132 | {
133 | get;
134 | set;
135 | } = new MemoryStream();
136 |
137 | internal EventCallbacks ResponseStartingEvents { get; private set; }
138 | void IHttpResponseFeature.OnStarting(Func