NancyFx / Nancy

Lightweight, low-ceremony, framework for building HTTP based services on .Net and Mono
http://nancyfx.org
MIT License
7.15k stars 1.47k forks source link

Wrong response body when using compression and multiple NancyHosts #2944

Closed frankiDotNet closed 5 years ago

frankiDotNet commented 5 years ago

I am reporting a bug for this version:

Nancy: 1.4.5 Nancy.Hosting.Self: 1.4.1

Scenario 1, single NancyHost with two Uris:

         string baseAddress1 = "http://localhost:51234/api/";
         string baseAddress2= "http://localhost:51235/api/";
         NancyHost host1;
         NancyHost host2;
         try
         {
            var hostConfig1 = new HostConfiguration()
            {
               UrlReservations = new UrlReservations { CreateAutomatically = true },
               EnableClientCertificates = true,
               RewriteLocalhost = true
            };
            var uris = new[] {
               new Uri(baseAddress1),
               new Uri(baseAddress2),
            };
            host1 = new NancyHost(hostConfig1, uris);
            host1.Start();
         }
         catch (Exception ex)
         {
            Console.WriteLine($"Error while opening host...{ex}");
            throw;
         }
         Console.WriteLine("Enter key to exit..");
         Console.ReadLine();
         return;
   public class NancyCompression: DefaultNancyBootstrapper
   {
      protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
      {
         base.ApplicationStartup(container, pipelines);

         pipelines.AfterRequest += CheckForCompression;
      }

      private static void CheckForCompression(NancyContext context)
      {
         if (!RequestIsGzipCompatible(context.Request))
         {
            return;
         }
         CompressResponse(context.Response);
      }

      private static bool RequestIsGzipCompatible(Request request)
      {
         return request.Headers.AcceptEncoding.Any(x => x.Contains("gzip"));
      }

      private static void CompressResponse(Response response)
      {
         response.Headers["Content-Encoding"] = "gzip";

         var contents = response.Contents;
         response.Contents = responseStream =>
         {
            using (var compression = new GZipStream(responseStream, CompressionMode.Compress))
            {
               contents(compression);
            }
         };
      }
   }

NancyModule:

   public class TesrModule: NancyModule
   {
      public TesrModule()
      {
         Get["/test"] = parameters =>
         {
            var testObj = new {Id = 2, Name = "Test"};
            return Response.AsJson(testObj);
         };
      }
   }

Response Body in browser:

{"id":2,"name":"Test"}

Request Headers

GET /api/test HTTP/1.1 Host: localhost:51235 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7

Response Headers:

HTTP/1.1 200 OK Transfer-Encoding: chunked Content-Type: application/json; charset=utf-8 Content-Encoding: gzip Server: Microsoft-HTTPAPI/2.0 Date: Mon, 26 Nov 2018 07:42:02 GMT

Scenario 2 two NancyHosts with unique Uri:

  ```

string baseAddress1 = "http://localhost:51234/api/"; string baseAddress2= "http://localhost:51235/api/"; NancyHost host1; NancyHost host2; try { var hostConfig1 = new HostConfiguration() { UrlReservations = new UrlReservations { CreateAutomatically = true }, EnableClientCertificates = true, RewriteLocalhost = true }; host1 = new NancyHost(hostConfig1, new Uri(baseAddress1)); host1.Start();

        host2 = new NancyHost(hostConfig1, new Uri(baseAddress2));
        host2.Start();
     }
     catch (Exception ex)
     {
        Console.WriteLine($"Error while opening host...{ex}");
        throw;
     }
     Console.WriteLine("Enter key to exit..");
     Console.ReadLine();
     return;


Bootstrapper and Module is the same for both.

**Response Body in browser:**

��������V�LQ�2�Q�K�MU�R
I-.Q����WO���

**Request Headers**

GET /api/test HTTP/1.1
Host: localhost:51235
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7

**Response Headers:**

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 26 Nov 2018 07:45:32 GMT

Scenario 1 works like expected, but scenario 2 does not work. 
I am not sure what the problem is...
frankiDotNet commented 5 years ago

Ok after trying with SharpZibLib and round about 1000 other ways of compressing the result.. :-D I logged the string before each response and I have seen that the after request delegate is getting called twice, I thought it is some kind of internal round trip, but then I logged the resulting stream as string and have seen that the second time the response stream has the compressed string already in, so the string was compressed twice. After searching through google I found an example where for each NancyHost there is created a new Bootstraper. So the following worked for me:

      try
      {
       var hostConfig1 = new HostConfiguration()
       {
            UrlReservations = new UrlReservations { CreateAutomatically = true },
            EnableClientCertificates = true,
            RewriteLocalhost = true
       };
        host1 = new NancyHost(new NancyCompression(), hostConfig1, new Uri(baseAddress1));
        host1.Start();

        host2 = new NancyHost(new NancyCompression(), hostConfig1, new Uri(baseAddress2));
        host2.Start();
     }
     catch (Exception ex)
     {
        Console.WriteLine($"Error while opening host...{ex}");
        throw;
     }
     Console.WriteLine("Enter key to exit..");
     Console.ReadLine();
     return;

I have not found such example in the docs, so adding the info that each NancyHost needs an own Bootstrapper would be helpfull..

cloudhunter89 commented 5 years ago

Out of curiosity, why create two hosts in one executable instead of making the port configurable and starting multiple instances? I suspect this was not an expected use case of the default Nancy host constructor.

On Mon, Nov 26, 2018, 06:20 Franki1986 <notifications@github.com wrote:

Ok after trying with SharpZibLib and round about 1000 other ways of compressing the result.. :-D I logged the string before each response and I have seen that the after request delegate is getting called twice, I thought it is some kind of internal round trip, but then I logged the resulting stream as string and have seen that the second time the response stream has the compressed string already in, so the string was compressed twice. After searching through google I found an example where for each NancyHost there is created a new Bootstraper. So the following worked for me:

  try
  {
   var hostConfig1 = new HostConfiguration()
   {
        UrlReservations = new UrlReservations { CreateAutomatically = true },
        EnableClientCertificates = true,
        RewriteLocalhost = true
   };
    host1 = new NancyHost(new NancyCompression(), hostConfig1, new Uri(baseAddress1));
    host1.Start();

    host2 = new NancyHost(new NancyCompression(), hostConfig1, new Uri(baseAddress2));
    host2.Start();
 }
 catch (Exception ex)
 {
    Console.WriteLine($"Error while opening host...{ex}");
    throw;
 }
 Console.WriteLine("Enter key to exit..");
 Console.ReadLine();
 return;

I have not found such example in the docs, so adding the info that each NancyHost needs an own Bootstrapper would be helpfull..

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/NancyFx/Nancy/issues/2944#issuecomment-441636180, or mute the thread https://github.com/notifications/unsubscribe-auth/AEEilkKmhzHN41DlVorAzhmbGwU7InfAks5uy-qhgaJpZM4YyfaK .

frankiDotNet commented 5 years ago

Do you mean create two instances by creating the host with an Uri array?

phillip-haydon commented 5 years ago

You can’t have 2 hosts on the 1 instance. Need 2 separate instances.

frankiDotNet commented 5 years ago

I am not sure if the way I am doing is wrong. If something is not right, could you please show it in an example what would be the right way?

cloudhunter89 commented 5 years ago

Sorry, I realize now that my wording was unclear. I meant make the port configurable and run the executable multiple times with different ports. Though, I don't understand. Why do you want to use two ports on one machine for the same set of HTTP endpoints?

On Mon, Nov 26, 2018 at 7:40 AM Franki1986 notifications@github.com wrote:

I am not sure if the way I am doing is wrong. If something is not right, could you please show it in an example what would be the right way?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/NancyFx/Nancy/issues/2944#issuecomment-441661054, or mute the thread https://github.com/notifications/unsubscribe-auth/AEEilghaILwe1mMqLV-1ReuURsxqD1pAks5uy_1DgaJpZM4YyfaK .

-- Jonathon Koyle

frankiDotNet commented 5 years ago

I have two applications that communicate with the webservices. And the webservices can be configured in diferent ways. And probably you can define to listen on any port only in the NancyHost instance. So if one should be accessed only local the other could be accessed from anywhere