I've seen so many implementations of sending an http post, and admittedly I don't fully understand the underlying details to know what's required.
What is the succinct/correct/canonical code to send an HTTP POST in C# .NET 3.5?
I want a generic method like
public string SendPost(string url, string data)
that can be added to a library and always used for posting data and will return the server response.
I believe that the simple version of this would be
var client = new WebClient();
return client.UploadString(url, data);
The System.Net.WebClient class has other useful methods that let you download or upload strings or a file, or bytes.
Unfortunately there are (quite often) situations where you have to do more work. The above for example doesn't take care of situations where you need to authenticate against a proxy server (although it will use the default proxy configuration for IE).
Also the WebClient doesn't support uploading of multiple files or setting (some specific) headers and sometimes you will have to go deeper and use the
System.Web.HttpWebRequest and System.Net.HttpWebResponse instead.
As others have said, WebClient.UploadString (or UploadData) is the way to go.
However the built-in WebClient has a major drawback : you have almost no control over the WebRequest that is used behind the scene (cookies, authentication, custom headers...). A simple way to solve that issue is to create your custom WebClient and override the GetWebRequest method. You can then customize the request before it is sent (you can do the same for the response by overridingGetWebResponse). Here is an example of a cookie-aware WebClient. It's so simple it makes me wonder why the built-in WebClient doesn't handle it out-of-the-box...
Compare:
// create a client object
using(System.Net.WebClient client = new System.Net.WebClient()) {
// performs an HTTP POST
client.UploadString(url, xml);
}
to
string HttpPost (string uri, string parameters)
{
// parameters: name1=value1&name2=value2
WebRequest webRequest = WebRequest.Create (uri);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes (parameters);
Stream os = null;
try
{ // send the Post
webRequest.ContentLength = bytes.Length; //Count bytes to send
os = webRequest.GetRequestStream();
os.Write (bytes, 0, bytes.Length); //Send it
}
catch (WebException ex)
{
MessageBox.Show ( ex.Message, "HttpPost: Request error",
MessageBoxButtons.OK, MessageBoxIcon.Error );
}
finally
{
if (os != null)
{
os.Close();
}
}
try
{ // get the response
WebResponse webResponse = webRequest.GetResponse();
if (webResponse == null)
{ return null; }
StreamReader sr = new StreamReader (webResponse.GetResponseStream());
return sr.ReadToEnd ().Trim ();
}
catch (WebException ex)
{
MessageBox.Show ( ex.Message, "HttpPost: Response error",
MessageBoxButtons.OK, MessageBoxIcon.Error );
}
return null;
} // end HttpPost
Why are people using/writing the latter?
Related
I am trying to use an API that helps detect bad IP, with C# code.
Here is the documentation.
How to call the API?
API requests are sent in a specific form. The API key is sent directly to the URL, as is the IP address from which you want to retrieve the information. This is the form of the URL called.
https://api.ipwarner.com/API-KEY/IP
According to this, I wrote a function:
private static string Get(string uri)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream ?? throw new InvalidOperationException());
return reader.ReadToEnd();
}
And called it with:
string myresult = Get("https://api.ipwarner.com/myapikey/myip");
However, it got stuck at HttpWebResponse. There was no response at all.
(I confirm my API key is available and the input IP is right)
How's that wrong?
Please set time out and Try. Now, You will get the error message. Work on the error.
private static string Get(string uri)
{
string returnStr = trsing.Empty;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Timeout=10;
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream ?? throw new
InvalidOperationException());
returnStr = reader.ReadToEnd();
}
catch( Exception ex)
{
Debug.Writeline( ex.ToString());
}
return returnStr ;
}
It's not your problem i have checked it right now and the api is down
you can also check
https://www.isitdownrightnow.com/api.ipwarner.com.html
i signed up the site and tested it my self the api site is down
contact there support
I am using the code below to post to a web site but I get a 411 error, (
The remote server returned an error: (411) Length Required).
This is the function I am using, I just removed the exception handling. I get a WebException been thrown.
private static async Task<string> PostAsync(string url, string queryString, Dictionary<string, string> headers)
{
var webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
if (headers != null)
{
foreach (var header in headers)
{
webRequest.Headers.Add(header.Key, header.Value);
}
}
if (!string.IsNullOrEmpty(queryString))
{
queryString = BuildQueryString(query);
using (var writer = new StreamWriter(webRequest.GetRequestStream()))
{
writer.Write(queryString);
}
}
//Make the request
try
{
using (
var webResponse = await Task<WebResponse>.Factory.FromAsync(webRequest.BeginGetResponse, webRequest.EndGetResponse, webRequest).ConfigureAwait(false))
{
using (var str = webResponse.GetResponseStream())
{
if (str != null)
{
using (var sr = new StreamReader(str))
{
return sr.ReadToEnd();
}
}
return null;
}
}
}
catch (WebException wex)
{
// handle webexception
}
catch (Exception ex)
{
// handle webexception
}
}
I saw on some site that adding
webRequest.ContentLength = 0;
Would work, but in some cases I get errors that the length is wrong, (so it must be something other than 0).
So my questions are, how do I set the content length properly?
And, am I sending my post requests properly? Is there another way?
Not sure if it matters, but I am using .NET 4.6, (but I can use 4.6.1 if needed).
The length is the length of the data you're writing to the request stream. So in this bit, set the ContentLength:
using (var writer = new StreamWriter(webRequest.GetRequestStream()))
{
writer.Write(queryString);
request.ContentLength = queryString.Length;
}
Note that you might have trouble if queryString contains unicode characters. The ContentLength indicates the number of bytes, but string.Length indicates the number of characters. Since some unicode characters take up more than one byte, there can possibly be a mismatch. There are ways to compensate if need be.
As an alternative, you can use HttpClient instead. I haven't had to set the ContentLength manually when using it.
If you are using POST and you need to use POST then you need this where data represents some data you are posting be it JSON, XML or whatever:
request.ContentLength = data.Length;
Having said that, I think you are probably ok with using GET instead since you are not sending nothing in the body of the request. But in some cases you have no choice but to use POST.
I am playing around with an app using HttpWebRequest to dialog with a web server.
I followed standard instructions I found on the web to build my request function that I tried to make as generic as possible (I try to get a unique method regardless of the method: PUT, POST, DELETE, REPORT, ...)
When I submit a "REPORT" request, I get two access logs on my server:
1) I get response 401 after following line is launched in debugger
reqStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length);
2) I get response 207 (multi-get, which is what I expect) after passing the line calling Request.GetResponse();
Actually, it seems to be the Request.GetRequestStream() line that is querying the server the first time, but the request is only committed once passing the reqStream.Write(...) line...
Same for PUT and DELETE, the Request.GetRequestStream() again generates a 401 access log on my server whereas the Request.GetResponse(); returns code 204.
I don't understand why for a unique request I have two server access logs, especially one that seems to be doing nothing as it always returns code 401... Could anybody explain what is going on? Is it a flaw in my code or a bad design due to my attempt to get a generic code for multiple methods?
Here is my full code:
public static HttpWebResponse getHttpWebRequest(string url, string usrname, string pwd, string method, string contentType,
string[] headers, string body) {
// Variables.
HttpWebRequest Request;
HttpWebResponse Response;
//
string strSrcURI = url.Trim();
string strBody = body.Trim();
try {
// Create the HttpWebRequest object.
Request = (HttpWebRequest)HttpWebRequest.Create(strSrcURI);
// Add the network credentials to the request.
Request.Credentials = new NetworkCredential(usrname.Trim(), pwd);
// Specify the method.
Request.Method = method.Trim();
// request headers
foreach (string s in headers) {
Request.Headers.Add(s);
}
// Set the content type header.
Request.ContentType = contentType.Trim();
// set the body of the request...
Request.ContentLength = body.Length;
using (Stream reqStream = Request.GetRequestStream()) {
// Write the string to the destination as a text file.
reqStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length);
reqStream.Close();
}
// Send the method request and get the response from the server.
Response = (HttpWebResponse)Request.GetResponse();
// return the response to be handled by calling method...
return Response;
}
catch (Exception e) {
throw new Exception("Web API error: " + e.Message, e);
}
i wrote a simple C# function to retrieve trade history from MtGox with following API call:
https://data.mtgox.com/api/1/BTCUSD/trades?since=<trade_id>
documented here: https://en.bitcoin.it/wiki/MtGox/API/HTTP/v1#Multi_currency_trades
here's the function:
string GetTradesOnline(Int64 tid)
{
Thread.Sleep(30000);
// communicate
string url = "https://data.mtgox.com/api/1/BTCUSD/trades?since=" + tid.ToString();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string json = reader.ReadToEnd();
reader.Close();
reader.Dispose();
response.Close();
return json;
}
i'm starting at tid=0 (trade id) to get the data (from the very beginning). for each request, i receive a response containing 1000 trade details. i always send the trade id from the previous response for the next request. it works fine for exactly 4 requests & responses. but after that, the following line throws a "System.Net.WebException", saying that "The operation has timed out":
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
here are the facts:
catching the exception and retying keeps causing the same exception
the default HttpWebRequest .Timeout and .ReadWriteTimeout are already high enough (over a minute)
changing HttpWebRequest.KeepAlive to false didn't solve anything either
it seems to always work in the browser even while the function is failing
it has no problems retrieveing the response from https://www.google.com
the amount of successful responses before the exceptions varies from day to day (but browser always works)
starting at the trade id that failed last time causes the exception immediately
calling this function from the main thread instead still caused the exception
running it on a different machine didn't work
running it from a different IP didn't work
increasing Thread.Sleep inbetween requests does not help
any ideas of what could be wrong?
I had the very same issue.
For me the fix was as simple as wrapping the HttpWebResponse code in using block.
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
// Do your processings here....
}
Details: This issue usually happens when several requests are made to the same host, and WebResponse is not disposed properly. That is where using block will properly dispose the WebResponse object properly and thus solving the issue.
There are two kind of timeouts. Client timeout and server timeout. Have you tried doing something like this:
request.Timeout = Timeout.Infinite;
request.KeepAlive = true;
Try something like this...
I just had similar troubles calling a REST Service on a LINUX Server thru ssl. After trying many different configuration scenarios I found out that I had to send a UserAgent in the http head.
Here is my final method for calling the REST API.
private static string RunWebRequest(string url, string json)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
// Header
request.ContentType = "application/json";
request.Method = "POST";
request.AllowAutoRedirect = false;
request.KeepAlive = false;
request.Timeout = 30000;
request.ReadWriteTimeout = 30000;
request.UserAgent = "test.net";
request.Accept = "application/json";
request.ProtocolVersion = HttpVersion.Version11;
request.Headers.Add("Accept-Language","de_DE");
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
byte[] bytes = Encoding.UTF8.GetBytes(json);
request.ContentLength = bytes.Length;
using (var writer = request.GetRequestStream())
{
writer.Write(bytes, 0, bytes.Length);
writer.Flush();
writer.Close();
}
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var jsonReturn = streamReader.ReadToEnd();
return jsonReturn;
}
}
This is not a solution, but just an alternative:
These days i almost only use WebClient instead of HttpWebRequest. Especially WebClient.UploadString for POST and PUT and WebClient.DownloadString. These simply take and return strings. This way i don't have to deal with streams objects, except when i get a WebException. i can also set the content type with WebClient.Headers["Content-type"] if necessary. The using statement also makes life easier by calling Dispose for me.
Rarely for performance, i set System.Net.ServicePointManager.DefaultConnectionLimit high and instead use HttpClient with it's Async methods for simultaneous calls.
This is how i would do it now
string GetTradesOnline(Int64 tid)
{
using (var wc = new WebClient())
{
return wc.DownloadString("https://data.mtgox.com/api/1/BTCUSD/trades?since=" + tid.ToString());
}
}
2 more POST examples
// POST
string SubmitData(string data)
{
string response;
using (var wc = new WebClient())
{
wc.Headers["Content-type"] = "text/plain";
response = wc.UploadString("https://data.mtgox.com/api/1/BTCUSD/trades", "POST", data);
}
return response;
}
// POST: easily url encode multiple parameters
string SubmitForm(string project, string subject, string sender, string message)
{
// url encoded query
NameValueCollection query = HttpUtility.ParseQueryString(string.Empty);
query.Add("project", project);
query.Add("subject", subject);
// url encoded data
NameValueCollection data = HttpUtility.ParseQueryString(string.Empty);
data.Add("sender", sender);
data.Add("message", message);
string response;
using (var wc = new WebClient())
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
response = wc.UploadString( "https://data.mtgox.com/api/1/BTCUSD/trades?"+query.ToString()
, WebRequestMethods.Http.Post
, data.ToString()
);
}
return response;
}
Error handling
try
{
Console.WriteLine(GetTradesOnline(0));
string data = File.ReadAllText(#"C:\mydata.txt");
Console.WriteLine(SubmitData(data));
Console.WriteLine(SubmitForm("The Big Project", "Progress", "John Smith", "almost done"));
}
catch (WebException ex)
{
string msg;
if (ex.Response != null)
{
// read response HTTP body
using (var sr = new StreamReader(ex.Response.GetResponseStream())) msg = sr.ReadToEnd();
}
else
{
msg = ex.Message;
}
Log(msg);
}
For what it's worth, I was experiencing the same issues with timeouts every time I used it, even though calls went through to the server I was calling. The problem in my case was that I had Expect set to application/json, when that wasn't what the server was returning.
I previously had a small VBScript that would test if a specific website was accessible by sending a GET request. The script itself was extremely simple and did everything I needed:
Function GETRequest(URL) 'Sends a GET http request to a specific URL
Dim objHttpRequest
Set objHttpRequest = CreateObject("MSXML2.XMLHTTP.3.0")
objHttpRequest.Open "GET", URL, False
On Error Resume Next 'Error checking in case access is denied
objHttpRequest.Send
GETRequest = objHttpRequest.Status
End Function
I now want to include this sort of functionality in an expanded C# application. However I've been unable to get the same results my previous script provided.
Using code similar to what I've posted below sort of gets me a proper result, but fails to run if my network connection has failed.
public static void GETRequest()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://url");
request.Method = "GET";
HttpStatusCode status;
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
status = response.StatusCode;
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(status);
}
catch (WebException e)
{
status = ((HttpWebResponse)e.Response).StatusCode;
Console.WriteLine(status);
}
}
But as I said, I need to know if the site is accessible, not matter the reason: the portal could be down, or the problem might reside on the side of the PC that's trying to access it. Either way: I don't care.
When I used MSXML2.XMLHTTP.3.0 in the script I was able to get values ranging from 12000 to 12156 if I was having network problems. I would like to have the same functionality in my C# app, that way I could at least write a minimum of information to a log and let the computer act accordingly. Any ideas?
A direct translation of your code would be something like this:
static void GetStatusCode(string url)
{
dynamic httpRequest = Activator.CreateInstance(Type.GetTypeFromProgID("MSXML2.XMLHTTP.3.0"));
httpRequest.Open("GET", url, false);
try { httpRequest.Send(); }
catch { }
finally { Console.WriteLine(httpRequest.Status); }
}
It's as small and simple as your VBScript script, and uses the same COM object to send the request.
This code happily gives me error code like 12029 ERROR_WINHTTP_CANNOT_CONNECT or 12007 ERROR_WINHTTP_NAME_NOT_RESOLVED etc.
If the code is failing only when you don't have an available network connection, you can use GetIsNetworkAvailable() before executing your code. This method will return a boolean indicating if a network connection is available or not. If it returns false, you could execute an early return / notify the user, and if not, continue.
System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()
using the code you provided above:
public static void GETRequest()
{
if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
return; //or alert the user there is no connection
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://url");
request.Method = "GET";
HttpStatusCode status;
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.GetResponse();
status = response.StatusCode;
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(status);
}
catch (WebException e)
{
status = ((HttpWebResponse)e.Response).StatusCode;
Console.WriteLine(status);
}
}
This should work for you, i've used it many times before, cut it down a bit for your needs: -
private static string GetStatusCode(string url)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = WebRequestMethods.Http.Get;
req.ProtocolVersion = HttpVersion.Version11;
req.UserAgent = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)";
try
{
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
StringBuilder sb = new StringBuilder();
foreach (string header in response.Headers)
{
sb.AppendLine(string.Format("{0}: {1}", header, response.GetResponseHeader(header)));
}
return string.Format("Response Status Code: {0}\nServer:{1}\nProtocol: {2}\nRequest Method: {3}\n\n***Headers***\n\n{4}", response.StatusCode,response.Server, response.ProtocolVersion, response.Method, sb);
}
catch (Exception e)
{
return string.Format("Error: {0}", e.ToString());
}
}
Feel free to ignore the section that gets the headers