I am trying to port some C# code over to F#.
The C# code has been taken from here (and slightly stripped back): https://github.com/joelpob/betfairng/blob/master/BetfairClient.cs
public bool Login(string p12CertificateLocation, string p12CertificatePassword, string username, string password)
{
var appKey = "APPKEY";
string postData = string.Format("username={0}&password={1}", username, password);
X509Certificate2 x509certificate = new X509Certificate2(p12CertificateLocation, p12CertificatePassword);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://identitysso.betfair.com/api/certlogin");
request.UseDefaultCredentials = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Headers.Add("X-Application", appKey);
request.ClientCertificates.Add(x509certificate);
request.Accept = "*/*";
using (Stream stream = request.GetRequestStream())
using (StreamWriter writer = new StreamWriter(stream, Encoding.Default))
writer.Write(postData);
using (Stream stream = ((HttpWebResponse)request.GetResponse()).GetResponseStream())
using (StreamReader reader = new StreamReader(stream, Encoding.Default))
The C# code above works great. However, when trying to run (what I think is) F# equivalent code, without any real alterations, I get an error message.
The code is being run from the same computer, same VS installation and with exactly the same 4 arguments.
The error message I get is on the second to last line:
member x.Login(username, password,p12CertificateLocation:string, p12CertificatePassword:string) =
let AppKey = "APPKEY"
let url = "https://identitysso.betfair.com/api/certlogin"
let postData = "username=" + username + "&password=" + password
let x509certificate = new X509Certificate2(p12CertificateLocation, p12CertificatePassword)
let req = HttpWebRequest.Create(url) :?> HttpWebRequest
req.ClientCertificates.Add(x509certificate)|>ignore
req.UseDefaultCredentials <- true
req.Method <- "POST"
req.ContentType <- "application/x-www-form-urlencoded"
req.Headers.Add("X-Application",AppKey)
req.Accept <-"*/*"
use stream = req.GetRequestStream()
use writer =new StreamWriter(stream,Encoding.Default)
writer.Write(postData)
// fails on this line:
use stream = (req.GetResponse() :?> HttpWebResponse ).GetResponseStream()
// with System.Net.WebException: 'The remote server returned an error: (400) Bad Request.'
use reader = new StreamReader(stream,Encoding.Default)
I'm a bit lost here, as to my mind the two code implementations should be identical?
In this C# code:
using (Stream stream1 = request.GetRequestStream())
using (StreamWriter writer = new StreamWriter(stream1, Encoding.Default))
writer.Write(postData);
using (Stream stream2 = ((HttpWebResponse)request.GetResponse()).GetResponseStream())
using (StreamReader reader = new StreamReader(stream2, Encoding.Default))
writer and stream1 are flushed and closed immediately after the writer.Write call is finished, before you call request.GetResponse(). (This fact is somewhat obscured due to the, uhh.. interesting formatting of your code.)
In this F# code:
use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData)
use stream2 = (req.GetResponse() :?> HttpWebResponse).GetResponseStream()
use reader = new StreamReader(stream2, Encoding.Default)
writer and stream1 stay alive and remain unflushed and unclosed when req.GetResponse() is called; you need to put them in an artificial scope to get the same behavior as C#:
do use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData)
(* or
(use stream1 = req.GetRequestStream()
use writer = new StreamWriter(stream1, Encoding.Default)
writer.Write(postData))
*)
use stream2 = (req.GetResponse() :?> HttpWebResponse).GetResponseStream()
use reader = new StreamReader(stream2, Encoding.Default)
That's not "the C# way" to make an HTTP POST call. The typical way, in all supported .NET versions (ie 4.5.2 and later) is to use HttpClient. Even with HttpWebRequest, there are too many redundant or contradictory calls, like using default credentials (ie Windows authentication)
The C# way is this:
var client=new HttpClient("https://identitysso.betfair.com/api");
var values = new Dictionary<string, string>
{
{ "username", username },
{ "password", password }
};
var content = new FormUrlEncodedContent(values);
content.Headers.Add("X-Application",apiKey);
var response = await client.PostAsync("certlogin", content);
var responseString = await response.Content.ReadAsStringAsync();
In order to use a client certificate, you have to create the client instance using a custom HTTP Handler:
var handler = new WebRequestHandler();
var x509certificate = new X509Certificate2(certPath, certPassword);
handler.ClientCertificates.Add(certificate);
var client = new HttpClient(handler)
{
BaseAddress = new Uri("https://identitysso.betfair.com/api")
}
Writing the same code in F# is straight-forward:
let login username password (certPath:string) (certPassword:string) (apiKey:string) =
let handler = new WebRequestHandler()
let certificate = new X509Certificate2(certPath, certPassword)
handler.ClientCertificates.Add certificate |> ignore
let client = new HttpClient(handler,BaseAddress = Uri("https://identitysso.betfair.com"))
async {
let values = dict["username", username ; "password", password ]
let content = new FormUrlEncodedContent(values)
content.Headers.Add( "X-Application" ,apiKey)
let! response = client.PostAsync("api/certlogin",content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! responseString = response.Content.ReadAsStringAsync() |> Async.AwaitTask
return responseString
}
The client, handler are thread safe and can be reused so they can be stored in fields. Reusing the same client means that the OS doesn't have to create a new TCP/IP connection each time, leading to improved performance. It's better to create the client separately. :
let buildClient (certPath:string) (certPassword:string) =
let handler = new WebRequestHandler()
let certificate = new X509Certificate2(certPath, certPassword)
handler.ClientCertificates.Add certificate |> ignore
new HttpClient(handler,BaseAddress = Uri("https://identitysso.betfair.com"))
let login (client:HttpClient) username password (apiKey:string) =
async {
let values = dict["username", username ; "password", password ]
let content = new FormUrlEncodedContent(values)
content.Headers.Add( "X-Application" ,apiKey)
let! response = client.PostAsync("api/certlogin",content) |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! responseString = response.Content.ReadAsStringAsync() |> Async.AwaitTask
//Do whatever is needed here
return responseString
}
Related
I have a Encoded string like this:
https://xx.yyy.ir/xx/ff/addUser?name=%d8%b3%d9%84%d8%a7%d9%85
But when I use Uri to convert it to a URL and send it
result = "https://xx.yyy.ir/xx/ff/addUser?name=%d8%b3%d9%84%d8%a7%d9%85"
var client = new HttpClient
{
BaseAddress = new Uri(result.ToString()),
};
var response = await client.GetAsync("");
it send this request :
https://xx.yyy.ir/xx/ff/addUser?name=سلام
why this happen? how to prevent from this?
This is what's causing your problem: new Uri(result.ToString())
Let's try to do this in a proper manner and see what happens.
var builder = new UriBuilder("https://xx.yyy.ir/xx/ff/addUser") { Port = -1 };
var query = HttpUtility.ParseQueryString(builder.Query);
query["name"] = "سلام";
builder.Query = query.ToString();
using var httpClient = new HttpClient();
var response = await client.GetAsync(builder.ToString());
builder.ToString() returns https://xx.yyy.ir/xx/ff/addUser?name=%d8%b3%d9%84%d8%a7%d9%85
So basically, the above code boils down to this:
using var httpClient = new HttpClient();
var response = await client.GetAsync("https://xx.yyy.ir/xx/ff/addUser?name=%d8%b3%d9%84%d8%a7%d9%85");
Tested and verified on my computer.
I'm trying to make chat app with XamarinForms and I'm trying to upload a file with parameters to server. But I'm getting always 403 Forbidden message. (There is no authentication, there is only token key for now).
If I try to get or send any data to server, it works as well. When I try to send a file with data it returns 403 Forbidden message. I also tried to send same data with Postman. it's worked as well. I'm writing part of code, Could you please tell me, I made it wrong where?
Thanks in advance.
private async Task<HttpClient> GetClient()
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("X-Token-Key", ServiceToken);
client.DefaultRequestHeaders.UserAgent.Add(new System.Net.Http.Headers.ProductInfoHeaderValue("Chrome", "41.0.2228.0"));
return client;
}
If I send text message, it works as well.
public async Task<MobileResult> SendConversationTextMessage(MessageModel message)
{
HttpClient client = await GetClient();
string param = JsonConvert.SerializeObject(message);
var response = await client.PostAsync(Url + "conversation/message_add_text", new StringContent(param, Encoding.UTF8, "application/json"));
var mobileResult = JsonConvert.DeserializeObject<MobileResult>(await response.Content.ReadAsStringAsync());
return mobileResult;
}
If I send message with data, it returns 403 Forbidden
public async Task<MobileResult> SendConversationFileMessage(
FileModel FileMessage,
int UserRemoteId,
int ConversationId,
int ToUserId,
string SendedTime,
MessageModel.MessageType Type,
MessageModel.MessageStatus Status,
string MessageType)
{
HttpClient client = await GetClient();
string PostUrl = Url + "conversation/message_add_" + MessageType;
MultipartFormDataContent content = new MultipartFormDataContent();
ByteArrayContent baContent = new ByteArrayContent(FileMessage.BinaryData);
StringContent UserIdContent = new StringContent(UserRemoteId.ToString());
StringContent ConversationIdContent = new StringContent(ConversationId.ToString());
StringContent ToUserIdContent = new StringContent(ToUserId.ToString());
StringContent SendedTimeContent = new StringContent(SendedTime.ToString());
StringContent TypeContent = new StringContent(Type.ToString());
StringContent StatusContent = new StringContent(Status.ToString());
content.Add(baContent, "AttachedFile", FileMessage.Name);
content.Add(UserIdContent, "serId");
content.Add(ConversationIdContent, "ConversationId");
content.Add(ToUserIdContent, "ToUserId");
content.Add(SendedTimeContent, "SendedTime");
content.Add(TypeContent, "Type");
content.Add(StatusContent, "Status");
try
{
var response = await client.PostAsync(PostUrl, content);
string result = await response.Content.ReadAsStringAsync();
var mobileResult = JsonConvert.DeserializeObject<MobileResult>(result);
return mobileResult;
}
catch (Exception e)
{
return new MobileResult
{
Result = false,
Data = null,
Message = e.ToString()
};
}
}
Postman-Screenshot
Edit: I've tested to send multipart/form-data different way but result is same I'm writing below code:
MultipartFormDataContent content = new MultipartFormDataContent();
var UserIdContent = new StringContent(UserId.ToString());
UserIdContent.Headers.Add("Content-Disposition", "form-data; name=\"UserId\"");
UserIdContent.Headers.Remove("Content-Type");
content.Add(UserIdContent, "UserId");
var ConversationIdContent = new StringContent(ConversationId.ToString());
ConversationIdContent.Headers.Add("Content-Disposition", "form-data; name=\"ConversationId\"");
ConversationIdContent.Headers.Remove("Content-Type");
content.Add(ConversationIdContent, "ConversationId");
var ToUserIdContent = new StringContent(ToUserId.ToString());
ToUserIdContent.Headers.Add("Content-Disposition", "form-data; name=\"ToUserId\"");
ToUserIdContent.Headers.Remove("Content-Type");
content.Add(ToUserIdContent, "ToUserId");
var SendedTimeContent = new StringContent(SendedTime.ToString());
SendedTimeContent.Headers.Add("Content-Disposition", "form-data; name=\"SendedTime\"");
SendedTimeContent.Headers.Remove("Content-Type");
content.Add(SendedTimeContent, "SendedTime");
var TypeContent = new StringContent(Type.ToString());
TypeContent.Headers.Add("Content-Disposition", "form-data; name=\"Type\"");
TypeContent.Headers.Remove("Content-Type");
content.Add(TypeContent, "Type");
var StatusContent = new StringContent(Status.ToString());
StatusContent.Headers.Add("Content-Disposition", "form-data; name=\"Status\"");
StatusContent.Headers.Remove("Content-Type");
content.Add(StatusContent, "Status");
var streamContent = new StreamContent(file.InputStream);
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"AttachedFile\"; filename=\"" + file.FileName + "\"");
streamContent.Headers.Add("Content-Type", "video/mp4");
content.Add(streamContent, "AttachedFile", file.FileName);
I'd like to load an image directly from a URL but without saving it on the server, I want to upload it directly from memory to Amazon S3 server.
This is my code:
Dim wc As New WebClient
Dim fileStream As IO.Stream = wc.OpenRead("http://www.domain.com/image.jpg")
Dim request As New PutObjectRequest()
request.BucketName = "mybucket"
request.Key = "file.jpg"
request.InputStream = fileStream
client.PutObject(request)
The Amazon API gives me the error "Could not determine content length". The stream fileStream ends up as "System.Net.ConnectStream" which I'm not sure if it's correct.
The exact same code works with files from the HttpPostedFile but I need to use it in this way now.
Any ideas how I can convert the stream to become what Amazon API is expecting (with the length intact)?
I had the same problem when I'm using the GetObjectResponse() method and its propertie ResponseStream to copy a file from a folder to another in same bucket. I noted that the AWS SDK (2.3.45) have some faults like a another method called WriteResponseStreamToFile in GetObjectResponse() that simply doesn't work. These lacks of functions needs some workarounds.
I solved the problem openning the file in array of bytes and putting it in a MemoryStream object.
Try this (C# code)
WebClient wc = new WebClient();
Stream fileStream = wc.OpenRead("http://www.domain.com/image.jpg");
byte[] fileBytes = fileStream.ToArrayBytes();
PutObjectRequest request = new PutObjectRequest();
request.BucketName = "mybucket";
request.Key = "file.jpg";
request.InputStream = new MemoryStream(fileBytes);
client.PutObject(request);
The extesion method
public static byte[] ToArrayBytes(this Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
You can also create a MemoryStream without an array of bytes. But after the first PutObject in S3, the MemoryStream will be discarted. If you need to put others objects, I recommend the first option
WebClient wc = new WebClient();
Stream fileStream = wc.OpenRead("http://www.domain.com/image.jpg");
MemoryStream fileMemoryStream = fileStream.ToMemoryStream();
PutObjectRequest request = new PutObjectRequest();
request.BucketName = "mybucket";
request.Key = "file.jpg";
request.InputStream = fileMemoryStream ;
client.PutObject(request);
The extesion method
public static MemoryStream ToMemoryStream(this Stream input)
{
byte[] buffer = new byte[16 * 1024];
int read;
MemoryStream ms = new MemoryStream();
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms;
}
I had the same problem in a similar scenario.
The reason for the error is that to upload an object the SDK needs to know the whole content length that is going to be uploaded. To be able to obtain stream length it must be seekable, but the stream returned from WebClient is not. To indicate the expected length set Headers.ContentLength in PutObjectRequest. The SDK will use this value if it cannot determine length from the stream object.
To make your code work, obtain content length from the response headers returned by the call made by WebClient. Then set PutObjectRequest.Headers.ContentLength. Of course this relies on the server returned content length value.
Dim wc As New WebClient
Dim fileStream As IO.Stream = wc.OpenRead("http://www.example.com/image.jpg")
Dim contentLength As Long = Long.Parse(client.ResponseHeaders("Content-Length"))
Dim request As New PutObjectRequest()
request.BucketName = "mybucket"
request.Key = "file.jpg"
request.InputStream = fileStream
request.Headers.ContentLength = contentLength
client.PutObject(request)
I came up with a solution that uses UploadPart when the length is not available by any other means, plus this does not load the entire file into memory.
if (args.DocumentContents.CanSeek)
{
PutObjectRequest r = new PutObjectRequest();
r.InputStream = args.DocumentContents;
r.BucketName = s3Id.BucketName;
r.Key = s3Id.ObjectKey;
foreach (var item in args.CustomData)
{
r.Metadata[item.Key] = item.Value;
}
await S3Client.PutObjectAsync(r);
}
else
{
// if stream does not allow seeking, S3 client will throw error:
// Amazon.S3.AmazonS3Exception : Could not determine content length
// as a work around, if cannot use length property, will chunk
// file into sections and use UploadPart, so do not have to load
// entire file into memory as a single MemoryStream.
var r = new InitiateMultipartUploadRequest();
r.BucketName = s3Id.BucketName;
r.Key = s3Id.ObjectKey;
foreach (var item in args.CustomData)
{
r.Metadata[item.Key] = item.Value;
}
var multipartResponse = await S3Client.InitiateMultipartUploadAsync(r);
try
{
var completeRequest = new CompleteMultipartUploadRequest
{
UploadId = multipartResponse.UploadId,
BucketName = s3Id.BucketName,
Key = s3Id.ObjectKey,
};
// just using this size, because it is the max for Azure File Share, but it could be any size
// for S3, even a configured value
const int blockSize = 4194304;
// BinaryReader gives us access to ReadBytes
using (var reader = new BinaryReader(args.DocumentContents))
{
var partCounter = 1;
while (true)
{
byte[] buffer = reader.ReadBytes(blockSize);
if (buffer.Length == 0)
break;
using (MemoryStream uploadChunk = new MemoryStream(buffer))
{
uploadChunk.Position = 0;
var uploadRequest = new UploadPartRequest
{
BucketName = s3Id.BucketName,
Key = s3Id.ObjectKey,
UploadId = multipartResponse.UploadId,
PartNumber = partCounter,
InputStream = uploadChunk,
};
// could call UploadPart on multiple threads, instead of using await, but that would
// cause more data to be loaded into memory, which might be too much
var part2Task = await S3Client.UploadPartAsync(uploadRequest);
completeRequest.AddPartETags(part2Task);
}
partCounter++;
}
var completeResponse = await S3Client.CompleteMultipartUploadAsync(completeRequest);
}
}
catch
{
await S3Client.AbortMultipartUploadAsync(s3Id.BucketName, s3Id.ObjectKey
, multipartResponse.UploadId);
throw;
}
}
I've been playing around with some basic HTTP request response operations in an F# interactive session to try and get a better feel for the language. Everything here seems to be working as expected except when I get the response stream for my HTTP request I can never seem to read anything out of it. In the following code I get the text from the printfn line every time but the value for responseString always seems to be empty. Am I missing anything obvious?
let httpListener = new HttpListener()
httpListener.Prefixes.Add("http://*:8020/")
let getContext = Async.FromBeginEnd(httpListener.BeginGetContext, httpListener.EndGetContext)
let processRequest = async {
while true do
let! context = getContext
let request = context.Request
use requestReader = new StreamReader(request.InputStream)
let requestString = requestReader.ReadToEnd()
let response = context.Response
use streamWriter = new StreamWriter(response.OutputStream)
streamWriter.WriteLine "A response!"
response.Close()
}
httpListener.Start()
let maxThreads = 10
for n = 0 to maxThreads do
Async.Start processRequest
printfn "HttpListener started with %d maximum threads processing requests." maxThreads
let sendRequest = async {
let request = WebRequest.Create("http://localhost:8020") :?> HttpWebRequest
let postBytes = Encoding.ASCII.GetBytes("Issuing HTTP")
request.Method <- "POST"
request.ContentType <- "application/x-www-form-urlencoded"
request.ContentLength <- int64 postBytes.Length
let requestStream = request.GetRequestStream()
requestStream.Write(postBytes, 0, postBytes.Length)
requestStream.Close()
let! response = Async.FromBeginEnd(request.BeginGetResponse, request.EndGetResponse)
let responseStream = response.GetResponseStream()
use streamReader = new StreamReader(responseStream)
let responseString = streamReader.ReadToEnd()
printfn "Received the following response: %s" responseString
}
for n = 0 to maxThreads do
Async.Start sendRequest
you need to reduce the scope of usage for StreamWriter so it can be flushed on Dispose.
let processRequest = async {
while true do
let! context = getContext
let request = context.Request
use requestReader = new StreamReader(request.InputStream)
let requestString = requestReader.ReadToEnd()
let response = context.Response
do
use streamWriter = new StreamWriter(response.OutputStream)
streamWriter.WriteLine "A response!"
response.Close()
}
I'm doing some performance testing and ran across something puzzling to myself and I was hoping someone could shed some light.
I'm comparing the performance between an HttpWebRequest and a SoapHttpClientProtocol. In my tests I see the SoapHttpClientProtocol class performing twice as fast. However, I expected the HttpWebRequest to performance better.
Thanks for any insight anyone can provide!
Sam
Here is the code for the HttpWebRequest
public string RetrieveValue()
{
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] payload = encoding.GetBytes("sIP=");
string Url = #"url/RetrieveValue";
HttpWebRequest wr = (HttpWebRequest)HttpWebRequest.Create(Url);
wr.Method = "POST";
wr.KeepAlive = false;
wr.ContentType = "application/x-www-form-urlencoded";
wr.ContentLength = payload.Length;
wr.Timeout = 30000;
HttpWebResponse webResponse;
Stream wrStream = wr.GetRequestStream();
wrStream.Write(payload, 0, payload.Length);
wrStream.Close();
webResponse = (HttpWebResponse)wr.GetResponse();
Stream baseStream = webResponse.GetResponseStream();
string result = null;
using (StreamReader sr = new StreamReader(baseStream))
result = sr.ReadToEnd();
return result;
}
Here is the Code for the SoapHttpClientProtocol
WebServiceBinding(Name = "Soap", Namespace = "http://namespace.com/")]
public class MyRetriever : SoapHttpClientProtocol
{
[SoapDocumentMethod("http://url.com/Retrieve", RequestNamespace = "http://url.com/", ResponseNamespace = "http://url.com/", Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
public string RetrieveValue(string sVal)
{
return (string)base.Invoke("RetrieveValue",
new object[] { sVal })[0];
}
}
How are you invoking the two tests? RetrieveValue is establishing a new connection each time, and if you are using a single instance of your test soap client and calling GetNewSessionKey each time you are probably not incurring the same overhead.