ca9163d9
ca9163d9

Reputation: 29159

This operation cannot be performed after the request has been submitted

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

Answers (2)

Gustavo Guerra
Gustavo Guerra

Reputation: 5359

This has been fixed on the latest version of F# Data

Upvotes: 1

Konrad Kokosa
Konrad Kokosa

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:

  1. Write your own method using HttpWebRequest directly,
  2. Compile your own version of FSharp.Data, changing order of do! writeBody req bytes and let req = customizeHttpRequest req calls
  3. Notify contributors of library to make a fix for this

Note: 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

Related Questions