jsreport / jsreport-dotnet-client

jsreport remote client for c#
MIT License
3 stars 4 forks source link

Problem using https address #2

Open xukangmin opened 3 years ago

xukangmin commented 3 years ago

I'm using https address for ReportingService. I'm hosting a http jsreport server on this address with nginx/certbot (using your suggested config)

The address works perfectly fine with PostMan. I send post request to https://xxx.xxx.xx/api/report/, it gives me 200 with PDF binary.

But when I use client in .NET.

var rs = new ReportingService("https://xxx.xxx.xx/, jsReportUser, jsReportPwd);

inHouseReport = rs.RenderByNameAsync(jsReportTemplateName, quotesData).GetAwaiter().GetResult();

It give me this error:

{StatusCode: 502, ReasonPhrase: 'Bad Gateway', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Server: nginx/1.18.0
  Server: (Ubuntu)
  Date: Fri, 20 Nov 2020 21:36:28 GMT
  Connection: keep-alive
  Content-Type: text/html
  Content-Length: 166
}}

I then try to use raw https call using .NET.

                var client = new HttpClient()
                {
                    BaseAddress = new Uri(jsReportURL)
                };

                var json = JsonSerializer.Serialize(rerpotPara);
                var data = new StringContent(json, Encoding.UTF8, "application/json");

                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(
                        Encoding.UTF8.GetBytes(String.Format("{0}:{1}", jsReportUser, jsReportPwd))));

                responseReport = client.PostAsync("api/report", data).GetAwaiter().GetResult();

This is the response:

{StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Server: nginx/1.18.0
  Server: (Ubuntu)
  Date: Fri, 20 Nov 2020 21:39:05 GMT
  Transfer-Encoding: chunked
  Connection: keep-alive
  X-Powered-By: Express
  Access-Control-Allow-Origin: *
  Access-Control-Expose-Headers: *
  Set-Cookie: render-complete=true; Path=/
  X-XSS-Protection: 0
  Content-Type: application/pdf
  Content-Disposition: inline; filename=inhousereport-main.pdf
}}

It seems to be httpClient TLS version issue. Check here: https://stackoverflow.com/questions/22251689/make-https-call-using-httpclient

in jsreport-dotnet-client, it's using Assembly netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51

I'm using Assembly System.Net.Http, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

Old version of httpClient seems not supporting TLS 1.1+.

My server is using 1.2+ only.

Currently I have to use custom method, hope you can fix it soon. A simple dependency update should solve the problem.

Screenshot 2020-11-20 154225

pofider commented 3 years ago

Are you sure the problem is in the System.Net.Http version? See the deps in the https://www.nuget.org/packages/jsreport.Client/ image

A .net core app in the VS

image

The 502 error can also mean that the nginx accepted your request, but the proxied node.js server failed to process it. Please check the jsreport logs, if it receives the request... Doesn't it fail during the processing?

xukangmin commented 3 years ago

Here is the jsreport log, it actually received the request and process the PDF, somehow the response cannot get back to .NET app. In .net, it still shows error 502.

2020-11-21T22:12:23.973Z - debug: API logging in user kangmin
2020-11-21T22:12:23.973Z - info: Starting rendering request 9 (user: kangmin)
2020-11-21T22:12:23.974Z - info: Rendering template { name: inhousereport-main, recipe: chrome-pdf, engine: handlebars, preview: false }
2020-11-21T22:12:23.974Z - debug: Inline data specified.
2020-11-21T22:12:23.975Z - debug: Resources not defined for this template.
2020-11-21T22:12:23.976Z - debug: Replaced assets ["style.css"]
2020-11-21T22:12:23.976Z - debug: Base url not specified, skipping its injection.
2020-11-21T22:12:23.976Z - debug: Rendering engine handlebars using http-server strategy
2020-11-21T22:12:23.992Z - debug: Taking compiled template from engine cache
2020-11-21T22:12:23.993Z - debug: Executing recipe chrome-pdf
2020-11-21T22:12:24.086Z - debug: Converting with chrome HeadlessChrome/79.0.3945.0 using dedicated-process strategy
2020-11-21T22:12:24.197Z - debug: Page request: GET (document) file:///tmp/jsreport/autocleanup/92181f52-1642-4f1b-98d4-eccb507f041f-chrome-pdf.html
2020-11-21T22:12:24.205Z - debug: Page request finished: GET (document) file:///tmp/jsreport/autocleanup/92181f52-1642-4f1b-98d4-eccb507f041f-chrome-pdf.html
2020-11-21T22:12:24.208Z - debug: Page request: GET (stylesheet) https://cdnjs.cloudflare.com/ajax/libs/metro/4.1.5/css/metro.min.css
2020-11-21T22:12:24.208Z - debug: Page request: GET (script) https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js
2020-11-21T22:12:24.267Z - debug: Page request finished: GET (stylesheet) 200 https://cdnjs.cloudflare.com/ajax/libs/metro/4.1.5/css/metro.min.css
2020-11-21T22:12:24.268Z - debug: Page request finished: GET (script) 200 https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js
2020-11-21T22:12:24.353Z - debug: Running chrome with params {"printBackground":true,"landscape":true,"marginBottom":"1cm","marginRight":"0cm","marginTop":"0cm","marginLeft":"0cm","margin":{"top":"0cm","right":"0cm","bottom":"1cm","left":"0cm"}}
2020-11-21T22:12:24.905Z - info: pdf-utils is starting pdf processing
2020-11-21T22:12:25.074Z - debug: pdf-utils detected 1 pdf operation(s) to process
2020-11-21T22:12:25.074Z - debug: pdf-utils running pdf operation merge
2020-11-21T22:12:25.074Z - info: Starting rendering request 10 (user: kangmin)
2020-11-21T22:12:25.075Z - info: Rendering template { name: header-footer, recipe: chrome-pdf, engine: handlebars, preview: false }
2020-11-21T22:12:25.075Z - debug: Inline data specified.
2020-11-21T22:12:25.076Z - debug: Resources not defined for this template.
2020-11-21T22:12:25.077Z - debug: Replaced assets ["style.css"]
2020-11-21T22:12:25.077Z - debug: Base url not specified, skipping its injection.
2020-11-21T22:12:25.078Z - debug: Rendering engine handlebars using http-server strategy
2020-11-21T22:12:25.089Z - debug: Taking compiled template from engine cache
2020-11-21T22:12:25.090Z - debug: Executing recipe chrome-pdf
2020-11-21T22:12:25.190Z - debug: Converting with chrome HeadlessChrome/79.0.3945.0 using dedicated-process strategy
2020-11-21T22:12:25.285Z - debug: Page request: GET (document) file:///tmp/jsreport/autocleanup/0b010a22-a804-4145-a8b7-d429ab7d803f-chrome-pdf.html
2020-11-21T22:12:25.295Z - debug: Page request finished: GET (document) file:///tmp/jsreport/autocleanup/0b010a22-a804-4145-a8b7-d429ab7d803f-chrome-pdf.html
2020-11-21T22:12:25.297Z - debug: Page request: GET (stylesheet) https://cdnjs.cloudflare.com/ajax/libs/metro/4.1.5/css/metro.min.css
2020-11-21T22:12:25.353Z - debug: Page request finished: GET (stylesheet) 200 https://cdnjs.cloudflare.com/ajax/libs/metro/4.1.5/css/metro.min.css
2020-11-21T22:12:25.365Z - debug: Running chrome with params {"printBackground":true,"marginTop":"0","marginRight":"0","marginLeft":"0","marginBottom":"0.4","landscape":true,"margin":{"top":"0","right":"0","bottom":"0.4","left":"0"}}
2020-11-21T22:12:25.465Z - debug: Skipping storing report.
2020-11-21T22:12:25.466Z - info: Rendering request 10 finished in 392 ms
2020-11-21T22:12:25.709Z - debug: pdf-utils postproces start
2020-11-21T22:12:25.711Z - debug: pdf-utils postproces end
2020-11-21T22:12:25.712Z - info: pdf-utils pdf processing was finished
2020-11-21T22:12:25.712Z - debug: Skipping storing report.
2020-11-21T22:12:25.713Z - info: Rendering request 9 finished in 1740 ms

Here is my NGINX setting.

upstream jsreport {
    # change port 8080 with the port you are running jsreport on
    server 127.0.0.1:5488;
    keepalive 15;
}

limit_req_zone $http_x_forwarded_for zone=login:2m rate=1r/s;

server {
    server_name   reporting.example.com;

    location /socket.io/ {
        proxy_pass http://jsreport;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
        proxy_redirect off;
        proxy_buffers 8 32k;
        proxy_buffer_size 64k;

        proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
    }

     location /login {
      limit_req zone=login burst=2;
    }

    location / {
        proxy_pass http://127.0.0.1:5488/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/reporting.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/reporting.example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = reporting.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name   reporting.example.com;
    listen 80;
    return 404; # managed by Certbot

}
xukangmin commented 3 years ago

I confirm it's not dependency issue, more like a request issue.

I make a test function in the library. It works fine.

        public HttpResponseMessage RenderByName(string url, string user, string pwd, string testData)
        {
            var client = new HttpClient()
            {
                BaseAddress = new Uri(url)
            };

            testRequest = testData;

            var data = new StringContent(testData, Encoding.UTF8, "application/json");

            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(
                    Encoding.UTF8.GetBytes(String.Format("{0}:{1}", user, pwd))));

            var responseReport = client.PostAsync("api/report", data).GetAwaiter().GetResult();

            return responseReport;
        }

I then test using the same request string, then RenderByNameAsync works fine.

Seems it's me passing the wrong data object, I will keep you updated.

xukangmin commented 3 years ago

There is a "options" key in the request which results in 502 Bad Gateway, if I remove "Options", it works fine. I tested in both PostMan and .Net. Any ideas?

(If I set "logsToResponseHeader" to false, it works)

"options" is added by RenderByNameAsync.

Working Request: (status 200)

{
    "template": {
        "name": "inhousereport-main"
    },
    "data": {
        "activeinhouse": 61472.30078125,
        "totalinhouse": 67134.296875,
        "currentdate": "11/21/2020",
        "openquote30": 0.0,
        "openquote90": 0.0,
        "saleslastday": 0.0,
        "saleslastdaydate": "2020-11-20T00:00:00",
        "salesthismonth": 0.0,
        "quotes": [
            {
                "ncus": 0,
                "exp": "2",
                "caltype": "AF",
                "po": "54535",
                "quoteid": "QTE002046",
                "mfg": "VPFlowscope",
                "customer": "Martin Calibration",
                "cert": "76960-23245",
                "calprice": 913.5,
                "datein": "2019-07-29",
                "datedue": "2019-07-31",
                "due": 193,
                "status": "Calibration Complete Awaiting Tech Review",
                "sortNumber": 66960
            },
            {
                "ncus": 1,
                "exp": "5",
                "caltype": "Vel",
                "po": "237454 OP",
                "quoteid": "19197",
                "mfg": "Amprobe",
                "customer": "Fike Corporation",
                "cert": "70780",
                "calprice": 264,
                "datein": "2018-09-26",
                "datedue": "2018-10-03",
                "due": 408,
                "status": "Trashed",
                "sortNumber": 70780
            }
        ]
    }
}

Not Working Request: (Status 502)

{
  "$id": "1",
  "template": {
    "$id": "2",
    "name": "inhousereport-main"
  },
  "options": {
    "$id": "3",
    "debug": {
      "$id": "4",
      "logsToResponseHeader": true
    }
  },
  "data": {
    "$id": "5",
    "activeinhouse": 61472.30078125,
    "totalinhouse": 67134.296875,
    "currentdate": "11/21/2020",
    "openquote30": 0.0,
    "openquote90": 0.0,
    "saleslastday": 0.0,
    "saleslastdaydate": "2020-11-20T00:00:00",
    "salesthismonth": 0.0,
    "quotes": [
      {
        "$id": "6",
        "ncus": 0,
        "exp": 5,
        "caltype": "AF",
        "po": "7202110809",
        "quoteid": "QTE002667",
        "mfg": "Matheson",
        "customer": "UL LLC",
        "certid": "78003-78004",
        "calprice": 229.0,
        "datein": "2019-09-16",
        "datedue": "2019-09-23",
        "due": 304,
        "status": "Calibration Complete Awaiting Tech Review",
        "sortnumber": 78004
      },
      {
        "$id": "7",
        "ncus": 0,
        "exp": 5,
        "caltype": "AF",
        "po": "58760",
        "quoteid": "QTE005170",
        "mfg": "Dwyer",
        "customer": "Martin Calibration",
        "certid": "82377",
        "calprice": 504.0,
        "datein": "2020-04-23",
        "datedue": "2020-04-30",
        "due": 146,
        "status": "Calibration In Progress",
        "sortnumber": 82377
      }
    ]
  }
}
xukangmin commented 3 years ago

2 solutions now.

  1. Disable logsToResponseHeader, by using RenderAsync with customized request.

                rs.RenderAsync(new RenderRequest()
                {
                    Template = new Template()
                    {
                        Name = "sales-order"
                    },
                    Data = data,
                    Options = new RenderOptions() { Debug = new DebugOptions() { LogsToResponseHeader = false } }
                })
  2. Increase Nginx buffer size

    location / {
        proxy_pass http://127.0.0.1:5488/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    
        proxy_buffer_size          128k;
        proxy_buffers              4 256k;
        proxy_busy_buffers_size    256k;
    
    }

At this point it's not a bug per say, I'm suggesting changing RenderByNameAsync debug option logsToResponseHeader to false by default or change your documentation about Nginx configuration.

pofider commented 3 years ago

Yes, we will completely remove the logsToResponseHeader in the next version in the next major release. I didn't expect it will cause such bad errors. My apologies.