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

Added: GZip compression feature (#621) #623

Merged
merged 1 commit into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 9 additions & 5 deletions src/Algolia.Search.Test/BaseTest.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/*
* Copyright (c) 2018 Algolia
* http://www.algolia.com/
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Expand Down Expand Up @@ -45,7 +45,11 @@ public void Setup()
{
TestHelper.CheckEnvironmentVariable();
SearchClient = new SearchClient(TestHelper.ApplicationId1, TestHelper.AdminKey1);
SearchClient2 = new SearchClient(TestHelper.ApplicationId2, TestHelper.AdminKey2);
SearchConfig configClient2 = new SearchConfig(TestHelper.ApplicationId2, TestHelper.AdminKey2)
{
Compression = CompressionType.NONE
};
SearchClient2 = new SearchClient(configClient2);
McmClient = new SearchClient(TestHelper.McmApplicationId, TestHelper.McmAdminKey);
AnalyticsClient = new AnalyticsClient(TestHelper.ApplicationId1, TestHelper.AdminKey1);
}
Expand Down Expand Up @@ -83,4 +87,4 @@ protected void PreviousTestCleanUp()

SearchClient.MultipleBatch(operations);
}
}
}
6 changes: 6 additions & 0 deletions src/Algolia.Search/Clients/AlgoliaConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* THE SOFTWARE.
*/

using Algolia.Search.Models.Enums;
using Algolia.Search.Serializer;
using Algolia.Search.Transport;
using System.Collections.Generic;
Expand Down Expand Up @@ -94,6 +95,11 @@ public AlgoliaConfig(string applicationId, string apiKey)
/// </summary>
public int? WriteTimeout { get; set; }

/// <summary>
/// Compression for outgoing http requests <see cref="CompressionType"/>
/// </summary>
public virtual CompressionType Compression { get; protected set; }

/// <summary>
/// Configurations hosts
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion src/Algolia.Search/Clients/AnalyticsConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public AnalyticsConfig(string applicationId, string apiKey) : base(applicationId
Accept = CallType.Read | CallType.Write
}
};

Compression = CompressionType.NONE;
}
}
}
}
4 changes: 3 additions & 1 deletion src/Algolia.Search/Clients/InsightsConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public InsightsConfig(string applicationId, string apiKey, string region = "us")
Accept = CallType.Read | CallType.Write
}
};

Compression = CompressionType.NONE;
}
}
}
}
5 changes: 5 additions & 0 deletions src/Algolia.Search/Clients/SearchConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public SearchConfig(string applicationId, string apiKey) : base(applicationId, a
hosts.AddRange(commonHosts);

DefaultHosts = hosts;

Compression = CompressionType.NONE;
}

/// <inheritdoc />
public new CompressionType Compression { get; set; }
}
}
5 changes: 5 additions & 0 deletions src/Algolia.Search/Http/AlgoliaHttpRequester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, int tot
Content = request.Body != null ? new StreamContent(request.Body) : null
};

if (request.Body != null)
{
httpRequestMessage.Content.Headers.Fill(request);
}

httpRequestMessage.Headers.Fill(request.Headers);
httpRequestMessage.SetTimeout(TimeSpan.FromSeconds(totalTimeout));

Expand Down
23 changes: 22 additions & 1 deletion src/Algolia.Search/Http/HttpRequestHeadersExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* THE SOFTWARE.
*/

using Algolia.Search.Models.Common;
using System.Collections.Generic;
using System.Net.Http.Headers;

Expand All @@ -43,5 +44,25 @@ internal static HttpRequestHeaders Fill(this HttpRequestHeaders headers, Diction

return headers;
}

/// <summary>
/// Extension method to easily fill HttpContentHeaders with the Request object
/// </summary>
/// <param name="headers"></param>
/// <param name="request"></param>
internal static HttpContentHeaders Fill(this HttpContentHeaders headers, Request request)
{
if (request.Body != null)
{
headers.Add(Defaults.ContentType, Defaults.ApplicationJson);

if (request.CanCompress)
{
headers.ContentEncoding.Add(Defaults.GzipEncoding);
}
}

return headers;
}
}
}
}
27 changes: 26 additions & 1 deletion src/Algolia.Search/Models/Common/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* THE SOFTWARE.
*/

using Algolia.Search.Models.Enums;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -52,5 +53,29 @@ public class Request
/// Body of the request
/// </summary>
public Stream Body { get; set; }

/// <summary>
/// Compression type of the request <see cref="CompressionType"/>
/// </summary>
public CompressionType Compression { get; set; }

/// <summary>
/// Tells if the request can be compressed or not
/// </summary>
public bool CanCompress
{
get
{
if (Method == null)
{
return false;
}

bool isMethodValid = Method.Equals(HttpMethod.Post) || Method.Equals(HttpMethod.Put);
bool isCompressionEnabled = Compression.Equals(CompressionType.GZIP);

return isMethodValid && isCompressionEnabled;
}
}
}
}
}
41 changes: 41 additions & 0 deletions src/Algolia.Search/Models/Enums/CompressionType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2018 Algolia
* http://www.algolia.com/
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

namespace Algolia.Search.Models.Enums
{
/// <summary>
/// Compression type for outgoing HTTP requests
/// </summary>
public enum CompressionType
{
/// <summary>
/// No compression
/// </summary>
NONE,

/// <summary>
/// GZip Compression. Only supported by Search API.
/// </summary>
GZIP
}
}
33 changes: 27 additions & 6 deletions src/Algolia.Search/Serializer/SerializerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

using Newtonsoft.Json;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading.Tasks;

Expand All @@ -34,16 +35,36 @@ namespace Algolia.Search.Serializer
/// </summary>
internal static class SerializerHelper
{
private static readonly UTF8Encoding _encoding = new UTF8Encoding(false);
private static readonly UTF8Encoding DefaultEncoding = new UTF8Encoding(false);
private static readonly int DefaultBufferSize = 1024;
// Buffer sized as recommended by Bradley Grainger, http://faithlife.codes/blog/2012/06/always-wrap-gzipstream-with-bufferedstream/
private static readonly int GZipBufferSize = 8192;

public static void Serialize<T>(T data, Stream stream, JsonSerializerSettings settings)
public static void Serialize<T>(T data, Stream stream, JsonSerializerSettings settings, bool gzipCompress)
{
using (var sw = new StreamWriter(stream, _encoding, 1024, true))
using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None })
if (gzipCompress)
{
using (var gzipStream = new GZipStream(stream, CompressionMode.Compress, true))
using (var sw = new StreamWriter(gzipStream, DefaultEncoding, GZipBufferSize))
using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None })
{
JsonSerialize(jtw);
}
}
else
{
using (var sw = new StreamWriter(stream, DefaultEncoding, DefaultBufferSize, true))
using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None })
{
JsonSerialize(jtw);
}
}

void JsonSerialize(JsonTextWriter writer)
{
JsonSerializer serializer = JsonSerializer.Create(settings);
serializer.Serialize(jtw, data);
jtw.Flush();
serializer.Serialize(writer, data);
writer.Flush();
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/Algolia.Search/Transport/HttpTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ public async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod method
var request = new Request
{
Method = method,
Body = CreateRequestContent(data),
Headers = GenerateHeaders(requestOptions?.Headers)
Headers = GenerateHeaders(requestOptions?.Headers),
Compression = _algoliaConfig.Compression
};

request.Body = CreateRequestContent(data, request.CanCompress);

foreach (var host in _retryStrategy.GetTryableHost(callType))
{
request.Uri = BuildUri(host.Url, uri, requestOptions?.QueryParameters);
Expand Down Expand Up @@ -138,14 +140,17 @@ public async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod method
/// Generate stream for serializing objects
/// </summary>
/// <param name="data">Data to send</param>
/// <param name="compress">Whether the stream should be compressed or not</param>
/// <typeparam name="T">Type of the data to send/retrieve</typeparam>
/// <returns></returns>
private MemoryStream CreateRequestContent<T>(T data)
private MemoryStream CreateRequestContent<T>(T data, bool compress)
{
if (data != null)
{
MemoryStream ms = new MemoryStream();
SerializerHelper.Serialize(data, ms, JsonConfig.AlgoliaJsonSerializerSettings);

SerializerHelper.Serialize(data, ms, JsonConfig.AlgoliaJsonSerializerSettings, compress);

ms.Seek(0, SeekOrigin.Begin);
return ms;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Algolia.Search/Utils/Defaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,7 @@ internal class Defaults
public const string UserAgentHeader = "User-Agent";
public const string Connection = "Connection";
public const string KeepAlive = "keep-alive";
public const string ContentType = "Content-Type";
public const string ApplicationJson = "application/json; charset=utf-8";
public const string GzipEncoding = "gzip";
}