模擬IIS向Silverlight輸出策略文件

作者: Leon Weng  來源: 博客園  發布時間: 2010-09-17 07:56  閱讀: 1044 次  推薦: 0   原文鏈接   [收藏]  

  問題

  最近的Silverlight開發中,由于部分需求對實時性和數據量下載速度有要求,部分WCF服務配置成了netTcpBinding,這種方式跟普通的service.svc寄宿IIS不同的是,Silverlight需要的策略文件需要放置在本機IIS的根下,也就是wwwroot文件夾下,以滿足Silverlight在以TCP協議調用本機WCF服務時請求策略文件。(注:Silverlight通過TCP協議調用WCF服務時,會以http方式請求主機的一個策略文件,地址是http://localhost/clientaccesspolicy.xml)

  這其實是個不太好的選擇,程序運行的所需的環境被分成了兩部分,同事的機器上并未安裝IIS,為了大家開發簡便,不用在額外安裝IIS,也為了讓程序更加獨立,我就想能不能寫代碼監控80端口模擬IIS向Silverlight輸出這個策略文件。

  解決方法

  有了這個想法之后,首先想到的是通過Socket進行監聽,因為此前在MSDN上看到過這種方式,但很無奈,將代碼轉移過來之后,并未成功。相信做過Silverlight在Socket方面應用的朋友對下面這個PolicyServer類很熟悉吧。

 
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace PolicyServer
{

// Encapsulate and manage state for a single connection from a client
class PolicyConnection
{

private Socket m_connection;

// buffer to receive the request from the client
private byte[] m_buffer;
private int m_received;

// the policy to return to the client
private byte[] m_policy;

// the request that we're expecting from the client
private static string s_policyRequestString = "<policy-file-request/>";



public PolicyConnection(Socket client, byte[] policy)
{
m_connection
= client;
m_policy
= policy;

m_buffer
= new byte[s_policyRequestString.Length];
m_received
= 0;

try
{
// receive the request from the client
m_connection.BeginReceive(m_buffer, 0, s_policyRequestString.Length, SocketFlags.None, new AsyncCallback(OnReceive), null);
}

catch (SocketException)
{
m_connection.Close();
}
}


// Called when we receive data from the client
private void OnReceive(IAsyncResult res)
{

try
{
m_received
+= m_connection.EndReceive(res);

// if we haven't gotten enough for a full request yet, receive again
if (m_received < s_policyRequestString.Length)
{
m_connection.BeginReceive(m_buffer, m_received, s_policyRequestString.Length
- m_received, SocketFlags.None, new AsyncCallback(OnReceive), null);
return;
}


// make sure the request is valid
string request = System.Text.Encoding.UTF8.GetString(m_buffer, 0, m_received);
if (StringComparer.InvariantCultureIgnoreCase.Compare(request, s_policyRequestString) != 0)
{
m_connection.Close();

return;
}


// send the policy
m_connection.BeginSend(m_policy, 0, m_policy.Length, SocketFlags.None, new AsyncCallback(OnSend), null);
}

catch (SocketException)
{
m_connection.Close();
}
}


// called after sending the policy to the client; close the connection.
public void OnSend(IAsyncResult res)
{

try
{
m_connection.EndSend(res);
}

finally
{
m_connection.Close();
}
}
}


// Listens for connections on port 943 and dispatches requests to a PolicyConnection
class PolicyServer
{

private Socket m_listener;
private byte[] m_policy;

// pass in the path of an XML file containing the socket policy
public PolicyServer(string policyFile)
{

// Load the policy file
FileStream policyStream = new FileStream(policyFile, FileMode.Open);

m_policy
= new byte[policyStream.Length];
policyStream.Read(m_policy,
0, m_policy.Length);

policyStream.Close();

m_listener
= new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);

m_listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)
27, 0);

m_listener.Bind(
new IPEndPoint(IPAddress.IPv6Any, 943));
m_listener.Listen(
10);

m_listener.BeginAccept(
new AsyncCallback(OnConnection), null);
}


public void OnConnection(IAsyncResult res)
{
Socket client
= null;

try
{
client
= m_listener.EndAccept(res);
}

catch (SocketException)
{

return;
}


// handle this policy request with a PolicyConnection
PolicyConnection pc = new PolicyConnection(client, m_policy);

// look for more connections
m_listener.BeginAccept(new AsyncCallback(OnConnection), null);
}


public void Close()
{
m_listener.Close();
}
}

public class Program
{

static void Main(string[] args)
{

if (args.Length == 0)
{
Console.WriteLine(
"usage: PolicyServer.exe PolicyFile.xml");
return;
}

PolicyServer ps
= new PolicyServer(args[0]);
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}
}
}

  此路不通之后,又想起使用HttpListener類,看看是否能夠監聽http請求,果然能夠截獲HTTP的請求。

 
HttpListener listener = new HttpListener();
listener.Prefixes.Add(http:
//localhost/);

listener.Start();Console.WriteLine("開始監聽…");

HttpListenerContext context
= listener.GetContext();
HttpListenerRequest request
= context.Request;
HttpListenerResponse response
= context.Response;

  但是這種方式有個明顯的缺點,就是線程是阻塞的。于是,又想到使用線程池。

 
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(Listen));
private static void Listen(object state)
{

while (httpListener.IsListening)
{
httpListener.BeginGetContext(
new AsyncCallback(ListenerCallback), httpListener);
listenForNextRequest.WaitOne();
}
}

 

  這樣的話,每接收一個請求便會異步處理這個請求。在請求的處理上,接收請求后需要向外輸出策略文件流,供silverlight端接收驗證。

 
using (System.Net.HttpListenerResponse response = context.Response)
{
System.Threading.Thread.Sleep(
1000);

string responseString = "<?xml version=\"1.0\" encoding=\"utf-8\"?> "
+ " <access-policy> "
+ " <cross-domain-access> "
+ " <policy> "
+ " <allow-from http-request-headers=\"*\">"
+ " <domain uri=\"*\" /> "
+ " </allow-from> "
+ " <grant-to> "
+ " <socket-resource port=\"4502-4534\" protocol=\"tcp\" /> "
+ " </grant-to> "
+ " </policy> "
+ " </cross-domain-access>"
+ " </access-policy>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64
= buffer.LongLength;
response.OutputStream.Write(buffer,
0, buffer.Length);
}

  啟動這個模擬服務,將clientaccesspolicy從wwwroot中移除后再運行一下程序,OK,我們不再需要將策略文件放到IIS下了。

  提醒 

  如果你的機器裝了IIS,請還是放一個策略文件到wwwroot吧,否則就停掉IIS再使用這個類,因為IIS和這個類只能有一方監聽80端口。

  本文中的這個類參考了:http://forums.silverlight.net/forums/p/113106/255614.aspx

0
0
 
標簽:Silverlight
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()