C# - HttpWebRequest - POST - c#

I am trying to make an Http POST to an Apache web server.
I am finding that setting ContentLength seems to be required for the request to work.
I would rather create an XmlWriter directly from GetRequestStream() and set SendChunked to true, but the request hangs indefinitely when doing so.
Here is how my request is created:
private HttpWebRequest MakeRequest(string url, string method)
{
HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
request.Method = method;
request.Timeout = Timeout; //Property in my class, assume it's 10000
request.ContentType = "text/xml"; //I am only writing xml with XmlWriter
if (method != WebRequestMethods.Http.Get)
{
request.SendChunked = true;
}
return request;
}
How can I make SendChunked work so I do not have to set ContentLength? I do not see a reason to store the XmlWriter's string somewhere before sending it to the server.
EDIT: Here is my code causing the problem:
using (Stream stream = webRequest.GetRequestStream())
{
using (XmlWriter writer = XmlWriter.Create(stream, XmlTags.Settings))
{
Generator.WriteXml<TRequest>(request, writer);
}
}
Before I did not have a using on the Stream object returned from GetRequestStream(), I assumed XmlWriter closed the stream when disposed, but this is not the case.
One of the answers below, let me to this. I'll mark them as the answer.
As far as HttpWebRequest is concerned, my original code works just fine.

This should work the way you have it written. Can we see the code that actually does the uploading? Are you remembering to close the stream?

Looking at the example at http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.sendchunked.aspx they still set a content length. Really the bottom line is that if you are sending data you need to tell the receiver how much data you will be sending. Why don't you know how much data you are sending before you send the request?
ContentLength:
Property Value
Type: System..::.Int64
The number of bytes of data to send to the Internet resource. The default is -1, which indicates the property has not been set and that there is no request data to send.
Edit for Aaron (I was wrong):
HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://test") as HttpWebRequest;
httpWebRequest.SendChunked = true;
MessageBox.Show("|" + httpWebRequest.TransferEncoding + "|");
From System.Net.HttpWebRequest.SerializeHeaders():
if (this.HttpWriteMode == HttpWriteMode.Chunked)
{
this._HttpRequestHeaders.AddInternal("Transfer-Encoding", "chunked");
}
else if (this.ContentLength >= 0L)
{
this._HttpRequestHeaders.ChangeInternal("Content-Length", this._ContentLength.ToString(NumberFormatInfo.InvariantInfo));
}

I prefer to use a generic method to comply this kind of stuff. Take a look at the XML sender request below. It will serialize your XML and then send it with the appropriate ContentType :
public bool SendXMLRequest<T>(T entity, string url, string method)
{
HttpWebResponse response = null;
bool received = false;
try
{
var request = (HttpWebRequest)WebRequest.Create(url);
var credCache = new CredentialCache();
var netCred = new NetworkCredential(YOUR_LOGIN_HERE, YOUR_PASSWORD_HERE, YOUR_OPTIONAL_DOMAIN_HERE);
credCache.Add(new Uri(url), "Basic", netCred);
request.Credentials = credCache;
request.Method = method;
request.ContentType = "application/xml";
request.SendChunked = "GET" != method.ToUpper();
using (var writer = new StreamWriter(request.GetRequestStream()))
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StringWriter textWriter = new Utf8StringWriter())
{
serializer.Serialize(textWriter, entity);
var xml = textWriter.ToString();
writer.Write(xml);
}
}
response = (HttpWebResponse)request.GetResponse();
received = response.StatusCode == HttpStatusCode.OK; //YOu can change the status code to check. May be created, etc...
}
catch (Exception ex) { }
finally
{
if(response != null)
response.Close();
}
return received;
}

Related

Trying to upload with a PUT request

I am trying to change the price of an article using the websites API
the documentation for it is at https://www.mkmapi.eu/ws/documentation/API_1.1:Stock
When run the class I get an error 417 Expectation Failed, which is described from the documentation as:
Typically you get a 417 Expectation Failed HTTP status code, when your request has an XML body without the corresponding header and/or the body not sent as text, but its byte representation. Another possible reason for a 417 is, when you send body data with more than 1.024 bytes without adding the header Expect: to your request.
Any help would be appreciated. I should also say that the authentication is not the problem I can download my article prices.
public void UpdateMarketPrice(string MarketID, string NewPrice)
{
// https://www.mkmapi.eu/ws/documentation/API_1.1:Stock
String finalResult;
String method = "PUT";
String url = "https://www.mkmapi.eu/ws/v1.1/stock";
HttpWebRequest request = WebRequest.CreateHttp(url) as HttpWebRequest;
OAuthHeader header = new OAuthHeader();
request.Headers.Add(HttpRequestHeader.Authorization, header.getAuthorizationHeader(method, url));
request.Method = method;
request.ContentType = "text/xml; encoding='utf-8'";
XElement xmlDoc =
new XElement("request",
new XElement("article",
new XElement("idArticle", MarketID),
new XElement("idLanguage", 1),
new XElement("comments", "Edited through the API"),
new XElement("count", 7),
new XElement("price", 11),
new XElement("condition", "NM"),
new XElement("isFoil", false),
new XElement("isSigned", false),
new XElement("isPlayset", false)
)
);
String finalXML = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + xmlDoc.ToString();
MessageBox.Show(finalXML);
byte[] bytes = Encoding.ASCII.GetBytes(finalXML);
request.ContentLength = bytes.Length;
using (Stream putStream = request.GetRequestStream())
{
putStream.Write(bytes, 0, bytes.Length);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
finalResult = reader.ReadToEnd();
}
MessageBox.Show(finalResult);
}
I have read that HttpWebRequest adds an "expect 100 continue" header to requests unless you turn it off. There are servers that possibly don't support this header. And will produce this 417 Expectation Failed message.
You could try setting it to false:
System.Net.ServicePointManager.Expect100Continue = false;
So the header isn't sent.
I've seen this suggested sollution to other similar questions also.
Maybe use StreamWriter ?
using (Stream putStream = request.GetRequestStream())
{
using (var writeStream = new StreamWriter(putStream, Encoding.ASCII))
{
writeStream.Write(finalXML);
}
request.ContentLength = putStream.Length; // I am not sure about that
}

using OpenTSDB HTTP api in .NET : 400 Bad Request

I'm trying to use .net to put datapoints in OpenTSDB, using the HTTP /api/put API.
I've tried with httpclient, webRequest and HttpWebRequest. The outcome is always 400 - bad request: chunked request not supported.
I've tried my payload with an api tester (DHC) and works well.
I've tried to send a very small payload (even plain wrong, like "x") but the reply is always the same.
Here's one of my code instances:
public async static Task PutAsync(DataPoint dataPoint)
{
try
{
HttpWebRequest http = (HttpWebRequest)WebRequest.Create("http://127.0.0.1:4242/api/put");
http.SendChunked = false;
http.Method = "POST";
http.ContentType = "application/json";
Encoding encoder = Encoding.UTF8;
byte[] data = encoder.GetBytes( dataPoint.ToJson() + Environment.NewLine);
http.Method = "POST";
http.ContentType = "application/json; charset=utf-8";
http.ContentLength = data.Length;
using (Stream stream = http.GetRequestStream())
{
stream.Write(data, 0, data.Length);
stream.Close();
}
WebResponse response = http.GetResponse();
var streamOutput = response.GetResponseStream();
StreamReader sr = new StreamReader(streamOutput);
string content = sr.ReadToEnd();
Console.WriteLine(content);
}
catch (WebException exc)
{
StreamReader reader = new StreamReader(exc.Response.GetResponseStream());
var content = reader.ReadToEnd();
}
return ;
}
where I explicitly set to false the SendChunked property.
note that other requests, like:
public static async Task<bool> Connect(Uri uri)
{
HttpWebRequest http = (HttpWebRequest)WebRequest.Create("http://127.0.0.1:4242/api/version");
http.SendChunked = false;
http.Method = "GET";
// http.Headers.Clear();
//http.Headers.Add("Content-Type", "application/json");
http.ContentType = "application/json";
WebResponse response = http.GetResponse();
var stream = response.GetResponseStream();
StreamReader sr = new StreamReader(stream);
string content = sr.ReadToEnd();
Console.WriteLine(content);
return true;
}
work flawlessly.
I am sure I am doing something really wrong.
I'd like to to reimplement HTTP in Sockets from scratch.
I've found a solution I'd like to share here.
I've used wireshark to sniff my packets, and I've found that this header is added:
Expect: 100-continue\r\n
(see 8.2.3 of https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
This is the culprit. I've read the post http://haacked.com/archive/2004/05/15/http-web-request-expect-100-continue.aspx/ by Phil Haack, and found that HttpWebRequest puts that header by default, unless you tell it to stop. In this article I've found that using ServicePointManager I can do just this.
Putting the following code on top of my method, when declaring the http object, makes it work very well, and solves my issue:
var uri = new Uri("http://127.0.0.1:4242/api/put");
var spm = ServicePointManager.FindServicePoint(uri);
spm.Expect100Continue = false;
HttpWebRequest http = (HttpWebRequest)WebRequest.Create(uri);
http.SendChunked = false;

Consuming REST service with C# code

I am using the following code to get the json result from the service. It works fine for get methods. But when the method type is POST the request address changes to the previous address.
ie;
on the first call to this method the request.address=XXXXX.com:1234/xxx/oldv1.json (method type is get)
and it returns a json string from which I extract another address:XXXXX.com:1234/xxx/newv1.json
and now I call the makerequest method with this endpoint and method type POST, contenttype="application/x-www-form-urlencoded".
When I put breakpint at using (var response = (HttpWebResponse)request.GetResponse()) and checked the request.address value, it was XXXXX.com:1234/xxx/newv1.json
But after that line is executed, the address changes to XXXXX.com:1234/xxx/oldv1.json and the function returns the same response I got with the first Endpoint(XXXXX.com:1234/xxx/oldv1.json).
Can anybody tell what I am doing wrong here?
Is there any better method to consume the service with POST method?
public string MakeRequest(string EndPoint,string Method, string contentType)
{
var request = (HttpWebRequest)WebRequest.Create(EndPoint);
request.Method = Method;
request.ContentLength = 0;
request.ContentType =contentType;
if ( Method == HttpVerb.POST)
{
var encoding = new UTF8Encoding();
var bytes = Encoding.GetEncoding("iso-8859-1").GetBytes("username=123&password=123");
request.ContentLength = bytes.Length;
using (var writeStream = request.GetRequestStream())
{
writeStream.Write(bytes, 0, bytes.Length);
}
}
using (var response = (HttpWebResponse)request.GetResponse())// request.address changes at this line on "POST" method types
{
var responseValue = string.Empty;
if (response.StatusCode != HttpStatusCode.OK)
{
var message = String.Format("Request failed. Received HTTP {0}", response.StatusCode);
throw new ApplicationException(message);
}
// grab the response
using (var responseStream = response.GetResponseStream())
{
if (responseStream != null)
using (var reader = new StreamReader(responseStream))
{
responseValue = reader.ReadToEnd();
}
}
return responseValue;
}
EDIT: Yesterday I asked THIS Question about consuming the service at client side and many suggested it needs to be done at server side as the other domain might not allow accessing the json result at client side.
The issue was about cookies. As I forgot to set the cookies, the request was getting redirected. I had to set cookie container by using
request.CookieContainer = new CookieContainer();

Google Translate V2 cannot hanlde large text translations from C#

I've implemented C# code using the Google Translation V2 api with the GET Method.
It successfully translates small texts but when increasing the text length and it takes 1,800 characters long ( including URI parameters ) I'm getting the "URI too large" error.
Ok, I burned down all the paths and investigated the issue across multiple pages posted on Internet. All of them clearly says the GET method should be overriden to simulate a POST method ( which is meant to provide support to 5,000 character URIs ) but there is no way to find out a code example to of it.
Does anyone has any example or can provide some information?
[EDIT] Here is the code I'm using:
String apiUrl = "https://www.googleapis.com/language/translate/v2?key={0}&source={1}&target={2}&q={3}";
String url = String.Format(apiUrl, Constants.apiKey, sourceLanguage, targetLanguage, text);
Stream outputStream = null;
byte[] bytes = Encoding.ASCII.GetBytes(url);
// create the http web request
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.KeepAlive = true;
webRequest.Method = "POST";
// Overrride the GET method as documented on Google's docu.
webRequest.Headers.Add("X-HTTP-Method-Override: GET");
webRequest.ContentType = "application/x-www-form-urlencoded";
// send POST
try
{
webRequest.ContentLength = bytes.Length;
outputStream = webRequest.GetRequestStream();
outputStream.Write(bytes, 0, bytes.Length);
outputStream.Close();
}
catch (HttpException e)
{
/*...*/
}
try
{
// get the response
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
if (webResponse.StatusCode == HttpStatusCode.OK && webRequest != null)
{
// read response stream
using (StreamReader sr = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8))
{
string lista = sr.ReadToEnd();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(TranslationRootObject));
MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(lista));
TranslationRootObject tRootObject = (TranslationRootObject)serializer.ReadObject(stream);
string previousTranslation = string.Empty;
//deserialize
for (int i = 0; i < tRootObject.Data.Detections.Count; i++)
{
string translatedText = tRootObject.Data.Detections[i].TranslatedText.ToString();
if (i == 0)
{
text = translatedText;
}
else
{
if (!text.Contains(translatedText))
{
text = text + " " + translatedText;
}
}
}
return text;
}
}
}
catch (HttpException e)
{
/*...*/
}
return text;
}
Apparently using WebClient won't work as you cannot alter the headers as needed, per the documentation:
Note: You can also use POST to invoke the API if you want to send more data in a single request. The q parameter in the POST body must be less than 5K characters. To use POST, you must use the X-HTTP-Method-Override header to tell the Translate API to treat the request as a GET (use X-HTTP-Method-Override: GET).
You can use WebRequest, but you'll need to add the X-HTTP-Method-Override header:
var request = WebRequest.Create (uri);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Headers.Add("X-HTTP-Method-Override", "GET");
var body = new StringBuilder();
body.Append("key=SECRET");
body.AppendFormat("&source={0}", HttpUtility.UrlEncode(source));
body.AppendFormat("&target={0}", HttpUtility.UrlEncode(target));
//--
body.AppendFormat("&q={0}", HttpUtility.UrlEncode(text));
var bytes = Encoding.ASCII.GetBytes(body.ToString());
if (bytes.Length > 5120) throw new ArgumentOutOfRangeException("text");
request.ContentLength = bytes.Length;
using (var output = request.GetRequestStream())
{
output.Write(bytes, 0, bytes.Length);
}
The accepted answer appears to be out of date. You can now use the WebClient (.net 4.5) successfully to POST to the google translate API making sure to set the X-HTTP-Method-Override header.
Here is some code to show you how.
using (var webClient = new WebClient())
{
webClient.Headers.Add("X-HTTP-Method-Override", "GET");
var data = new NameValueCollection()
{
{ "key", GoogleTranslateApiKey },
{ "source", "en" },
{ "target", "fr"},
{ "q", "<p>Hello World</p>" }
};
try
{
var responseBytes = webClient.UploadValues(GoogleTranslateApiUrl, "POST", data);
var json = Encoding.UTF8.GetString(responseBytes);
var result = JsonConvert.DeserializeObject<dynamic>(json);
var translation = result.data.translations[0].translatedText;
}
catch (Exception ex)
{
loggingService.Error(ex.Message);
}
}
? What? it is trivial to post using C# - it is right there in the documentation.
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
{
// Set type to POST
request.Method = "POST";
From there on you bascially put the data into fom fields into the content stream.
This is not "simulate a post meethod", it is fully doing a post request as per specifications.
Btw. hwhere does json enter here? You say "in C#". There is no need to use json?

RavenDB Backup: Checking the status using the HTTP API [/Raven/Backup/Status] Fails

I have successfully issued a Start-Backup request to my local RavenDB using an asynchronous HTTP WebRequest (C#). I can see the the backup files are being created in the backup-location I specified. This is good.
The Start-Backup call is asynchronous so I need some way to determine when the backup process is complete. Fortunately the RavenDB docs state:
You can check the status of the backup by querying the document with
the key: "Raven/Backup/Status". The backup is completed when the
IsRunning field in the document is set to false.
RavenDB Documentation
Can somebody please show me how to do this check?
I have tried issuing another HTTP request but it always returns with an status [400-Bad Request]. I would be happy to actually query the database using code for this doc but I do not know the type of the 'status document' so cannot call any generic db.Query<>() method and honestly, I am not sure how to query using a 'key'.
Here is the code I am using so far.
private void StartBackup(string backupLocation)
{
var requestUri = new UriBuilder(Default.RavenUri){Path ="/admin/backup"};
var formData = "{ 'BackupLocation': '" + backupLocation + "' }";
var request = GetRequest(requestUri.Uri, formData);
request.BeginGetResponse(asynchResult => CheckStatus(), null);
}
private void CheckStatus()
{
var requestUri = new UriBuilder(Default.RavenUri){Path = "/Raven/Backup/Status"};
var request = GetRequest(requestUri.Uri);
var response = request.GetResponse();
}
private WebRequest GetRequest(Uri uri, string formData = null)
{
var request = WebRequest.Create(uri);
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Credentials = CredentialCache.DefaultCredentials;
if (formData == null)
{
request.Method = "GET";
request.ContentLength = 0;
return request;
}
request.Method = "POST";
var data = Encoding.UTF8.GetBytes(formData);
request.ContentLength = data.Length;
request.ContentType = "application/x-www-form-urlencoded";
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(data, 0, data.Length);
}
return request;
}
The Raven/Backup/Status is a document, not an endpoint, if you want to just grab the data over the wire, use:
GET docs/Raven/Backup/Status
And it will work.
But you can also just use Raven.Backup.exe to do so.

Categories

Resources