从HTTPClient响应中解压GZip流

127

我正在尝试连接到一个返回GZip编码JSON的API,从一个WCF服务(WCF服务到WCF服务)。我使用 HTTPClient 连接到API,并已能够将JSON对象返回为字符串。但是,我需要能够将这个返回的数据存储在数据库中,因此我想最好的方法是返回并将JSON对象存储在数组或字节等类型中。

我遇到的具体问题是解压GZip编码,并且一直在尝试很多不同的例子,但仍然无法做到。下面的代码是我建立连接并获取响应的方式,这是从API返回字符串的代码。

public string getData(string foo)
{
    string url = "";
    HttpClient client = new HttpClient();
    HttpResponseMessage response;
    string responseJsonContent;
    try
    {
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        response = client.GetAsync(url + foo).Result;
        responseJsonContent = response.Content.ReadAsStringAsync().Result;
        return responseJsonContent;
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show(ex.Message);
        return "";
    }
}

我一直在参考一些不同的示例,比如StackExchange APIMSDN和一些stackoverflow上的示例,但是我都不能让它们为我工作。

什么是最好的方法来实现这个,我是否走对了路?

谢谢大家。


最好的方法是将JSON对象返回并存储在数组或字节中。请注意,字符串是字节数组。 - user3285954
4个回答

315

只需像这样实例化 HttpClient :

HttpClientHandler handler = new HttpClientHandler()
{
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};

using (var client = new HttpClient(handler)) //see update below
{
    // your code
}

2020年6月19日更新:不建议在“using”块中使用httpclient,因为这可能会导致端口耗尽。

private static HttpClient client = null;
    
ContructorMethod()
{
   if(client == null)
   {
        HttpClientHandler handler = new HttpClientHandler()
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        };        
        client = new HttpClient(handler);
   }
// your code            
 }
如果使用 .Net Core 2.1+,请考虑使用 IHttpClientFactory 并像下面这样在启动代码中注入。
 var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
            TimeSpan.FromSeconds(60));

 services.AddHttpClient<XApiClient>().ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        }).AddPolicyHandler(request => timeout);

1
@FoxDeploy,使用此解决方案时,代码无需更改即可获取内容。请参见此处的参考:https://dev59.com/UF8d5IYBdhLWcg3woDcZ - DIG
1
尽管这是一篇旧帖子,但这个答案刚好解决了我的.netcore问题。从1.1升级到2.0后,客户端似乎自动进行了解压缩,因此我不得不在2.0中添加这段代码才能使其正常工作...谢谢! - Sebastian Castaldi
3
只是跟随 @SebastianCastaldi 的意见,但是 .net core 1.1 已经正确设置了自动解压缩,但在 .net core 2.0 中它被设置为 NONE。这让我花费了太长时间去弄明白... - KallDrexx
7
注意:HttpClient 不应该在 using 语句中使用。 - imba-tjd
1
np!我曾经吃过亏,当我们集群中一个主机上的每个虚拟机都因端口问题停止工作时。花了几周时间才发现是一个应用程序没有正确使用HttpClient。 - jugg1es
显示剩余7条评论

5

我使用了下面链接中的代码来解压GZip流,然后使用解压缩后的字节数组获取所需的JSON对象。希望能够帮到一些人。

var readTask = result.Content.ReadAsByteArrayAsync().Result;
var decompressedData = Decompress(readTask);
string jsonString = System.Text.Encoding.UTF8.GetString(decompressedData, 0, decompressedData.Length);
ResponseObjectClass responseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<ResponseObjectClass>(jsonString);

https://www.dotnetperls.com/decompress

static byte[] Decompress(byte[] gzip)
{
    using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
    {
        const int size = 4096;
        byte[] buffer = new byte[size];
        using (MemoryStream memory = new MemoryStream())
        {
            int count = 0;
            do
            {
                count = stream.Read(buffer, 0, size);
                if (count > 0)
                {
                    memory.Write(buffer, 0, count);
                }
            }
            while (count > 0);
            return memory.ToArray();
        }
    }
}

0

好的,我最终解决了我的问题。如果有更好的方法,请告诉我 :-)

        public DataSet getData(string strFoo)
    {
        string url = "foo";

        HttpClient client = new HttpClient();
        HttpResponseMessage response;   
        DataSet dsTable = new DataSet();
        try
        {
               //Gets the headers that should be sent with each request
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
              //Returned JSON
            response = client.GetAsync(url).Result;
              //converts JSON to string
            string responseJSONContent = response.Content.ReadAsStringAsync().Result;
              //deserializes string to list
            var jsonList = DeSerializeJsonString(responseJSONContent);
              //converts list to dataset. Bad name I know.
            dsTable = Foo_ConnectAPI.ExtentsionHelpers.ToDataSet<RootObject>(jsonList);
              //Returns the dataset                
            return dsTable;
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message);
            return null;
        }
    }

       //deserializes the string to a list. Utilizes JSON.net. RootObject is a class that contains the get and set for the JSON elements

    public List<RootObject> DeSerializeJsonString(string jsonString)
    {
          //Initialized the List
        List<RootObject> list = new List<RootObject>();
          //json.net deserializes string
        list = (List<RootObject>)JsonConvert.DeserializeObject<List<RootObject>>(jsonString);

        return list;
    }

根对象包含获取JSON值的get set。
public class RootObject
{  
      //These string will be set to the elements within the JSON. Each one is directly mapped to the JSON elements.
      //This only takes into account a JSON that doesn't contain nested arrays
    public string EntityID { get; set; }

    public string Address1 { get; set; }

    public string Address2 { get; set; }

    public string Address3 { get; set; }

}

最简单的创建上述类的方法是使用 json2charp,它将格式化它并提供正确的数据类型。
以下内容来自Stackoverflow的另一个答案,同样没有考虑嵌套的JSON。
    internal static class ExtentsionHelpers
{
    public static DataSet ToDataSet<T>(this List<RootObject> list)
    {
        try
        {
            Type elementType = typeof(RootObject);
            DataSet ds = new DataSet();
            DataTable t = new DataTable();
            ds.Tables.Add(t);

            try
            {
                //add a column to table for each public property on T
                foreach (var propInfo in elementType.GetProperties())
                {
                    try
                    {
                        Type ColType = Nullable.GetUnderlyingType(propInfo.PropertyType) ?? propInfo.PropertyType;

                            t.Columns.Add(propInfo.Name, ColType);

                    }
                    catch (Exception ex)
                    {
                        System.Windows.Forms.MessageBox.Show(ex.Message);
                    }

                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
            }

            try
            {
                //go through each property on T and add each value to the table
                foreach (RootObject item in list)
                {
                    DataRow row = t.NewRow();

                    foreach (var propInfo in elementType.GetProperties())
                    {
                        row[propInfo.Name] = propInfo.GetValue(item, null) ?? DBNull.Value;
                    }

                    t.Rows.Add(row);
                }
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
            }

            insert.insertCategories(t);
            return ds.
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message);

            return null;
        }
    }
};

最后,为了将上述数据集插入到列映射到JSON的表中,我使用了SQL批量复制和以下类

public class insert
{ 
    public static string insertCategories(DataTable table)
    {     
        SqlConnection objConnection = new SqlConnection();
          //As specified in the App.config/web.config file
        objConnection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["foo"].ToString();

        try
        {                                 
            objConnection.Open();
            var bulkCopy = new SqlBulkCopy(objConnection.ConnectionString);

            bulkCopy.DestinationTableName = "dbo.foo";
            bulkCopy.BulkCopyTimeout = 600;
            bulkCopy.WriteToServer(table);

            return "";
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message);
            return "";
        }
        finally
        {
            objConnection.Close();
        }         
    }
};

因此,以上内容可以将来自WebAPI的JSON插入到数据库中。这是我设法实现的一件事情。但是我并不指望它是完美的。如果您有任何改进意见,请相应地进行更新。


2
你应该在每个using()语句中创建自己的HttpClientHttpResponse,以确保底层流的适当及时释放和关闭。 - Ian Mercer

0

早晚你的代码可能会出问题,如果你的服务器使用另一种压缩方案,比如“Brotli”,并且 content-type 为 br。

我制作了一个视频,使用 clientHandler 处理 httpClient 中的解压缩:

compression algos handled by httpClientHandler


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接