3

Alright guys, I have been wrestling with this issue for a day or so with no clear resolution. I will start with the exception:

The remote server returned an error: NotFound.
    at System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
    at System.Net.Browser.ClientHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)

I am attempting to connect to the JIRA rest API for logging in a user. Our JIRA system is currently running 4.4.1, and the API information I am attempting to hit is documented here: https://developer.atlassian.com/static/rest/jira/4.4.1.html. (See the POST request for the "/auth/1/session" API)

The API calls for a POST request, with a JSON body for the user credentials. I have tried both building the JSON manually, as well as using a JSON library, and the results were the same. The json I am sending is:

{ 
    "username": "test",
    "password": "test"
}

I have attempted to change the content type and encoding to anything I can imagine. This includes "text/json", "application/json", adding Encoding.UTF8 to the stream writer, etc. All the results are the same.

Perhaps the most frustrating part of this entire ordeal, is that I was able to write this in Java for Android in no time at all, so I do not believe it is an API misunderstanding as so much as a Windows Phone 8 and/or C# misunderstanding.

A few final things to point out:

  • If I change the code to use a GET request, point to "http://www.google.com", and remove the request callback (skipping directly to the response), everything works and I get the result I expect.
  • I am confused by the "BeginX" "EndX" methods for the HttpWebRequest. I understand asynchronous tasks, but just not exactly how C# manages this. Most of the MSDN documentation does not use these, and instead has methods for "GetRequest()" and "GetResponse()" which seem far more straight forward. And most recent examples I have sifted through also use these methods. I am under the assumption that these methods were removed in the Windows Phone 8 SDK to ensure everything that can run asynchronously does.
  • If I hit the JIRA URL directly from any browser except the Windows Phone 8 emulator, I get a valid 403 as outlined by the documentation. However, if I hit the URL directly in the emulator, it prompts me for login credentials. This made me think that basic auth was required, so I have tried adding that but I get the same results.

Below is the code as I currently have it. I have taken out my Jira host name

class LoginService
{
    public static UserSession login(string aUsername, string aPassword)
    {
        String loginUrl = "http://{myjiraurl}/rest/auth/1/session/";
        HttpWebRequest request = (HttpWebRequest) WebRequest.Create(loginUrl);

        string jsonBody = JsonHelper.GenerateLoginJson(aUsername, aPassword);

        RequestInformation requestInfo = new RequestInformation();
        requestInfo.request = request;
        requestInfo.JsonBody = jsonBody;

        requestInfo.request.Method = "POST";
        requestInfo.request.ContentType = "text/json";
        requestInfo.request.ContentLength = (long)jsonBody.Length;

        request.BeginGetRequestStream(new AsyncCallback(LoginRequestCallback), requestInfo);
        return null;
    }

    private static void LoginRequestCallback(IAsyncResult result)
    {
        RequestInformation requestInfo = (RequestInformation)result.AsyncState;
        HttpWebRequest webRequest = requestInfo.request;

        // End the Asynchronus request.
        Stream requestSream = webRequest.EndGetRequestStream(result);

        StreamWriter requestWriter = new StreamWriter(requestSream);
        requestWriter.Write(requestInfo.JsonBody);
        requestWriter.Flush();
        requestWriter.Close();
        requestSream.Close();

        webRequest.BeginGetResponse(new AsyncCallback(LoginResponseCallback), requestInfo);
    }

    private static void LoginResponseCallback(IAsyncResult result)
    {
        RequestInformation requestInfo = (RequestInformation)result.AsyncState;
        HttpWebRequest webRequest = requestInfo.request;
        try
        {

            HttpWebResponse response = (HttpWebResponse)webRequest.EndGetResponse(result);

            if (response.StatusCode == HttpStatusCode.OK)
            {

                Stream streamResponse = response.GetResponseStream();

                string responseResult = StreamHelper.ReadStreamToString(streamResponse);
                streamResponse.Close();
            }
            response.Close();
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine(e.Message);
            System.Diagnostics.Debug.WriteLine(e.StackTrace);
        }
    }
}

public class RequestInformation
{
    // This class stores the request state of the request and any necessary information for the request body
    public HttpWebRequest request;

    public string JsonBody { get; set; }
    public string Result { get; set; }

    public RequestInformation()
    {
        request = null;
    }
}

Edit: For some clarification, the code is failing when attempting to generate the response object on this line...

HttpWebResponse response = (HttpWebResponse)webRequest.EndGetResponse(result);

Update 1:

I have discovered that I can cast the response of the WebException into an HttpWebResponse. This allowed me to see what the exact status code was, which was HttpStatusCode.UnsupportedMediaType or 415. This now points directly at an issue with the JSON encoding that is being sent to the server.

calebisstupid
  • 423
  • 5
  • 13
  • Regarding the prompt for login credential only in Windows 8 emulator, what happens when you change the user agent? – LostInComputer Jan 17 '13 at 14:40
  • I will try this right now. Would you suggest any specific user agents to test with? – calebisstupid Jan 17 '13 at 14:43
  • I changed the user agent in code to several different ones: android, ipad, IE desktop browser. All the same results. – calebisstupid Jan 17 '13 at 14:46
  • Does the test user exist on the Jira server? – flup Jan 17 '13 at 15:21
  • Have you looked at the contents of the response? Maybe it tells you what you're doing wrong. Have you compared the working request (from Android) to the non-working one? Something like Wireshark could be helpful when doing that. – svick Jan 17 '13 at 15:22
  • flup, the test user does not exist on the Jira server, but I should still be getting a valid response (401 with a description that the credentials failed). – calebisstupid Jan 17 '13 at 15:27
  • svick, I have looked into the contents of the response through other means (other browsers, CURL, etc) and they are all giving me valid responses. The problem here is that the response object is never created due to the runtime exception, so I am unable to debug it from code. – calebisstupid Jan 17 '13 at 15:27
  • 1
    Are you following this example? http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetrequeststream.aspx – flup Jan 17 '13 at 15:44
  • I am having the same issue as @calebisstupid. Flup, that is a good example, but how I can I write the code for my code? – Logan Serman Jan 17 '13 at 15:45
  • flup, I was under the impression that you must get and write the desired body to the request before grabbing the response. All of the examples I have seen that only use `BeginBetResponse()` are typically using GET requests, and not sending any data. One of the examples I used is [this stack overflow post](http://stackoverflow.com/questions/9145667/how-to-post-json-to-the-server-using-c), but as I outlined above, those simplified `GetRequestStream()` and `GetResponse()` do not exist in the latest WP8 SDK. – calebisstupid Jan 17 '13 at 15:46
  • @calebisstupid Yes, I saw a bit later that this is the way to go for posting. – flup Jan 17 '13 at 15:48
  • Also worth noting, that my original code was nearly identical to that example. I will revisit that example shortly since I feel I have learned a lot since my original attempt. – calebisstupid Jan 17 '13 at 15:49
  • Biggest difference with the example that I can see is the `allDone.WaitOne();` What is your original thread doing while the async call is out? – flup Jan 17 '13 at 15:50
  • During my original attempt, I used the ManualResetEvent for thread control. I found that it would commonly lead to deadlocks, and saw where another WP8 developer recommended against using it (I can dig up the link if I need to). You did get me thinking though, since I am not holding the main thread, it would be returning null in this case. I'm not sure how valid of a concern that is though, since when I alter this code to send a GET to google.com it works as expected. – calebisstupid Jan 17 '13 at 15:53
  • 2
    Can you run Fiddler or somesuch to see what http traffic is going on? – flup Jan 17 '13 at 16:36
  • I read a bit about Fiddler, have yet to try it. I will get that setup after lunch today and post any results. – calebisstupid Jan 17 '13 at 16:54
  • So a brief update. I have managed to cast the WebException to an HttpWebException and have pulled that actual response code. It is HttpStatusCode.UnsupportedMediaType. This helps me a great deal. – calebisstupid Jan 17 '13 at 18:16
  • 1
    so we're thinking you have to add a header that says you'll accept json? http://stackoverflow.com/questions/9847564/how-do-i-get-mvc-4-webapi-to-return-json-instead-of-xml-using-chrome – flup Jan 17 '13 at 19:11
  • flup, I have tried that header as well, same error – calebisstupid Jan 17 '13 at 20:23

1 Answers1

0

You guys are going to think I am a lunatic, but as of about 3:00pm I have been getting the expected results.

I will be editing this answer with the updated code once I refactor it a little bit.

Updated working code:

public static async Task<HttpWebResponse> SendHttpPostRequest(string url, string content, string contentType, string acceptType)
    {
        HttpWebRequest request = HttpWebRequest.CreateHttp(new Uri(url, UriKind.Absolute));
        HttpWebResponse response = new HttpWebResponse();
        string responseText = "";

        request.Method = "POST";
        request.ContentType = contentType;
        request.Accept = acceptType;

        Task<Stream> requestTask = Task.Factory.FromAsync(request.BeginGetRequestStream, asyncResult => request.EndGetRequestStream(asyncResult), (object)null);
        await requestTask.ContinueWith(t =>
        {
            using (Stream stream = requestTask.Result)
            using (StreamWriter requestWriter = new StreamWriter(stream))
            {
                requestWriter.Write(content);
            }
        });

        Task<WebResponse> responseTask = Task.Factory.FromAsync(request.BeginGetResponse, asyncResult => request.EndGetResponse(asyncResult), (object)null);
        await responseTask.ContinueWith(t =>
        {
            try
            {
                response = (HttpWebResponse)responseTask.Result;
            }
            catch (AggregateException ae)
            {
                foreach (Exception e in ae.InnerExceptions)
                {
                    if (e is WebException)
                    {
                        response = (HttpWebResponse)((WebException)e).Response;
                        System.Diagnostics.Debug.WriteLine(e.Message);
                        System.Diagnostics.Debug.WriteLine(e.StackTrace);
                    }
                }
            }
        });

        return response;
    }
}
calebisstupid
  • 423
  • 5
  • 13