Question

last time I posted a question on here everyone provided some great guidance on getting my problem solved. Move forward in time and here is another. I'm attempting to redo a small helper tool I have that checks URL's and Files against VirusTotal to get some basic information. The code below works quite well but locks up the UI. I was told that I should look into Rx and am enjoying reading up on it but cannot seem to get my head wrapped around it. So now here is where the question comes in, what is the best way to design the following code to make it utilize Rx so that it is asynchronous and leaves my UI alone while it does it's thing. VirusTotal also utilizes multilevel JSON for responses so if anyone has a nice way of integrating that into this that would even be better.

class Virustotal
{
    private string APIKey = "REMOVED";
    private string FileReportURL = "https://www.virustotal.com/vtapi/v2/file/report";
    private string URLReportURL = "http://www.virustotal.com/vtapi/v2/url/report";
    private string URLSubmitURL = "https://www.virustotal.com/vtapi/v2/url/scan";

    WebRequest theRequest;
    HttpWebResponse theResponse;
    ArrayList  theQueryData;

    public string GetFileReport(string checksum) // Gets latest report of file from VT using a hash (MD5 / SHA1 / SHA256)
    {
        this.WebPostRequest(this.FileReportURL);
        this.Add("resource", checksum);
        return this.GetResponse();
    }

    public string GetURLReport(string url) // Gets latest report of URL from VT
    {
        this.WebPostRequest(this.URLReportURL);
        this.Add("resource", url);
        this.Add("scan", "1"); //Automatically submits to VT if no result found
        return this.GetResponse();
    }

    public string SubmitURL(string url) // Submits URL to VT for insertion to scanning queue
    {
        this.WebPostRequest(this.URLSubmitURL);
        this.Add("url", url);
        return this.GetResponse();
    }

    public string SubmitFile() // Submits File to VT for insertion to scanning queue
    {
        // File Upload code needed
        return this.GetResponse();
    }

    private void WebPostRequest(string url)
    {
        theRequest = WebRequest.Create(url);
        theRequest.Method = "POST";
        theQueryData = new ArrayList();
        this.Add("apikey", APIKey);
    }

    private void Add(string key, string value)
    {
        theQueryData.Add(String.Format("{0}={1}", key, Uri.EscapeDataString(value)));
    }

    private string GetResponse()
    {
        // Set the encoding type
        theRequest.ContentType="application/x-www-form-urlencoded";

        // Build a string containing all the parameters
        string Parameters = String.Join("&",(String[]) theQueryData.ToArray(typeof(string)));
        theRequest.ContentLength = Parameters.Length;

        // We write the parameters into the request
        StreamWriter sw = new StreamWriter(theRequest.GetRequestStream());
        sw.Write(Parameters);
        sw.Close();

        // Execute the query
        theResponse =  (HttpWebResponse)theRequest.GetResponse();
        StreamReader sr = new StreamReader(theResponse.GetResponseStream());
        return sr.ReadToEnd();
    }
}
Was it helpful?

Solution

Your code is poorly written which makes it more difficult to make it asynchronous - primarily the three class-level variables. When coding in Rx you want to think "functional programming" and not "OOP" - so no class-level variables.

So, what I've done is this - I've recoded the GetResponse method to encapsulate all of the state into a single call - and I've made it return IObservable<string> rather than just string.

The public functions can now be written like this:

public IObservable<string> GetFileReport(string checksum)
{
    return this.GetResponse(this.FileReportURL,
        new Dictionary<string, string>() { { "resource", checksum }, });
}

public IObservable<string> GetURLReport(string url)
{
    return this.GetResponse(this.URLReportURL,
        new Dictionary<string, string>()
            { { "resource", url }, { "scan", "1" }, });
}

public IObservable<string> SubmitURL(string url)
{
    return this.GetResponse(this.URLSubmitURL,
        new Dictionary<string, string>() { { "url", url }, });
}

public IObservable<string> SubmitFile()
{
    return this.GetResponse("UNKNOWNURL", new Dictionary<string, string>());
}

And GetResponse looks like this:

private IObservable<string> GetResponse(
    string url,
    Dictionary<string, string> theQueryData)
{
    return Observable.Start(() =>
    {
        var theRequest = WebRequest.Create(url);
        theRequest.Method = "POST";
        theRequest.ContentType="application/x-www-form-urlencoded";

        theQueryData.Add("apikey", APIKey);

        string Parameters = String.Join("&",
            theQueryData.Select(x =>
                String.Format("{0}={1}", x.Key, x.Value)));
        theRequest.ContentLength = Parameters.Length;

        using (var sw = new StreamWriter(theRequest.GetRequestStream()))
        {
            sw.Write(Parameters);
            sw.Close();
        }

        using (var theResponse =  (HttpWebResponse)theRequest.GetResponse())
        {
            using (var sr = new StreamReader(theResponse.GetResponseStream()))
            {
                return sr.ReadToEnd();
            }
        }
    });
}

I haven't actually tested this - I don't have the APIKEY for starters - but it should work OK. Let me know how you go.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top