views:

1361

answers:

4

I'm attempting to update a legacy VB6 component (not written by me) to the .NET platform. There is one function which posts an XML string to a URL:

Function PostToUrl(ByRef psUrl, ByRef psData, Byref psResponseText, ByRef psErrorMsg, ByRef psUsername, ByRef psPassword)

 On Error Resume Next 
 Dim objWinHTTP

 PostToUrl = False
 psErrorMsg = ""
 psResponseText = ""
 Dim m_lHTTPREQUEST_SETCREDENTIALS_FOR_SERVER
 m_lHTTPREQUEST_SETCREDENTIALS_FOR_SERVER    =0  

 Set objWinHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")


 objWinHTTP.Open "POST", psUrl

 If psUsername <> "" Then
  Call objWinHTTP.SetCredentials(psUsername, psPassword, m_lHTTPREQUEST_SETCREDENTIALS_FOR_SERVER)
 End If
 objWinHTTP.SetRequestHeader "Host", "http://www.xxx.com/CSIHTTP.asp"
 objWinHTTP.SetRequestHeader "X-Method", "Submit"
 objWinHTTP.SetRequestHeader "Content-Type", "text/xml"

 objwinHTTP.setTimeouts 3000, 15000, 15000, 120000


 Call objWinHTTP.Send(psData)

 ' Handle errors  '


 If Err.Number <> 0 Then

  psErrorMsg = Err.Description & " test"
  PostToUrl = False

 ElseIf objWinHTTP.Status <> 200 AND objWinHTTP.Status <> 202 Then

  psErrorMsg = "(" & objWinHTTP.Status & ") " & objWinHTTP.StatusText
  PostToUrl = False

 Else

  psErrorMsg = objWinHTTP.ResponseText & "(" & objWinHTTP.Status & ") " & objWinHTTP.StatusText
  PostToUrl = True

 End If

 Set objWinHTTP = Nothing
End Function

I've updated this to:

Public Function PostXml(ByVal XML As String) As Boolean

        Try
            Dim URL As String = My.Settings.NTSPostURL 
            'TODO: supply username and password!  '
            Dim Bytes As Byte() = Me.Encoding.GetBytes(XML)
            Dim HTTPRequest As HttpWebRequest = DirectCast(WebRequest.Create(Me.PostURL), HttpWebRequest)
            Dim Cred As New NetworkCredential("username", "password", "http://www.xxx.com")

            With HTTPRequest
                .Method = "POST"
                .ContentLength = Bytes.Length
                .ContentType = "text/xml"
                .Credentials = Cred
                .Timeout = 120000
                .Method = "Submit"
            End With

            Using RequestStream As Stream = HTTPRequest.GetRequestStream()
                RequestStream.Write(Bytes, 0, Bytes.Length)
                RequestStream.Close()
            End Using

            Using Response As HttpWebResponse = DirectCast(HTTPRequest.GetResponse(), HttpWebResponse)

                If Response.StatusCode <> HttpStatusCode.OK Then

                    Dim message As String = [String].Format("POST failed. Received HTTP {0}", Response.StatusCode)

                    Throw New ApplicationException(message)

                End If

            End Using

        Catch ex As WebException
            Dim s As String = ex.Response.ToString() & " " & ex.Status.ToString()
            Throw
        End Try

    End Function

However when I run the .NET code the server returns the error '403 Forbidden - protocol error' on the line: Using Response As HttpWebResponse = DirectCast(HTTPRequest.GetResponse(), HttpWebResponse) . The VB6 code runs fine. Can anyone identify any discrepancies between the two that might be causing this? It's left me stumped....

+2  A: 

Doesnt look like you are setting a HOST header int he .net version, unless I am missing it. If your server is using HostHeaderResolution to host multiple domains on the same ip address, then you will get a 403.

This is just a wild guess of course.

Goblyn27
Actually, this looks like a good guess since the VB6 example uses a whole URL for the Host header which isn't exactly conventional.
steamer25
Thanks for your replies, do you know how to set this? As far as I can see there is no property to set this.
Simon
The Host header thing is very strange. 1. The WinHTTP component will add the host header for you drawing it from the URL, this is a HTTP/1.1 requirment. 2. The host header should be the host name portion of the URL it should not contain additional path elements.
AnthonyWJones
@Simon: Ignore the host header thing, it does nothing and you certainly should not be attempting to add one yourself to the request.
AnthonyWJones
It's very possible that the Host header is not meaningless on the server side (if Simon has access to the code, he can find out for certain). If it turns out that Host: http://www.xxx.com/CSIHTTP.asp is important to the server, it looks like the client will need to be implemented using sockets.You can play with various HTTP requests using the WFetch tool from the IIS resource kit.
steamer25
+2  A: 

Have you tried capturing the traffic from the two sources and comparing them?

I'd start by trying Fiddler http://www.fiddler2.com/fiddler2/ which is a free HTTP debugging proxy.

If that doesn't work you can try WireShark which is a free protocol analyzer (a bit lower level so there will be a lot of detail to filter out). http://www.wireshark.org/

steamer25
+1  A: 

I don't think you've got the Credential thing sorted correctly. You are using the authority portion of the URL as a credential domain.

Try it like this:-

Dim myCache as New CredentialCache()
myCache.Add(New Uri("http://www.xxx.com"), "Basic", New NetworkCredential("username", "password"))

HTTPRequest.Credentials = myCache

This assumes that Basic authentication is needed, another contender would be "Digest" or "NTLM" which may require a domain. For example if the text of the username is like this "domain\username" in the old code then you would want:-

Dim myCache as New CredentialCache()
myCache.Add(New Uri("http://www.xxx.com"), "NTLM", New NetworkCredential("username", "password", "domain"))

HTTPRequest.Credentials = myCache

BTW, I agree with steamer25, for this sort of thing you really need a tool such as fiddler.

If you have an example of the old stuff working place fiddler on the machine where this code runs. Start fiddler and use the command proxycfg -u. Now you can observe the converstation the original stuff has with the ASP server. Use proxycfg -d before closing fiddler.

Now in your .NET code place the following in the app.config:-

<system.net>
 <defaultProxy enabled="false">
  <proxy proxyaddress="http://127.0.0.1:8888" bypassonlocal="False"/>
 </defaultProxy>
</system.net>

Start fiddler and flick the enabled attribute to "true". Now you should see the conversation your new code is trying to have with the ASP Server. Make sure you set enabled="false" before using your new code without fiddler capturing.

AnthonyWJones
A: 

After much pain I've finally managed to get it working. Problem was with the text encoding, I've changed it to ASCII and it all works:

Try
            Dim Bytes As Byte() = Me.Encoding.GetBytes(XML)
            Dim HTTPRequest As HttpWebRequest = DirectCast(WebRequest.Create(Me.PostURL), HttpWebRequest)

            With HTTPRequest
                .Method = "POST"
                .ContentLength = Bytes.Length
                .ContentType = "text/xml"
                .Credentials = New NetworkCredential("user", "password") 'myCache
            End With

            Using RequestStream As Stream = HTTPRequest.GetRequestStream()
                RequestStream.Write(Bytes, 0, Bytes.Length)
                RequestStream.Close()
            End Using

            Using Response As HttpWebResponse = DirectCast(HTTPRequest.GetResponse(), HttpWebResponse)

                Return Response.StatusCode

            End Using

        Catch ex As WebException
            Dim s As String = ex.Status.ToString
            Throw
        End Try

Thanks to all who helped. Upvotes all round.

Simon