I built a C# application about 8 months ago to pull SurveyMonkey responses and store them on our SQL Server. The application has run every day for over 6 months without any issue, but suddenly Friday morning I can't get any requests to go through. I'm not seeing anything on the developer site mentioning outages, so I've been examining my code for possible issues.
This is the first app I've ever written to create web requests, so it's possible I'm doing things badly.
Request, from Fiddler:
POST https://api.surveymonkey.net/v2/surveys/get_survey_list?api_key=<key hidden> HTTP/1.1
Authorization: bearer <token hidden>
Content-Type: application/json
Host: api.surveymonkey.net
Content-Length: 146
Expect: 100-continue
Connection: Keep-Alive
{
"page": 1,
"fields": ["title","analysis_url","preview_url","date_created","date_modified","language_id","question_count","num_responses"]
}
Response body, from Fiddler:
{"status":1,"errmsg":"Request header \"Authorization\" token not found"}
C# code:
private JObject SubmitPostRequest(string URIPath, string RequestBody)
{
using (var webClient = new WebClient())
{
// Create URI for POST request to go to. URI varies depending on API method and includes API Key.
Uri requestURI = new Uri(APIHost, URIPath + "?api_key=" + APIKey);
// Have web client use NT credentials for proxy server
webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
// Specify headers: access token in authorization header, content type
webClient.Headers["Authorization"] = "bearer " + AccessToken;
webClient.Headers["Content-Type"] = "application/json";
// Create requestBody as byte[]
byte[] requestBody = Encoding.Default.GetBytes(RequestBody);
// Connect to the URI and send requestBody as a POST command.
byte[] postResponse = webClient.UploadData(requestURI, "POST", requestBody);
// Update LastConnect
LastConnect = DateTime.Now;
++TransactionCounter;
// Convert byte[] response to string, parse string and return as JObject.
JObject jsonResponse = JObject.Parse(Encoding.Default.GetString(postResponse));
// Evaluate "status" field of jsonResponse. Throw exception if response is not 0.
dynamic dyn = jsonResponse;
if (dyn.status != 0)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("HTTP Post request failed:");
sb.Append("\tstatus: ").AppendLine(dyn.status.ToString());
sb.Append("\terrmsg: ").AppendLine(dyn.errmsg.ToString());
throw new WebException(sb.ToString());
}
return jsonResponse;
}
}
It took awhile, but I managed to get in touch with someone in corporate IT who monitored the traffic to see what was going on. They implemented a fix to the proxy server and everything is finally working again.
Related
I'm using RestSharp in .NET 6 to execute a POST request to NetSuite in a c# console application.
I'm using Token Based Authentication and OAuth1
When I execute the request using the same credentials (consumer key, consumer secret, access token, access token secret and realm) in C#, for GET requests, it works. I'm able to authenticate and get a response.
When I try a POST in C#, I get a 401, 'Unauthorized' with an error message stating that the token was rejected. The same POST request, with the same auth values and URL works in Postman however.
I feel like Postman is doing something to the authentication header in a different way to Restsharp, but that still doesn't explain why GET requests are working with RestSharp
public string ExecuteRequest(string url, int httpMethod, string body = "")
{
var client = new RestClient(url);
client.Authenticator = GetOAuth1Authenticator();
Method method = (Method)httpMethod;
var request = new RestRequest(url, method);
client.AddDefaultHeader("Accept", "*/*");
client.Options.MaxTimeout = -1;
request.AddHeader("Cookie", "NS_ROUTING_VERSION=LAGGING");
request.AddHeader("ContentType", "application/json");
if (string.IsNullOrEmpty(body) == false)
{
request.AddParameter("application/json", body, ParameterType.RequestBody);
}
var response = client.Execute(request);
if (response.IsSuccessful == false)
{
throw new HttpRequestException($"ERROR: {response.ErrorMessage} - RESPONSE CONTENT: {response.Content}");
}
if (response.Content == null)
{
throw new NullReferenceException("API RESPONSE IS NULL");
}
return response.Content;
}
private OAuth1Authenticator GetOAuth1Authenticator()
{
OAuth1Authenticator authenticator = OAuth1Authenticator.ForAccessToken(consumerKey: Credential.consumer_key,
consumerSecret: Credential.consumer_secret,
token: Credential.access_token, tokenSecret: Credential.access_token_secret, signatureMethod: RestSharp.Authenticators.OAuth.OAuthSignatureMethod.HmacSha256);
authenticator.Realm = Credential.accountId;
return authenticator;
}
For anyone who knows SuiteTalk REST API for NetSuite, I'm trying to do a POST request to transform a PO into a VendorBill, using this endpoint:
[netsuite host url]/purchaseOrder/{id}/!transform/vendorBill
try
var client = new RestClient(urlString);
var request = new RestRequest(Method.POST);
btw, check your oauth method, when you are generating the signature you must specify the method you are using ("POST")
I'm attempting to pass username/password from an application to the API to receive a token authorization key. When I attempt to do so, I receive a 400 Bad Request error and I cannot figure out why. Below is the method in question:
public User UserAuthentication(string username, string password)
{
string endpoint = baseURL + "/TOKEN";
// Could be POST maybe
string method = "POST";
Credential jsonObj = new Credential
{
grant_type = "password",
username = username,
password = password
};
string jsonStr = JsonConvert.SerializeObject(jsonObj);
WebClient wc = new WebClient();
//x - www - form - urlencoded
wc.Headers[HttpRequestHeader.ContentType] = "application/x - www - form - urlencoded";
wc.Headers.Add("Access-Control-Allow-Headers", "content-type");
wc.Headers.Add("Access-Control-Allow-Origin", "*");
wc.Headers[HttpRequestHeader.Authorization] = "Bearer <token>";
wc.Headers.Add("Access-Control-Allow-Methods", "POST, PUT, GET, DELETE, OPTIONS");
string header = wc.Headers.ToString();
try
{
string response = wc.UploadString(endpoint, method, jsonStr);
return JsonConvert.DeserializeObject<User>(response);
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
I've messed around altering just about everything in this method in search of a fix.
What I've done:
/TOKEN was /values & /api/values
POST method was GET -- With this, I received a "Cannot send a content-body with this verb-type." error.
ContentType was changed to "application/json"
Access-Control-Allow-Origin had the baseURL
Checked the format of header & body:
Header:
{Content-Type: application/x - www - form - urlencoded
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Origin: *
Authorization: Bearer <token>
Access-Control-Allow-Methods: POST, PUT, GET, DELETE, OPTIONS}
Body:
{"grant_type":"password",
"username":"test#gmail.com",
"password":"password123"}
I obviously have something wrong in my request, I've just run out of ideas to try. I'm not entirely sure if UploadString() is the correct method to be using in this situation, but I couldn't find another method in the WebClient class that would be better. Any help to try and push me in the right direction would be very much appreciated.
So what I think you are trying to do is a form-urlencoded post to a "token" endpoint with a username/password grant. These are typically done like so:
using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri("https://example.com/token"))
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", "username#site.com" },
{ "password", "password12345" }
})
})
{
using (var resp = await _client.SendAsync(request))
{
resp.EnsureSuccessStatusCode();
//await resp.Content.ReadAsAsync<BearerToken>();
// for testing purposes, try this:
var returnData = await resp.Content.ReadAsStringAsync();
Console.WriteLine(returnData);
}
}
You should define this outside all scopes where you need to do Http requests:
private static readonly HttpClient _client = new HttpClient();
So, first off, try to stick with HttpClient. Other patterns such as WebClient are considered legacy.
Next, CORS headers are typically returned from the server when an OPTIONS call is sent to the server. You aren't doing that here, and you should never have to worry about that kind of stuff inside a C# program running from your computer. So you can drop the access-control header stuff.
Form-urlencoded data is not JSON data. It's a different way to format data. If you want to send JSON data, you should use the content-type application/json
Finally, you are trying to add an Authorization header. But that doesn't make much sense as you are trying to authenticate yourself to become authorized. If you send the right username/password, you will receive a bearer token that you can use in an Authorization header for future requests to said service.
Oh and I forgot to add: Whenever you see an error in the [400,499] range (in this case "400 - bad request") it means that you sent something wrong and the server doesn't understand what you are trying to do. For example: a 401 means you sent invalid or missing authorization information. A 400 means your data was probably malformed.
But I like your question... I can see what you were doing and you tried all kinds of different things.
Download a program called Fiddler if you want to see how HTTP works. It's a great tool to debug your HTTP calls.
I'm having some trouble getting a token from a HttpResponseMessage using RestSharp. Here is my API controller code:
public HttpResponseMessage Create(long id)
{
var token = _tokenCreator.CreateToken(id);
var response = new HttpResponseMessage();
response.Headers.Add("Token", token);
response.StatusCode = HttpStatusCode.OK;
return response;
}
This is generating the token like I want, and creating the response pictured below. When I call the method with Postman, this is the body of the response that I receive. This is the same result I get if I look at the Content property of the response in my code that sends the request.
Picture of response body.
There is clearly a header section there, but it's not being recognized by RestSharp in my calling code.
public string CreateToken(long id)
{
var client = new RestClient(apiUrl);
var request = new RestRequest(Method.GET)
{
Resource = "tokencreator/create"
}
request.AddQueryParameter("id", id.ToString());
var response = client.Execute(request);
var headers = response.Headers.ToList();
// Here is what I want to do, but does not return a result
var tokenHeader = headers.Find(x => x.Name == "Token");
if(tokenHeader != null)
{
return tokenHeader.Value.ToString();
}
return "no token";
}
If I loop through and print the response's headers, these are the results:
Transfer-Encoding chunked
X-SourceFiles =?UTF-8?B?QzpccHJvamVjdHNcYXBpXEVudGl0bGVtZW50QWNjZXNzXHRva2VuY3JlYXRvclxjcmVhdGU=?=
Content-Type application/json; charset=utf-8
Date Mon, 03 Apr 2017 16:00:18 GMT
Server Kestrel
X-Powered-By ASP.NET
The "Token" header I added in the controller to the response is not there. Is there a simple way to access the "header" section that is appearing in the body of the response?
Edit: Attaching a picture of the "Headers" section of the response from Postman. There is no "Token" header. Any idea why the response.Headers.Add("Token", token) method does not add the header, or am I misunderstanding headers completely?
Picture of headers in Postman response.
So I should have specified that this was a .NET Core application. To fix the problem, I had to go to the Startup.cs file and add the following to the ConfigureServices method:
Change services.AddMvc(); to services.AddMvc().AddWebApiConventions();
You will need the NuGet package Microsoft.AspNetCore.Mvc.WebApiCompatShim, which allows compatibility with the old Web Api 2 way.
I'm having a hard time trying to consume a REST service from Cielo (credit/debit card gateway company). If I use postman it works:
POST /1/sales/ HTTP/1.1
Host: apisandbox.cieloecommerce.cielo.com.br
MerchantKey: my_key
Content-Type: application/json
MerchantId: merc_id
Cache-Control: no-cache
Postman-Token: 6643cc5a-173a-f5db-8924-85ea8b7bbb55
{"MerchantId":"00000000-0000-0000-0000-000000000000","MerchantKey":null,"RequestId":"00000000-0000-0000-0000-000000000000","MerchantOrderId":"1223","Customer":{"Name":"Emerson Fitchy"},"Payment":{"PaymentId":"00000000-0000-0000-0000-000000000000","Type":"CreditCard","Amount":15700,"Installments":1,"Provider":null,"ProofOfSale":null,"Tid":null,"AuthorizationCode":null,"SoftDescriptor":null,"ECI":null,"Status":0,"ReturnCode":null,"ReturnMessage":null,"CreditCard":{"CardNumber":"0000000000000001","Holder":"Emerson Fitchy Santis","ExpirationDate":"12/2022","SecurityCode":"154","Brand":"Visa"}}}
And this is the C# code (at the moment, I tried also with RestSharp and HttpClient with the same results):
var webrequest = (HttpWebRequest)WebRequest.Create(Constants.Cielo.GetSalesUrl());
webrequest.ContentType = "application/json";
webrequest.Method = "POST";
webrequest.Headers.Add("MerchantId", Constants.Cielo.Sandbox.MerchantId.ToString());
webrequest.Headers.Add("MerchantKey", Constants.Cielo.Sandbox.MerchantKey);
using (var streamWriter = new StreamWriter(webrequest.GetRequestStream()))
{
var json = JsonConvert.SerializeObject(sale);
streamWriter.Write(json);
}
var httpResponse = (HttpWebResponse)webrequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
var x = result;
}
Any ideas on what am I doing wrong? I tried using Fiddler 4 but it's not picking up this request (I don't know why) and WireShark picks it up, but doesn't show the information the way Fiddler does (Maybe it's because I'm using https?).
Any ideas?
Thanks!
EDIT
Response/Exception
I just had the same issue where I suddenly was unable to make calls to a specific website from a Windows Server where I was running a console application on .NET 4.7.2. I was able to make the call in Postman from the same machine, but started getting the An existing connection was forcibly closed by the remote host.
It turns out that the sever had TLS 1.2 disabled. I was able to resolve it following the steps in this answer. I still am not sure if this setting was changed on the server or if the website started enforcing it, but it resolved the issue for me.
Try this client and see if it you get a different result:
var client = new HttpClient();
using (HttpRequestMessage request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(Constants.Cielo.GetSalesUrl(), UriKind.Absolute);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var requestContent = JsonConvert.SerializeObject(sale);
request.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
request.Headers.Add("MerchantId", Constants.Cielo.Sandbox.MerchantId.ToString());
request.Headers.Add("MerchantKey", Constants.Cielo.Sandbox.MerchantKey);
using (HttpResponseMessage response = await client.SendAsync(request))
{
if (response.IsSuccessStatusCode)
{
if (response.Content != null)
{
var rawJson = await response.Content.ReadAsStringAsync();
// do stuff - map to type, etc.
}
}
return something;
}
}
I created RESTful webservice (WCF) where I check credentials on each request. One of my clients is Android app and everything seems to be great on server side. I get request and if it's got proper header - I process it, etc..
Now I created client app that uses this service. This is how I do GET:
// Create the web request
var request = WebRequest.Create(Context.ServiceURL + uri) as HttpWebRequest;
if (request != null)
{
request.ContentType = "application/json";
// Add authentication to request
request.Credentials = new NetworkCredential(Context.UserName, Context.Password);
// Get response
using (var response = request.GetResponse() as HttpWebResponse)
{
// Get the response stream
if (response != null)
{
var reader = new StreamReader(response.GetResponseStream());
// Console application output
var s = reader.ReadToEnd();
var serializer = new JavaScriptSerializer();
var returnValue = (T)serializer.Deserialize(s, typeof(T));
return returnValue;
}
}
}
So, this code get's my resource and deserializes it. As you see - I'm passing credentials in my call.
Then when debugging on server-side I noticed that I get 2 requests every time - one without authentication header and then server sends back response and second request comes bach with credentials. I think it's bad for my server - I'd rather don't make any roundtrips. How should I change client so it doesn't happen? See screenshot of Fiddler
EDIT:
This is JAVA code I use from Android - it doesn't do double-call:
MyHttpResponse response = new MyHttpResponse();
HttpClient client = mMyApplication.getHttpClient();
try
{
HttpGet request = new HttpGet(serviceURL + url);
request.setHeader(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
request.addHeader("Authorization", "Basic " + Preferences.getAuthorizationTicket(mContext));
ResponseHandler<String> handler = new BasicResponseHandler();
response.Body = client.execute(request, handler);
response.Code = HttpURLConnection.HTTP_OK;
response.Message = "OK";
}
catch (HttpResponseException e)
{
response.Code = e.getStatusCode();
response.Message = e.getMessage();
LogData.InsertError(mContext, e);
}
The initial request doesn't ever specify the basic header for authentication. Additionally, since a realm is specified, you have to get that from the server. So you have to ask once: "hey, I need this stuff" and the server goes "who are you? the realm of answering is 'secure area'." (because realm means something here) Just because you added it here:
request.Credentials = new NetworkCredential(Context.UserName, Context.Password);
doesn't mean that it's going to be for sure attached everytime to the request.
Then you respond with the username/password (in this case you're doing BASIC so it's base64 encoded as name:password) and the server decodes it and says "ok, you're all clear, here's your data".
This is going to happen on a regular basis, and there's not a lot you can do about it. I would suggest that you also turn on HTTPS since the authentication is happening in plain text over the internet. (actually what you show seems to be over the intranet, but if you do go over the internet make it https).
Here's a link to Wikipedia that might help you further: http://en.wikipedia.org/wiki/Basic_access_authentication
Ok, I got it. I manually set HttpHeader instead of using request.Credentials
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(Context.UserName + ":" + Context.Password)));
Now I see only single requests as expected..
As an option you can use PreAuthenticate property of HttpClientHandler. This would require a couple of lines more
var client = new HttpClient(new HttpClientHandler
{
Credentials = yourCredentials,
PreAuthenticate = true
});
With using this approach, only the first request is sent without credentials, but all the rest requests are OK.