3

I've been trying for two days to set up an endpoint that meets the requirements of a 3rd-party provider. They are going to send us updates about the status of a business object via a HTTPS POST and the contents of the request will be JSON. Unfortunately, it has to be written in VBScript for now.

At the moment, I'm unable to get the raw contents of the request they are sending me, so I cannot process it at all.

I have created a simple endpoint (raw-form.asp) and two test pages to demonstrate the issue. First, I set up a simple test HTML page (raw-form-test1.asp) using an HTML form, and it works correctly. The second test page (raw-form-test2.asp) sends the contents to the endpoint using a WinHttpRequest. When using this, the data isn't there. I'm attempting to get it via Request.Body.

raw-form-asp:

<%
    Dim post : post = Request.Body
    Response.ContentType = "text/plain"
    Response.Write "Your " & Request.ServerVariables("REQUEST_METHOD") & " data was: " & post
%>

raw-form-test1.asp:

<!DOCTYPE html>
<html>
    <body>
        <form action="raw-form.asp" method="post">
            <p><textarea name="data"></textarea></p>
            <p><input type="submit"></p>
        </form>
    </body>
</html>

raw-form-test2.asp:

<%
    Dim data : data = Request.Form("data")
    Dim resp : resp = ""

    If data <> "" Then
        Dim http : Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
        http.Open "post", "http://localhost:8080/raw-form.asp"
        http.Send data
        http.WaitForResponse(10)
        resp = http.Status & " | " & http.ResponseText
    End If
%>
<!DOCTYPE html>
<html>
    <body>
        <%= Server.HTMLEncode(resp) %>
        <form action="raw-form-test2.asp" method="post">
            <p><textarea name="data"></textarea></p>
            <p><input type="submit"></p>
        </form>
    </body>
</html>

When filling in some random text and submitting the first test, the response body is as I'd expect:

Your POST data was: data=abc

When using the second test, the returned result in resp is:

200 | Your POST data was:

I've also tried to use Request.BinaryRead() without success (VBScript can get its length, but not the contents - probably just VB being terrible with types). I'm hoping there's an alternative way to get the data.

BoffinBrain
  • 6,337
  • 6
  • 33
  • 59
  • Depending on how the 3rd party endpoint is setup, I wouldn't expect a `POST` request to return a response other than `HTTP 200 OK` or better still `HTTP 201 Created`. The only way you are going to know for sure what is being returned is run Fiddler while you are testing the code. Most *(not all)* HTTP-based endpoints use RESTful design principles, reading up on that might help you understand why your not seeing anything other than an `HTTP 200 OK`. It likely has a `Location` header in the response which you are not seeing because you are trying to check the body. – user692942 Apr 05 '17 at 17:00
  • This might be of interest *(assuming it is RESTful)* - [Understanding REST: Verbs, error codes, and authentication](//stackoverflow.com/a/2022938) – user692942 Apr 05 '17 at 17:05
  • I've just realised your example isn't talking to a 3rd party endpoint, ignore what I've said the problem is likely you are calling `Request.Form("data")` in `raw-form-test2.asp` but where is that suppose to come from? Surely you just want some test data a string or something? You'll only get something in `Request.Form` if that page was called via a form submission. – user692942 Apr 05 '17 at 17:18
  • @Lankymart Glad you managed to spot what was being asked now. :) So, what the test 2 does is post to itself, and then use WinHttpRequest to forward that data to the test endpoint with a POST of its own. – BoffinBrain Apr 05 '17 at 20:56
  • @BoffinBrain there is no such thing as `Request.Body`. A full list of the `request` objects is at https://www.w3schools.com/asp/asp_ref_request.asp. – Mike Poole Nov 19 '19 at 13:57
  • @MikePoole I'm afraid you're a little late to the party! Also, in my experience, W3Schools is not a definitive source for APIs. They usually cover just the basics. The request body does exist but only with POST requests. – BoffinBrain Nov 20 '19 at 16:00
  • @BoffinBrain that's me, fashionably late! Do you have a source for `Request.Body`? I have not used it before as I usually deal with posts in binary but am intrigued about receiving posts with less palaver. – Mike Poole Nov 20 '19 at 20:12
  • @MikePoole Funnily enough, I can't find a source for it now, but I imagine VS suggested it or I guessed it. I'm not sure if it was an official part of ASP, .NET or something else, but it worked on my setup. – BoffinBrain Nov 21 '19 at 20:12

2 Answers2

6

In raw-form.asp, you can Response.BinaryWrite the result of Request.BinaryRead, like this:

<%
If Request.TotalBytes > 0 Then    
    Response.ContentType = "text/plain"
    Response.Write "Your " & Request.ServerVariables("REQUEST_METHOD") & " data was: " 
    Response.BinaryWrite Request.BinaryRead(Request.TotalBytes)
End If
%>

Or you can use Request.BinaryRead and then write the bytes to an ADO stream object, which you can then read the text from. Here's an example from: https://stackoverflow.com/a/9777124/989516

<%

If Request.TotalBytes > 0 Then
    Dim lngBytesCount, post
    lngBytesCount = Request.TotalBytes
    post = BytesToStr(Request.BinaryRead(lngBytesCount))
    Response.ContentType = "text/plain"
    Response.Write "Your " & Request.ServerVariables("REQUEST_METHOD") & " data was: " & post
End If

Function BytesToStr(bytes)
    Dim Stream
    Set Stream = Server.CreateObject("Adodb.Stream")
        Stream.Type = 1 'adTypeBinary
        Stream.Open
        Stream.Write bytes
        Stream.Position = 0
        Stream.Type = 2 'adTypeText
        Stream.Charset = "iso-8859-1"
        BytesToStr = Stream.ReadText
        Stream.Close
    Set Stream = Nothing
End Function

%>
Community
  • 1
  • 1
Kevin Collins
  • 1,453
  • 1
  • 10
  • 16
  • They are doing a standard `HTTP POST` there is no reason they can't use `Request.Form` the issue I have is they seem to say they are using `Request.Body` but there is no such property in Classic ASP of the `Request` object. – user692942 Apr 06 '17 at 08:57
  • Genius! Thank you... That worked like a charm. I did try to get the `BinaryRead` working before, but didn't know I'd have to start using Streams to read it. I vaguely recall another VBScript library that handles multipart form data using similar techniques. – BoffinBrain Apr 06 '17 at 09:04
  • @Lankymart I think VBScript's `Request.Form` stops working if you don't specify (correctly, or omit) what the type the POST request actually is. Hence, getting the raw request data is necessary. – BoffinBrain Apr 06 '17 at 09:06
  • @BoffinbraiN you sending it multi-part? If that's your issue set the `ContentType` header to `application/x-www-form-urlencoded` before posting via xhr, this is overkill unless you are wanting to send binary data. – user692942 Apr 06 '17 at 09:11
  • @Lankymart In this case, I had no control over what the content type was, because it was a third-party service. Technically speaking, since the data they were sending was JSON, specifying that content type would be wrong/misleading. – BoffinBrain Apr 06 '17 at 09:40
  • @BoffinbraiN so the issue is you want to post to an endpoint and get a JSON response is that really all you are trying to do? – user692942 Apr 06 '17 at 10:10
  • @BoffinbraiN it's not a case of you not having control, you can pass any `Content-Type` you want if the 3rd party returns JSON then just interpret the response as `application/json` *(see [my answer](http://stackoverflow.com/a/43251605/692942))*. – user692942 Apr 06 '17 at 10:32
0

Still trying to understand what the big issue is, assuming your endpoint returns JSON you could modify the below procedures in the example code I posted.

Sub http_post()
  Dim data : data = Request.Body
  Dim resp : resp = ""

  If data <> "" Then
    Dim http : Set http = Server.CreateObject("WinHttp.WinHttpRequest.5.1")
    http.Open "post", "http://localhost:1337/test9.asp?action=2"
    Call http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
    http.Send "data=" & data
    http.WaitForResponse(10)
    resp = http.Status & " | " & http.ResponseText
  End If
  'Interpret the response from the xhr as JSON.
  Response.ContentType = "application/json"
  Call Response.Write(http.ResponseText)
End Sub

Sub http_xhr()
  Dim post : post = Request.Body
  Response.ContentType = "application/json"
%>{
  data: {
    "SomeItem": "SomeData",
    "SomeNumber": 1,
    "PostedData": "<%= post %>"
  }
}<%
End Sub
%>

I've just tested your code and restructured it a bit so I could test it using one file and it does the same thing.

<%
Dim data, method

Call init()

Sub init()
  Dim action
  method = UCase(Request.ServerVariables("REQUEST_METHOD") & "")

  Select Case method
  Case "GET"
    Call http_get()
  Case "POST"
    action = Request.QueryString("action") & ""
    If Len(action) > 0 And IsNumeric(action) Then action = CLng(action) Else action = 1
    Select Case action
    Case 1
      Call http_post()
    Case 2
      Call http_xhr()
    End Select
  End Select
End Sub

Sub http_get()
%>
<!DOCTYPE html>
<html>
  <body>
    <form action="?action=1" method="post">
      <p><textarea name="data"></textarea></p>
      <p><input type="submit"></p>
    </form>
  </body>
</html>
<%
End Sub

Sub http_post()
  Dim data : data = Request.Form("data")
  Dim resp : resp = ""

  If data <> "" Then
    Dim http : Set http = Server.CreateObject("WinHttp.WinHttpRequest.5.1")
    http.Open "post", "http://localhost:1337/test9.asp?action=2"
    'We are mimicing a form post use x-ww-form-urlencoded.
    Call http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
    'Need to make sure we pass this as "data=value" so we can use `Request.Form("data") in the xhr call.
    http.Send "data=" & data
    http.WaitForResponse(10)
    resp = http.Status & " | " & http.ResponseText
  End If
  Call Response.Write(resp)
End Sub

Sub http_xhr()
  Dim post : post = Request.Form("data")
  Response.ContentType = "text/plain"
  Response.Write "Your " & Request.ServerVariables("REQUEST_METHOD") & " data was: " & post
End Sub
%>

In main differences that make it work are;

  1. Setting the Content-Type header on the xhr so we can call Request.Form (actually works with Request.Body, new one on me).
  2. Passing the data to the xhr as data=value encoded values.
Community
  • 1
  • 1
user692942
  • 16,398
  • 7
  • 76
  • 175
  • Thanks for trying to help out, but Kevin Collins already nailed the issue. Just to clarify again, *I am the one* having to write the endpoint. The 3rd party posts to it. I have no control over the content type they use. The test page I wrote was just simulating the issue. As it turns out, they use `application/json` which is why `Request.Form` and `Request.Body` did not work. – BoffinBrain Apr 07 '17 at 13:52
  • @BoffinbraiN That clears it up, sorry I thought you were sending to an endpoint and you were simulating that. Pointless answer but will leave it anyway, might help someone. – user692942 Apr 07 '17 at 14:11