Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Can I do a custom request to retrieve a PDF file?

    I have a SmartClientJS 13 application using a non-SmartClient server. I've created a data source instance to retrieve orders. It more or less has these properties:

    Code:
    {
      dataFormat: 'json',
      dataURL: '/orders.json',
      disableQueuing: true,
      jsonPrefix: '',
      jsonSuffix: '',
      operationBindings: [
        { dataProtocol: 'postMessage', operationType: 'fetch' },
        { dataProtocol: 'postMessage', operationType: 'add' },
        { dataProtocol: 'postMessage', operationType: 'update' },
        { dataProtocol: 'postMessage', operationType: 'remove' }
      ]
    }
    SmartClient uses the HTTP POST method for all operation types.

    When a single record is loaded, the data source sends a payload with a "data" object containing an "id" property (eg. { data: { id: 1 }}). My server checks this "data" property and if it contains an ID, it retrieves a single record, else a collection.

    How can I make a request to retrieve a PDF file (eg. the invoice for an order)? Can I still use the data source or should I use RPCManager.sendRequest()? I've tried the latter but couldn't create the right "data" object. Ideally, I would like to change the dataURL to end with ".pdf" instead of ".json".

    Hopefully my question makes sense. I've tried ChatGPT to find an answer but wasn't able to work it out.

    #2
    Returning a binary PDF file is not a JSON or XML response, so it doesn't fit into your "dataFormat" here.

    So you just do it as a normal HTTP request, which you can initiate via RPCManager.sendRequest().

    Note that when using the SmartClient Server, as the dataFormat is not just JSON or XML, but a proprietary one, in which binary responses are allowed. That means that binary fields can be treated as just another field type, and a DataSource request can be used to download them.

    But implementing the same thing for you would require a lot of work: parsing & validating your own JSON responses, dealing with HTTP headers, etc, so unless you plan to implement a general-purpose system for storing binary files in any DataSource, because you're going to be doing this with lots of different types of binary files and lots of different DataSources, you should just use RPCManager.sendRequest().

    Comment


      #3
      The last hours I tried the approach via the data source, but it doesn't seem to work. Reading your reply, confirms that. :-)

      So I will go back to my initial approach of using RPCManager.sendRequest(). This gives me the problem of not being able to send the right payload. It sends data as form data which isn't what I want (I guess). At least, the data sources that I use, retrieve the data via a POST with a payload. Ideally, I want a universal solution on the server, which expects the "id" property inside of a "data" object.

      So the request needs to be something like this POST /<hostname>/orders.pdf and the payload should be { data: { id: 1 } }.

      Comment


        #4
        The request I make is like this:

        Code:
          isc.RPCManager.sendRequest({
            actionURL: '/orders.pdf',
            httpHeaders: { 'X-CSRF-Token': '<some hash code>' },
            params: {
              data: { id: 1 },
              operationId: 'download_invoice'
            },
            useSimpleHttp: true
          });
        In Chrome, when inspecting the Network tab, for that request it normally (when using data source requests) says "Request Payload", but now it says "Form Data". And the data itself is displayed differently (when Request Payload is active, the variables can be opened via a Tree component when they are an object.

        Should I fix this on my server?

        Hope this makes sense.

        Comment


          #5
          We're not following the question.

          Yes, RPCRequests are displayed differently from DataSource Requests in the RPC tab. This is by design, and is desirable.

          Nothing you could do on your server would change how the RPC request is sent.

          At this point you should be able to see the request hitting your server, with the expected HTTP params.. so now it's time to write server code that returns the file.

          Comment


            #6
            This seems to be the right call:

            Code:
            isc.RPCManager.sendRequest({
              actionURL: '/orders.pdf',
              data: isc.JSON.encode({ data: { id: 1 } }),
              httpHeaders: { 'X-CSRF-Token': '<some hash code>' },
              params: { operationId: 'download_invoice' },
              useSimpleHttp: true
            });
            Now the data comes in correctly and my server understands it!

            Comment


              #7
              Using the above code, shows "Request payload" in the developer console of Chrome (Network tab -> click on a specific request and click on its "Payload" tab). My server sees a "data" request parameter containing an "id" attribute. So for me this should be enough to implement the sending of the PDF file.

              Comment


                #8
                Originally posted by Isomorphic View Post
                We're not following the question.

                Yes, RPCRequests are displayed differently from DataSource Requests in the RPC tab. This is by design, and is desirable.

                Nothing you could do on your server would change how the RPC request is sent.

                At this point you should be able to see the request hitting your server, with the expected HTTP params.. so now it's time to write server code that returns the file.
                I was afraid of that. Apparently I'm unable to explain things in a clear way. I have no clue on how to change it. Using ChatGPT doesn't help either. It simply gives false results. For example, it just told me I should use the "rpcPayload" property when using sendRequest(). That property doesn't exist at all.

                Comment


                  #9
                  Let's assume the request is okay right now (at least it looks okay). I haven't yet created the PDF response. But when I send it back, will the browser start showing the PDF? Or download it? It's not the same as doing a GET request via window.open().

                  Comment


                    #10
                    What may be the problem here, both with ChatGPT and with talking to us, is that it looks like you're not that familiar with HTTP, that is, you've really only seen it via the Rails and SmartClient APIs.

                    For example, you're saying the data came across correctly once you JSON encoded it. Well, JSON is not part of the HTTP spec. So this means that however you're picking up the data in Rails, Rails is expecting it as JSON encoded. Somewhere else, Rails surely has access to the HTTP parameters directly, without assuming JSON encoding.

                    Perhaps try this: ask ChatGPT "please show me a typical HTTP request and response for downloading a PDF file that is stored in a database, assuming that the ID of the file is passed as an HTTP parameter".

                    This will show you the way file download works at an HTTP level. This will give you an idea of what needs to be set, and should help you find the appropriate APIs in both SmartClient and Rails, since you will now understand what needs to happen at the HTTP level.

                    Comment


                      #11
                      Changing the code from a couple of messages above into:

                      Code:
                      isc.RPCManager.sendRequest({
                        actionURL: '/orders.pdf',
                        contentType: 'application/json', // this seems to be important to get the right payload
                        data: isc.JSON.encode({ data: { id: 1 } }),
                        httpHeaders: {
                          'Accept': 'application/pdf', // this instructs my server to send the PDF file
                          'X-CSRF-Token': '<some hash code>'
                        },
                        params: { operationId: 'download_invoice' },
                        useSimpleHttp: true
                      });
                      The server indeed sends back a PDF file, but then nothing happens: the browser isn't showing or downloading the PDF. Is this because the request is made using the POST method?

                      Comment


                        #12
                        Yes, you generally would want an HTTP GET (as the suggested ChatGPT interaction covers). POST is also possible, but much more complicated.

                        Comment


                          #13
                          I have looked at the question suggested by you in ChatGPT. It's not using SmartClient but native JavaScript. And when the response comes, it creates a <A> element and programmatically clicks on it. Is this really a good approach? But I will try that, using the SmartClient callback.

                          When it comes to the POST request: in an earlier version of my Rails application I followed the REST principles where GET is used for retrieval of data. But looking at the description of the RestDataSource class (under the header "Server inbound data formats"), "postMessage" is advised as data protocol. The way my server is set up (quite generic), it was easier to have the PDF download also as a POST request (because it is triggered as "operationId" of my show() action).

                          Writing this down makes me wonder if it makes sense at all without showing you the bigger picture. But that's way too much time consuming for both of us.

                          So, I will try the dynamic creation and clicking of the <A> element (which I personally dislike) and I will try to create the GET request approach.

                          Thanks for your patience, I know I'm asking too much questions.

                          Comment


                            #14
                            No, don't do that. You either are using an older AI, or you've got a long chat history about how to do this in JavaScript, which means you didn't get the expected response.

                            Try a new chat window.

                            Here (with formatting not quite right) is the kind of response that would be expected to this question, if you ask it on its own:


                            Below is an example of a typical HTTP GET request that could be used to download a PDF file from a server. The client (for example, a web browser) sends the GET request, and the server responds with the PDF file.
                            HTTP Request:


                            vbnetCopy code
                            GET /download/pdf?id=12345 HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Accept: application/pdf
                            In the above request:
                            • GET /download/pdf?id=12345 HTTP/1.1 - This line is called the request line. It's a GET request to the path /download/pdf with a query string ?id=12345 specifying the ID of the file. The protocol version is HTTP/1.1.
                            • Host: www.example.com - The Host header specifies the domain name of the server.
                            • User-Agent: Mozilla/5.0... - The User-Agent header field contains information about the client (usually a web browser) making the request.
                            • Accept: application/pdf - This Accept header tells the server what media types the client will accept, in this case, PDF files.
                            HTTP Response:


                            yamlCopy code
                            HTTP/1.1 200 OK Date: Tue, 25 Jul 2023 14:28:12 GMT Server: Apache/2.4.27 (Unix) Last-Modified: Mon, 24 Jul 2023 12:34:56 GMT ETag: "74c012-5578-4c432678" Accept-Ranges: bytes Content-Length: 22344 Content-Type: application/pdf <binary PDF data>
                            In the above response:
                            • HTTP/1.1 200 OK - This line is called the status line. It includes the HTTP version (HTTP/1.1), the status code (200), and the status text (OK).
                            • Date: Tue, 25 Jul 2023 14:28:12 GMT - This header field contains the date and time at which the message was originated.
                            • Server: Apache/2.4.27 (Unix) - This header field contains information about the server software used.
                            • Last-Modified: Mon, 24 Jul 2023 12:34:56 GMT - This header indicates the date and time at which the server believes the variant was last modified.
                            • ETag: "74c012-5578-4c432678" - The ETag (Entity Tag) header field provides a mechanism to cache mechanisms in protocols like HTTP.
                            • Accept-Ranges: bytes - This header indicates that the server accepts range requests, which allows a client to request a portion of the file. This is useful for resuming interrupted downloads or splitting a download into multiple simultaneous streams.
                            • Content-Length: 22344 - This header field indicates the size of the body data in the response, in bytes.
                            • Content-Type: application/pdf - This header field indicates the media type of the resource, in this case, a PDF file.
                            • <binary PDF data> - This is where the content of the PDF file would be. In an actual HTTP message, this would be the binary data of the PDF file.

                            Remember that if the file is stored in a database, the server-side software (for example, a script written in a language like PHP, Node.js, Python, or Ruby) would be responsible for fetching the file from the database based on the provided id parameter, setting the appropriate headers, and including the file data in the response.

                            Comment

                            Working...
                            X