将Angular的请求压缩到Web API

时间:2015-12-13 13:54:12

标签: c# angularjs asp.net-web-api

我正在尝试通过将来自角度客户端的请求压缩到AspNet Web API来优化带宽使用。有没有办法实现这个目标?

1 个答案:

答案 0 :(得分:12)

一种可能性是使用行业标准算法来压缩gzip等数据。它们为原始字符串提供了非常好的压缩,如果要将大对象发送到服务器,那么通过减少请求的大小,您肯定可以获得性能。更不用说当您的应用在带宽有限的移动设备上运行时获得的好处。

但足够的喋喋不休,让我们来练习。这里最大的挑战是在javascript中生成有效的gzip请求。一种可能性是阅读此格式的规范并滚动您自己的实现或使用一些现有的库。我觉得特别有趣的是pako

通过简单地发出以下命令,使用bower在您的应用程序中安装是微不足道的:

bower install pako

现在让我们看一下从客户角度看样本请求的样子。假设您想将以下JSON发送到服务器(作为POST或PUT谓词):

{ my: 'super', puper: [456, 567], awesome: 'pako' }

您可以像使用现代浏览器中提供的普通XMLHttpRequest对象一样实现这一目标(如果您对Angular特定解决方案感兴趣,请阅读以下内容):

<script src="bower_components/pako/dist/pako.min.js"></script>
<script>
    var xhr = new XMLHttpRequest();
    xhr.open('POST', '/api/myresource', true);

    // Indicate to the serve that you will be sending data in JSON format
    xhr.setRequestHeader('Content-Type', 'application/json');
    // Indicate to the server that your data is encoded using the gzip format
    xhr.setRequestHeader('Content-Encoding', 'gzip');

    xhr.onreadystatechange = function (e) {
        if (this.readyState == 4 && this.status == 200) {
            alert('We have just successfully sent a gzip encoded payload to the server');
        }
    };

    var data = { my: 'super', puper: [456, 567], awesome: 'pako' };
    var binaryString = pako.gzip(JSON.stringify(data));
    xhr.send(binaryString);
</script>

既然您询问了Angular请求,那么让我们使用本机$http对象来角化这个示例AJAX请求:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
</head>
<body ng-app="myapp">
    <div ng-controller="HomeController"></div>
    <script src="bower_components/pako/dist/pako.min.js"></script>
    <script src="bower_components/angular/angular.min.js"></script>
    <script>
        angular.module('myapp', []).controller('HomeController', ['$http', function ($http) {
            var data = { my: 'super', puper: [456, 567], awesome: 'pako' };
            var binaryString = pako.gzip(JSON.stringify(data));
            var req = {
                method: 'POST',
                url: '/api/myresource',
                headers: {
                    'Content-Type': 'application/json',
                    'Content-Encoding': 'gzip'
                },
                data: binaryString,
                transformRequest: []
            }

            $http(req).then(function (result) {
                alert('We have just successfully sent a gzip encoded payload to the server');
            }, function () {
                alert('OOPS, something went wrong, checkout the Network tab in your browser for more details');
            });
        }]);
    </script>
</body>
</html>

好的,基本上我们现在已经覆盖了使用AJAX请求的客户端发送部分,并指定了正确的Content-Encoding请求头。

现在让我们来处理服务器端部分。假设您使用IIS中托管的Web API。

所以基本上你的ASP.NET应用程序中会有一个Startup类来引导你的Web API:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = GlobalConfiguration.Configuration;
        config.MapHttpAttributeRoutes();
        app.UseWebApi(config);
        config.EnsureInitialized();
    }
}

然后显然你有一个视图模型来将你的有效负载映射到:

public class MyViewModel
{
    public string My { get; set; }
    public int[] Puper { get; set; }
    public string Awesome { get; set; }
}

和一个Web API控制器,它将用于AJAX请求的服务器端处理程序:

public class TestController : ApiController
{
    [HttpPost]
    [Route("api/myresource")]
    public HttpResponseMessage Post(MyViewModel viewModel)
    {
        // We will simply echo out the received request object to the response
        var response = Request.CreateResponse(HttpStatusCode.OK, viewModel);
        return response;
    }
}

到目前为止一切顺利。遗憾的是,Web API不支持开箱即用的gzip请求编码。但是因为这是一个非常可扩展的框架,所以你要做的就是编写一个自定义委托处理程序,它将知道如何解压缩来自客户端的请求。

让我们从编写自定义HttpContent开始:

public class DecompressedHttpContent: HttpContent
{
    private readonly HttpContent _content;
    public DecompressedHttpContent(HttpContent content)
    {
        _content = content;
        foreach (var header in _content.Headers)
        {
            Headers.TryAddWithoutValidation(header.Key, header.Value);
        }
    }

    protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        using (var originalStream = await _content.ReadAsStreamAsync())
        using (var gzipStream = new GZipStream(originalStream, CompressionMode.Decompress))
        {
            await gzipStream.CopyToAsync(stream);
        }
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1;
        return false;
    }
}

然后是我们的委托处理程序:

public class GzipDecompressionHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken
    )
    {
        var isCompressedPayload = request.Content.Headers.ContentEncoding.Any(x => string.Equals(x, "gzip", StringComparison.InvariantCultureIgnoreCase));
        if (!isCompressedPayload)
        {
            return await base.SendAsync(request, cancellationToken);
        }

        request.Content = new DecompressedHttpContent(request.Content);
        return await base.SendAsync(request, cancellationToken);
    }
}

现在剩下的就是在我们的Startup类中注册这个自定义处理程序:

config.MessageHandlers.Add(new GzipDecompressionHandler());

这就是它。现在,当从客户端AJAX请求调用TestController.Post操作时,输入主体将包含正确的标头,我们的委托处理程序将负责对其进行解码,以便在调用Post操作时,您将获得已经反序列化的预期视图模型

现在回顾一下你应该知道,对于这个例子中显示的小请求你可能不会通过使用gzip获得太多 - 你甚至可以让事情变得更糟,因为魔法gzip数字会增加到有效载荷。但是对于更大的请求,这种方法肯定会提高您的请求大小,我强烈建议您使用gzip。

这是这项努力的结果:

enter image description here