Reputation: 29159
The following code, which uses the Http module of FSharp.Data, raised exception in function setProxy
when the Http.RequestString
was called. The similar code works when using query
parameter instead of body
on a different website. What can cause the problem? BTW, it works when using a modified (for assigning proxy) previous version of Http module.
let headers = [ UserAgent ConfigurationManager.AppSettings.["UserAgent"] ]
let setProxy (req: HttpWebRequest) =
req.Proxy <- WebProxy(ConfigurationManager.AppSettings.["Proxy"], true)
req // Error here
let cc = CookieContainer()
let body = HttpRequestBody.FormValues [
"username", user;
"password", password;
"nothing.x", "21"; "nothing.y", "34"]
let html =
Http.RequestString("https://..../CheckLoginServlet",
httpMethod = "POST",
body = body, cookieContainer = cc, headers = headers,
customizeHttpRequest = setProxy )
Exception:
An exception of type 'System.InvalidOperationException' occurred in System.dll but was not handled in user code
Additional information: This operation cannot be performed after the request has been submitted.
Upvotes: 2
Views: 1525
Reputation: 16878
I believe this is a bug in the FSharp.Data library. Looking through its source code on GitHub, we can extract code of our interest to something like:
let values = [ "id", "2"; ]
let req = WebRequest.Create("https://..../CheckLoginServlet") :?> HttpWebRequest
req.Method <- HttpMethod.Post
req.UserAgent <- "Magic"
req.AutomaticDecompression <- DecompressionMethods.GZip ||| DecompressionMethods.Deflate
let cookieContainer = new CookieContainer()
req.CookieContainer <- cookieContainer
req.ContentType <- "application/x-www-form-urlencoded"
let bytes = [ for k, v in values -> Uri.EscapeDataString k + "=" + Uri.EscapeDataString v ]
|> String.concat "&"
|> HttpEncodings.PostDefaultEncoding.GetBytes
let f = fun () -> async {
do! writeBody req bytes
let req = customizeHttpRequest req
let! resp = Async.FromBeginEnd(req.BeginGetResponse , req.EndGetResponse)
let stream = resp.GetResponseStream()
return! toHttpResponse
}
f()
the problem is in writeBody
call before call to customizeHttpRequest
, while its code is:
let writeBody (req:HttpWebRequest) (postBytes:byte[]) = async {
req.ContentLength <- int64 postBytes.Length
use! output = Async.FromBeginEnd(req.BeginGetRequestStream, req.EndGetRequestStream)
do! output.AsyncWrite(postBytes, 0, postBytes.Length)
output.Flush()
}
where Async.FromBeginEnd(req.BeginGetRequestStream, req.EndGetRequestStream)
changes HttpWebRequest.m_RequestSubmitted
flag, which is then checked by (like in many others) property setter of HttpWebRequest.Proxy
. My suggestions:
HttpWebRequest
directly,do! writeBody req bytes
and let req = customizeHttpRequest req
callsNote: This can be easily reproduced in C# sample program:
private static async void GetRequestStreamCallback(IAsyncResult ar)
{
HttpWebRequest req = (HttpWebRequest)ar.AsyncState;
Stream postStream = req.EndGetRequestStream(ar); // Here flag req.m_RequestSubmitted is true already
byte[] byteArray = Encoding.UTF8.GetBytes("id=1");
await postStream.WriteAsync(byteArray, 0, byteArray.Length);
postStream.Close();
req.Proxy = new WebProxy("localhost:8888", true);
allDone.WaitOne();
}
private static ManualResetEvent allDone = new ManualResetEvent(false);
static void Main(string[] args)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://localhost:64343/api/products");
req.Method = "POST";
req.UserAgent = "Magic";
req.AutomaticDecompression = DecompressionMethods.GZip;
req.CookieContainer = new CookieContainer();
req.ContentType = "application/x-www-form-urlencoded";
var bytes = Encoding.UTF8.GetBytes("id=2");
req.ContentLength = bytes.Length;
req.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), req);
allDone.WaitOne();
}
Upvotes: 4