Skip to content

Add support for binary response content#75

Closed
ebekker wants to merge 8 commits intoaws:masterfrom
ebekker:master
Closed

Add support for binary response content#75
ebekker wants to merge 8 commits intoaws:masterfrom
ebekker:master

Conversation

@ebekker
Copy link
Copy Markdown

@ebekker ebekker commented Mar 9, 2017

As per #74

Copy link
Copy Markdown
Member

@normj normj left a comment

Choose a reason for hiding this comment

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

Good feature add. Just want to make a slight change.


/// <summary>
/// An optional set of Content-Types that will trigger encoding the response content in Base64.
/// </summary>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What if we switch this around. Maintain a collection of common content types that we don't need to be base 64 encoded like text/html. Don't expose the data collection but add an AddTextContentType operation. Then when processing the response if the content type is not in the collection then base 64 encode it.

This way we can make it so that must users don't have to anything special to handle binary type.

Copy link
Copy Markdown
Author

@ebekker ebekker Mar 10, 2017

Choose a reason for hiding this comment

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

I like the works by default strategy. :-)

With this change, do you also want to do away with the XXXContentType property and only have the one method AddXXXContentType() as the sole interface point to this feature? If yes, should there also be a way to inspect the list and remove from the list (i.e. enumerator and RemoveXXXContentType())?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Was thinking about it a bit more -- does it make sense to support it "both ways"? For example, you can establish a default behavior (to encode or not to encode), and then define a list of exceptions -- the content types that alter the default behavior. And then of course, the default configuration is as you described which would accommodate most users. Something like:

enum ResponseEncoding
{
  String = 0, // Or UTF8 or something...
  Base64 = 1,
}

class APIGatewayProxyFunction
{
  protected Dictionary<string, ResponseEncoding> _responseEncodingForContentType =
      new Dictionary<string, ResponseEncoding>
      {
        // The defaults, such as from https://en.wikipedia.org/wiki/Media_type#Common_examples
        ["text/plain"] = ResponseEncoding.String,
        ["text/html"] = ResponseEncoding.String,
        ["text/css"] = ResponseEncoding.String,
        ["application/xml"] = ResponseEncoding.String,
        ["application/json"] = ResponseEncoding.String,
        // etc...
      };

  public ResponseEncoding DefaultResponseEncoding
  { get; set; } = ResponseEncoding.Base64;

  public void AddResponseEncodingForContentType(string contentType, ResponseEncoding encoding)
  {
    // ...
  }
}

Don't want to explode this feature change, but just thinking about the possible usage scenarios and what would be most stable for the long-term.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Just occurred to me there is one more dependency to consider with whatever interface changes are made to support this, namely that you also have to update (and keep in sync) the Binary Support settings of the API Gateway by enumerating the binary content-types that your app will support.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like the code changes except I would keep _responseEncodingForContentType private at least for now. I'd rather make it protected later if we find we really need to.

Hopefully the API Gateway setting will be made available in the serverless.template at some point which is where I would expect that to be maintain. Right now that seems beyond the scope of this change.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Mar 10, 2017

@normj, I've incorporated all the changes we discussed.

One thing to note, because the default disposition is to treat all responses as binary unless otherwise specified through registrations and/or default encoding property, many of the existing unit tests were failing because they were comparing content that was now being returned Base64-encoded. For that reason you'll see that most of tests were revised to use a slightly different pattern for looking at the response content body with the help of a new extension method on the response object.

For example, what used to be something like:

 Assert.True(response.Body.Length > 0);

is now

 Assert.True(response.BodyAsString().Length > 0);

The extension method now looks at the new IsBase64Encoded flag in the response and returns the Body as a string with possible transformation. As you mention in the comments for the extensions class, this is really only a concern for testing situations, however, it could potentially introduce a breaking change for existing users in their unit tests.

I suppose this is OK, since the AspNetCoreServer package is still considered experimental?

* Forgot to remove unnecessary usings
* Forgot to rename file after renaming enumeration
@normj
Copy link
Copy Markdown
Member

normj commented Mar 16, 2017

I was testing this feature in my dev branch. I didn't realize that for proxy requests, which is what we use for the ASP.NET Core stuff, the client has to send an Accept header for the binary content type. Seeing as this is likely to be called from a browser which isn't going to send the Accept header is this feature still useful?

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Mar 16, 2017

I agree the API Gateway (and maybe Lambda?) teams are placing some unusual, and seemingly unnecessary obstacles on making the most of their platforms, but perhaps they'll relax those restrictions at some point. If they simply allowed for a "default" mapping of binary content type, that would go a long way with many apps, perhaps I'll submit that request...

Regardless, this support for binary content is still quite accessible when dealing with non-browser clients, such as mobile apps or custom clients. Also, even with conventional browsers, the request Accept headers can be managed when dealing with dynamic JavaScript calls (via XMLHTTPRequest) which is more and more the norm these days with modern JS app frameworks (Angular, Aurelia, React, etc.) or just good old jQuery.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Mar 16, 2017

Actually just discovered something very useful -- it turns out that you can force APIGW + Lambda to always respond with binary content if you simply specify a Binary media type of */* -- in which case all responses will be sent exactly as returned by Lambda, which makes this addition completely relevant for all clients.

I've confirmed, with this setting in place, the behavior of ASP.NET Core on Lambda now seems compatible with the expected behavior otherwise. I original opened this PR to clean up a similar hack I had to introduce in my project and with this latest change, I've confirmed the behavior is now compatible with millions of clients deployed on Windows.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Mar 28, 2017

@normj, any other suggestions?

@muneebm
Copy link
Copy Markdown

muneebm commented Apr 3, 2017

@ebekker thanks for implementing this feature! This would be especially useful for integrating Swashbuckle to a net core web API lambda project. We were able to integrate Swashbuckle successfully and can access the swagger documentation through API gateway, the only bottleneck now is getting the swagger ui image files through API gateway and I hope that can be solved by having IsBase64Encoded flag in APIGatewayProxyResponse

@normj
Copy link
Copy Markdown
Member

normj commented Apr 19, 2017

Sorry I have been off for a bit and coming back to this.

Initially when I suggested we default to Base64 I assumed everything would be transparent to the user but since we found out it requires additional configuration on the API Gateway side I think we should change DefaultResponseContentEncoding to be Default instead of Base64.

I don't need another pull request for the change. I have things merged into a local branch. I just wanted your thoughts before making that change.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Apr 21, 2017

So you're saying that 'DefaultResponseContentEncoding' will be changed, but you would still including the base set of content-type specific mappings (the initial set in _responseContentEncodingForContentType)?

I'm OK with that, we would still need to update documentation for folks to make that work (i.e. provide guidance on adding the wildcard */* binary mime-type or a specific type).

Do you have any insight on to the timeline of updating the SAM support for adding the ability to define Binary mappings? It looks like it stems from a limitation that Binary mappings can't even be defined by CloudFormation at this point, so I guess that team needs to bring that support up to speed first.

@normj
Copy link
Copy Markdown
Member

normj commented Apr 21, 2017

Yes, I just changed DefaultResponseContentEncoding and left _responseContentEncodingForContentType alone. So images will be base64 encoded by default.

Sorry I don't have any information about the SAM support for adding the ability to define binary mappings.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Apr 24, 2017

Sounds good to me.


## Supporting Binary Response Content

The interface between the API Gateway and Lambda provides for and assumes repsonse content to be returned as a UTF-8 string.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo: repsonse -> response

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fixed, thanks


In order to facilitate this mechanism, the `APIGatewayProxyFunction` base class maintains a registry of MIME content types
and how they should be transformed before being returned to the calling API Gateway. For any binary content types that are
returned by your applicaiton, you should register them for Base64 tranformation and then the framework will take care of
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

applicaiton -> application

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fixed

using (var ms = new MemoryStream())
{
responseFeatures.Body.CopyTo(ms);
bodyBytes = ms.ToArray();
Copy link
Copy Markdown
Contributor

@damianh damianh Apr 25, 2017

Choose a reason for hiding this comment

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

Binary responses (images etc) can often be >80KB. Should we be worried about LOH fragmentation in the event of the function being re-used? If so, there's a netstandard version of this package https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream coming (or just copy the files) that might help.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting, curious when that package will support .NET Core

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

normj added a commit that referenced this pull request Apr 26, 2017
@normj
Copy link
Copy Markdown
Member

normj commented Apr 26, 2017

I pushed out version 0.10.0-preview1 of Amazon.Lambda.AspNetCoreServer which has this pull request. Thanks for the pull request.

@normj normj closed this Apr 26, 2017
@ebekker
Copy link
Copy Markdown
Author

ebekker commented Apr 26, 2017

Cool, I'll keep an eye out on progress in CloudFormation and SAM support for the binary mapping, and we can hopefully simplify the interface to this feature in the future.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Apr 26, 2017

Regarding the MS pooling, @damianh -- do want to take a crack at incorporating that in? Let me know if not, I think it could be a valuable addition.

@ebekker
Copy link
Copy Markdown
Author

ebekker commented Apr 26, 2017

@normj -- can I ask, how did you pull in the changes from this PR? I'm trying to pull in all upstream changes into my local fork so that I get all caught up, but git is complaining about merge conflicts with all of the commits associated with this PR, even though there are only about a dozen or so lines that differ.

@normj
Copy link
Copy Markdown
Member

normj commented Apr 26, 2017

I don't remember doing anything special. But admittedly I did the merge a while ago and then went on a 2 week vacation:)

I did have a quite a few merge conflicts I had to deal with because your pull request came in while I was converting all the projects to the new MSBuild format. Probably something with that messed up the commit history compared your fork. Sorry about that.

@damianh
Copy link
Copy Markdown
Contributor

damianh commented Apr 27, 2017

I can take a crack. However master branch is not compiling for me. Package restore fails:

C:\dev\aws\aws-lambda-dotnet\Libraries [master-upstream ≡]> dotnet restore
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.ConfigEvents\Amazon.Lambda.ConfigEvents.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.AspNetCoreServer\Amazon.Lambda.AspNetCoreServer.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.CognitoEvents\Amazon.Lambda.CognitoEvents.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.Core\Amazon.Lambda.Core.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.APIGatewayEvents\Amazon.Lambda.APIGatewayEvents.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.KinesisEvents\Amazon.Lambda.KinesisEvents.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.DynamoDBEvents\Amazon.Lambda.DynamoDBEvents.csproj...
  Restoring packages for C:\dev\aws\aws-lambda-dotnet\Libraries\src\Amazon.Lambda.LexEvents\Amazon.Lambda.LexEvents.csproj...
C:\Program Files\dotnet\sdk\1.0.3\NuGet.targets(97,5): error : Failed to retrieve information about 'AWSSDK.Kinesis' from remote source 'C:\dev\aws\aws-lambda-dotnet\Deployment\nuget-packages'. [C:\dev\aws\aws-lambda-dotnet\Libraries\Libraries.sln]
C:\dev\aws\aws-lambda-dotnet\Libraries [master-upstream ≡]>

And compilation dotnet build is resulting in a lot of errors. Any guidance?

@normj
Copy link
Copy Markdown
Member

normj commented Apr 27, 2017

Looks like you are missing the official NuGet feed if you can't find AWSSDK.Kinesis.

I am curious how this package improves performance. I'm always leary of adding dependencies so if I have the data showing it improves performance then it makes it easier to decide to take the pull request.

@damianh
Copy link
Copy Markdown
Contributor

damianh commented Apr 27, 2017

I am curious how this package improves performance. I'm always leary of adding dependencies so if I have the data showing it improves performance then it makes it easier to decide to take the pull request.

Fully agreed. Am dependency allergic also.

@damianh
Copy link
Copy Markdown
Contributor

damianh commented Apr 27, 2017

Looks like you are missing the official NuGet feed if you can't find AWSSDK.Kinesis.

C:\dev\aws\aws-lambda-dotnet\Libraries [master-upstream ≡]> nuget sources
Registered Sources:

  1.  build-artifacts [Enabled]
      C:\dev\aws\aws-lambda-dotnet\./Deployment/nuget-packages
  2.  nuget.org [Enabled]
      https://api.nuget.org/v3/index.json
   ... more ...

I have the official feed registered. Looks like it's choking on the the non-existent path defined in the nuget.config and I don't have the package already in the cache.

Anyway, will open a separate PR for this, if it turns out to be desirable.

// Figure out how we should treat the response content
var rcEncoding = DefaultResponseContentEncoding;
if (contentType != null
&& _responseContentEncodingForContentType.ContainsKey(contentType))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is problematic for content types that specify a charset. i.e. text/plain; charset=utf8.

Either specify all variations in _responseContentEncodingForContentType dictionary or perhaps ignore the charset altogether _responseContentEncodingForContentType.ContainsKey(contentType.Split(';')[0]) ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants