2

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....

AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
Simon
  • 6,062
  • 13
  • 60
  • 97
  • Its not easy porting something that is of such low quality to start with. Are you sure you don't want to analyse the component at a more functional level and implement a-fresh in .NET? – AnthonyWJones Jun 02 '09 at 18:45
  • Absolutely! The only reason I am delving into this VB6 function specifically is beacuse I could not get the .Net code to run! – Simon Jun 03 '09 at 10:36

4 Answers4

2

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.

Peter Lange
  • 2,786
  • 2
  • 26
  • 40
  • 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 Jun 02 '09 at 15:43
  • 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 Jun 02 '09 at 18:45
  • 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 Jun 02 '09 at 18:47
  • @Simon: Ignore the host header thing, it does nothing and you certainly should not be attempting to add one yourself to the request. – AnthonyWJones Jun 02 '09 at 18:48
  • 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 Jun 02 '09 at 19:21
2

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
  • 9,278
  • 1
  • 33
  • 38
1

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
  • 6,062
  • 13
  • 60
  • 97
1

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
  • 187,081
  • 35
  • 232
  • 306