2
\$\begingroup\$

I am building a website that uses Amazon S3 to host its image (and other) files.

I require that images displayed on the website look like they come from my site and not from Amazon (so we are talking on the request/response level). What I have done is created a controller which all these files will pass through. The controller should request the file from Amazon and pass it on, as if it's from the local website.

My concern is my usage of the response stream and error-handling. Basically, if anything happens, I want it to simply return a 404.

public class RequestState
{
    public RequestState()
    {
        BufferSize = 1024;
        BufferRead = new byte[BufferSize];
        Request = null;
        ResponseStream = null;
    }

    public int BufferSize { get; private set; }
    public byte[] BufferRead { get; set; }
    public HttpWebRequest Request { get; set; }
    public HttpWebResponse Response { get; set; }
    public Stream ResponseStream { get; set; }
}

public class MediaController : AsyncController
{
    public void IndexAsync(string key)
    {
        if (string.IsNullOrWhiteSpace(key))
            throw new HttpException((int)HttpStatusCode.NotFound, "File not found.");

        AsyncManager.OutstandingOperations.Increment();

        var request = (HttpWebRequest)WebRequest.Create(string.Format(Constants.Urls.AmazonFileGetUrlFormat, AppSettings.AmazonS3BucketName, key));
        request.Method = "GET";
        request.ContentType = "application/x-www-form-urlencoded";

        var state = new RequestState { Request = request };

        request.BeginGetResponse(ResponseCallback, state);
    }

    private void ResponseCallback(IAsyncResult result)
    {
        try
        {
            var state = (RequestState)result.AsyncState;
            HttpWebRequest request = state.Request;

            state.Response = (HttpWebResponse)request.EndGetResponse(result);
            Stream responseStream = state.Response.GetResponseStream();
            state.ResponseStream = responseStream;

            AsyncManager.Parameters["imageInBytes"] = ReadFully(responseStream);
            AsyncManager.Parameters["contentType"] = state.Response.ContentType;
        }
        catch (WebException ex)
        {
            var state = (RequestState)result.AsyncState;

            if (state.Response != null)
                state.Response.Close();

            AsyncManager.Parameters["webException"] = ex;
        }
        catch (Exception ex)
        {
            var state = (RequestState)result.AsyncState;

            if (state.Response != null)
                state.Response.Close();

            throw;
        }
        finally
        {
            AsyncManager.Parameters["key"] = "key";
            AsyncManager.OutstandingOperations.Decrement();
        }
    }

    public static byte[] ReadFully(Stream input)
    {
        var buffer = new byte[16 * 1024];

        using (var stream = new MemoryStream())
        {
            int read;
            while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                stream.Write(buffer, 0, read);
            }
            return stream.ToArray();
        }
    }

    [OutputCache(Duration = 600, VaryByParam = "key")]
    public FileContentResult IndexCompleted(string key, byte[] imageInBytes, string contentType, WebException webException)
    {
        if (webException != null && webException.Status == WebExceptionStatus.ProtocolError)
        {
            var response = webException.Response as HttpWebResponse;

            if (response != null)
            {
                throw new HttpException((int)response.StatusCode, response.StatusDescription);
            }

            throw new HttpException((int)HttpStatusCode.NotFound, "File not found.");
        }

        if (imageInBytes == null || imageInBytes.Length == 0)
        {
            throw new HttpException((int)HttpStatusCode.NotFound, "File not found.");
        }

        return File(imageInBytes, contentType);
    }
}
\$\endgroup\$
1
2
\$\begingroup\$

I don't understand the point in using this pattern:

    catch (WebException ex)
    {
        var state = (RequestState)result.AsyncState;

        if (state.Response != null)
            state.Response.Close();

        AsyncManager.Parameters["webException"] = ex;
    }
    catch (Exception ex)
    {
        var state = (RequestState)result.AsyncState;

        if (state.Response != null)
            state.Response.Close();

        throw;
    }
    finally
    {
        AsyncManager.Parameters["key"] = "key";
        AsyncManager.OutstandingOperations.Decrement();
    }

When it looks like it could be simplified to this:

    catch (WebException ex)
    {
        AsyncManager.Parameters["webException"] = ex;
    }
    finally
    {
        var state = (RequestState)result.AsyncState;

        if (state.Response != null)
            state.Response.Close();

        AsyncManager.Parameters["key"] = "key";
        AsyncManager.OutstandingOperations.Decrement();
    }

The following may stem from my lack of familiarity with Amazon S3:

I'm not sure what functionality your ReadFully method offers over Stream.Read(Stream.Length) or, more safely, Stream.Read(Stream.Length - Stream.Position) except potentially causing issues if the stream does not have a length of multiplies of 16x1024.

Speaking of which, you've hard-coded that 16x1024 in a magic number:

var buffer = new byte[16 * 1024];

I would recommend converting these values to consts so you can change them later with ease.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.