/// Copyright (c) Microsoft Corporation. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Activation;
using Microsoft.ServiceModel.Web;
using System.Linq;
using System.Net;
using System.IO;
using System.Text;
using ProxyStuff;
using System.Web;
using System.Diagnostics;
using System.Threading;
using AsyncServiceLibrary;
// The following line sets the default namespace for DataContract serialized typed to be ""
[assembly: ContractNamespace("", ClrNamespace = "WcfAsyncRestApi")]
namespace WcfAsyncRestApi
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall, ConcurrencyMode=ConcurrencyMode.Multiple),
AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed),
ServiceContract]
public partial class AsyncService
{
/// <summary>
/// Returns the content from destination URL and caches the response in the browser for specified seconds.
/// </summary>
/// <param name="url">URL to fetch</param>
/// <param name="cacheDuration">Cache duration</param>
/// <returns></returns>
[OperationContract(AsyncPattern=true)]
//[WebGet(BodyStyle=WebMessageBodyStyle.Bare, UriTemplate="/Url?uri={url}&cacheDuration={cacheDuration}")]
[WebGet(BodyStyle=WebMessageBodyStyle.Bare)]
public IAsyncResult BeginGetUrl(string url, int cacheDuration, AsyncCallback wcfCallback, object wcfState)
{
/// If the url already exists in cache then there's no need to fetch it from the source.
/// We can just return the response immediately from cache
if (IsInCache(url))
{
return WcfAsyncHelper.BeginSync<WebRequestState>(
new WebRequestState
{
Url = url,
CacheDuration = cacheDuration,
ContentType = WebOperationContext.Current.IncomingRequest.ContentType,
},
wcfCallback, wcfState);
}
else
{
/// The content does not exist in cache and we need to get it from the
/// original source
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "GET";
//request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
return WcfAsyncHelper.BeginAsync<WebRequestState>(
new WebRequestState
{
Request = request,
ContentType = WebOperationContext.Current.IncomingRequest.ContentType,
Url = url,
CacheDuration = cacheDuration,
StartThreadId = Thread.CurrentThread.ManagedThreadId
},
wcfCallback, wcfState,
(myState, externalServiceCallback, customState) =>
myState.Request.BeginGetResponse(externalServiceCallback, customState));
}
}
public Stream EndGetUrl(IAsyncResult asyncResult)
{
//Debug.WriteLine("Requested completed on Thread: " + Thread.CurrentThread.ManagedThreadId);
if (WcfAsyncHelper.IsSync<WebRequestState>(asyncResult))
{
return WcfAsyncHelper.EndSync<WebRequestState, Stream>(
asyncResult,
(myState, completedResult) =>
{
CacheEntry cacheEntry = GetFromCache(myState.Url);
var outResponse = WebOperationContext.Current.OutgoingResponse;
SetResponseHeaders(cacheEntry.ContentLength, cacheEntry.ContentType,
cacheEntry.ContentEncoding,
myState, outResponse);
return new MemoryStream(cacheEntry.Content);
},
(exception, myState) => { throw new ProtocolException(exception.Message, exception); },
(myState) => { /*Nothing to dispose*/ });
}
else
{
return WcfAsyncHelper.EndAsync<WebRequestState, Stream>(asyncResult,
(myState, serviceResult) =>
{
myState.EndThreadId = Thread.CurrentThread.ManagedThreadId;
var httpResponse = myState.Request.EndGetResponse(serviceResult) as HttpWebResponse;
var outResponse = WebOperationContext.Current.OutgoingResponse;
SetResponseHeaders(httpResponse.ContentLength,
httpResponse.ContentType, httpResponse.ContentEncoding,
myState, outResponse);
// If response needs to be cached, then wrap the stream so that
// when the stream is being read, the content is stored in a memory
// stream and when the read is complete, the memory stream is stored
// in cache.
if (myState.CacheDuration > 0)
return new StreamWrapper(httpResponse.GetResponseStream(),
(int)(outResponse.ContentLength > 0 ? outResponse.ContentLength : 8 * 1024),
buffer =>
{
StoreInCache(myState.Url, myState.CacheDuration, new CacheEntry
{
Content = buffer,
ContentLength = buffer.Length,
ContentEncoding = httpResponse.ContentEncoding,
ContentType = httpResponse.ContentType
});
});
else
return httpResponse.GetResponseStream();
},
(exception, myState) => { throw new ProtocolException(exception.Message, exception); },
(myState) => { /*Nothing to dispose*/ });
}
}
private void SetResponseHeaders(long contentLength, string contentType, string contentEncoding,
WebRequestState webState,
OutgoingWebResponseContext outResponse)
{
if (!string.IsNullOrEmpty(contentEncoding))
outResponse.Headers["Content-Encoding"] = contentEncoding;
outResponse.ContentLength = contentLength;
outResponse.ContentType = contentType;
SetCaching(WebOperationContext.Current, DateTime.Now, webState.CacheDuration);
}
private void SetCaching(WebOperationContext context, DateTime lastModifiedDate, int maxCacheAge)
{
// set CacheControl header
HttpResponseHeader cacheHeader = HttpResponseHeader.CacheControl;
String cacheControlValue = String.Format("max-age={0}, must-revalidate", maxCacheAge);
context.OutgoingResponse.Headers.Add(cacheHeader, cacheControlValue);
// set cache validation
context.OutgoingResponse.LastModified = lastModifiedDate;
// No ETag, want this to be cached on browser for good.
// If you want to emit etag, then do so here.
//String eTag = context.IncomingRequest.UriTemplateMatch.RequestUri.ToString() + lastModifiedDate.ToString();
//context.OutgoingResponse.ETag = eTag;
}
private bool IsInCache(string url)
{
return false;
//return HttpRuntime.Cache.Get(url) != null;
}
private CacheEntry GetFromCache(string url)
{
return HttpRuntime.Cache.Get(url) as CacheEntry;
}
private void StoreInCache(string url, int cacheDuration, CacheEntry entry)
{
return;
//HttpRuntime.Cache.Add(url, entry, null, DateTime.Now.AddSeconds(cacheDuration),
// System.Web.Caching.Cache.NoSlidingExpiration,
// System.Web.Caching.CacheItemPriority.Normal,
// null);
}
internal class WebRequestState
{
public HttpWebRequest Request;
public string Url;
public int CacheDuration;
public string ContentType;
public int StartThreadId;
public int EndThreadId;
}
[Serializable]
internal class CacheEntry
{
public long ContentLength;
public string ContentType;
public string ContentEncoding;
public byte[] Content;
}
}
}