Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to upload GZip compressed data using System.Net.WebClient in C#

In my project I need to upload large JSON data from a desktop app. So, I need it to be compressed.

I searched everywhere but I did not find a complex solution to my problem. So, I put several snippets together. See the answer below. I hope you find it useful.

like image 907
petriq Avatar asked Jun 12 '14 09:06

petriq


1 Answers

I extended WebClient and shadow its UploadString method (all overloads):

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;

namespace DesktopApp
{
    public class ExtendedWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest w = base.GetWebRequest(uri);
            w.Timeout = 60 * 60 * 1000;
            return w;
        }

        private byte[] GZipBytes(string data)
        {
            //Transform string into byte[]  
            byte[] ret = null;

            using (System.IO.MemoryStream outputStream = new System.IO.MemoryStream())
            {
                using (GZipStream gzip = new GZipStream(outputStream, System.IO.Compression.CompressionMode.Compress))
                {
                    //write to gzipper
                    StreamWriter writer = new StreamWriter(gzip);
                    writer.Write(data);
                    writer.Flush();

                    //write to output stream
                    gzip.Flush();
                    gzip.Close();

                    ret = outputStream.ToArray();
                }
            }

            return ret;
        }

        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server address.</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(string address, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }

        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server URI.</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(Uri address, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }

        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server address.</param>
        /// <param name="method">HTTP method (e.g. POST, PUT, DELETE, GET).</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(string address, string method, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, method,bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }


        /// <summary>
        /// Overriden method using GZip compressed data upload.
        /// </summary>
        /// <param name="address">Remote server URI.</param>
        /// <param name="method">HTTP method (e.g. POST, PUT, DELETE, GET).</param>
        /// <param name="data">String data.</param>
        /// <returns>Server response string.</returns>
        public new string UploadString(Uri address, string method, string data)
        {
            string ret = null;
            byte[] bytes = GZipBytes(data);

            this.Headers.Add("content-encoding", "gzip");
            bytes = base.UploadData(address, method, bytes);
            ret = System.Text.Encoding.UTF8.GetString(bytes);

            return ret;
        }
    }
}

You can use ExtendedWebClient like:

        using (ExtendedWebClient client = new ExtendedWebClient())
        {
            try
            {
                //requestData object represents any data you need to send to server
                string data = JsonConvert.SerializeObject(
                                requestData,
                                new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() });

                client.Headers.Add("Content-Type", "application/json; charset=utf-8");
                client.Encoding = System.Text.Encoding.UTF8;
                string url = "http://yourdomain.com/api";
                string response = client.UploadString(url, data);

                //Deal with response as you need
            }
            catch (Exception ex)
            {
                Console.Error.Write(ex.Message);
            }
        }

On the server GZIP uploads are not normally supported, so I needed to add support for this.

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace WebServer
{
    public class CompressedRequestHandler : DelegatingHandler
    {
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            if (IsRequetCompressed(request))
            {
                request.Content = DecompressRequestContent(request);
            }

            return base.SendAsync(request, cancellationToken);
        }

        private bool IsRequetCompressed(HttpRequestMessage request)
        {
            if (request.Content.Headers.ContentEncoding != null &&
                request.Content.Headers.ContentEncoding.Contains("gzip"))
            {
                return true;
            }

            return false;
        }

        private HttpContent DecompressRequestContent(HttpRequestMessage request)
        {
            // Read in the input stream, then decompress in to the outputstream.
            // Doing this asynronously, but not really required at this point
            // since we end up waiting on it right after this.
            MemoryStream outputStream = new MemoryStream();

            Task task = request.Content.ReadAsStreamAsync().ContinueWith(t =>
                {
                    Stream inputStream = t.Result;

                    using (GZipStream gzipStream = new GZipStream(inputStream, CompressionMode.Decompress))
                    {
                        gzipStream.CopyTo(outputStream);
                    }

                    // Setting output streem position to begin is ESSENTIAL!
                    outputStream.Seek(0, SeekOrigin.Begin);
                });

            // Wait for inputstream and decompression to complete. Would be nice
            // to not block here and work async when ready instead, but I couldn't 
            // figure out how to do it in context of a DelegatingHandler.
            task.Wait();

            // Save the original content
            HttpContent origContent = request.Content;

            // Replace request content with the newly decompressed stream
            HttpContent newContent = new StreamContent(outputStream);

            // Copy all headers from original content in to new one
            // Change content-encoding and content-length
            foreach (var header in origContent.Headers)
            {
                // Change content-encoding header to default value
                if (header.Key.ToLowerInvariant() == "content-encoding")
                {
                    newContent.Headers.Add(header.Key, "identity");
                    continue;
                }

                // Change content-length header value to decompressed length
                if (header.Key.ToLowerInvariant() == "content-length")
                {
                    newContent.Headers.Add(header.Key, outputStream.Length.ToString());
                    continue;
                }

                // Copy other headers
                newContent.Headers.Add(header.Key, header.Value);
            }

            ////For testing purpose only!
            //Task task2 = newContent.ReadAsStringAsync().ContinueWith(x =>
            //{
            //    string strConent = x.Result;
            //});

            //task2.Wait();


            return newContent;
        }
    }
}

This handler must be registered in GlobalConfig file (under App_Start):

using System.Web.Http;
using Newtonsoft.Json.Serialization;
using System.Web.Http.Filters;

namespace WebServer
{
    public static class GlobalConfig
    {
        public static void CustomizeConfig(HttpConfiguration config)
        {
            //REGISTER CompressedRequestHandler
            config.MessageHandlers.Add(new CompressedRequestHandler());

            // Remove Xml formatters. This means when we visit an endpoint from a browser,
            // Instead of returning Xml, it will return Json.
            // More information from Dave Ward: http://jpapa.me/P4vdx6
            config.Formatters.Remove(config.Formatters.XmlFormatter);

            // Configure json camelCasing per the following post: http://jpapa.me/NqC2HH
            // Here we configure it to write JSON property names with camel casing
            // without changing our server-side data model:
            //var json = config.Formatters.JsonFormatter;
            //json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            //json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

            //remove standard JSON formatter
            config.Formatters.Remove(config.Formatters.JsonFormatter);

            //add JSONP formatter to support both JSON and JSONP
            var jsonp = new JsonpMediaTypeFormatter();
            jsonp.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            jsonp.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            config.Formatters.Add(jsonp);

            // Add model validation, globally
            config.Filters.Add(new ValidationActionFilter());

            //            config.Filters.Add(new AuthorizeAttribute());

            //
            //config.Filters.Add(new CustomSecurityAttribute());
            //config.Filters.Add(new XchangeSecurityAttribute());
            config.Filters.Add(new ExceptionHandlingAttribute());
        }
    }
}
like image 82
petriq Avatar answered Nov 11 '22 00:11

petriq