构建具有持久状态的 Web 服务






4.67/5 (9投票s)
描述了一种创建 Web 服务的方法,该服务可在连续调用之间持久化其状态。
引言
由于 HTTP 协议的无状态性,Web 服务器会将每个请求都视为一个新请求。这意味着,如果您想跟踪一系列相关的调用,则必须建立一种方法来识别当前请求是新请求还是与另一个请求相关。
实现这一点的一种方法是为第一个请求生成一个令牌,然后为相关请求重用同一个令牌。令牌可以是任何东西,从数字到复杂对象,唯一重要的是令牌保证是唯一的。如果该令牌随每个请求一起发送,那么一切都会正常工作,因为我们可以确定当前请求属于哪个组。我们也可以反过来做:客户端使用唯一的令牌发起调用,然后基于该令牌进行通信。例如,当使用唯一的用户名作为令牌时,就可以实现这一点。
然而,另一个问题也随之而来,而且更为重要。如果所有请求都与同一个对象相关怎么办?如果我们需要在同一个对象上多次调用方法,并且该方法会修改该对象怎么办?我们需要一种方法来在调用之间存储该对象,以便我们可以通过每次新调用来修改该对象。
这就是我们将要讨论的内容。利用令牌的概念,我将描述一种在连续调用同一对象之间维护对象状态的方法。
想法
有几种方法可以实现此功能。本文只讨论其中两种。核心思想对两者都适用,区别在于实现细节。
假设我们采用客户端使用用户名作为令牌并向 Web 服务发起请求的方法。
思路:有一个列表,包含所有先前发起请求的令牌。当请求到来时,Web 服务会从请求的参数中检索令牌,并将其与列表进行比较。如果列表中找不到该令牌,则表示这是为该令牌发起的第一个请求,并且将为该请求创建一个新对象。然后将令牌添加到令牌列表中。
如果在列表中找到该令牌,则表示这是一个后续请求,对象是可用的。然后检索该对象。它将包含当前数据(上次请求修改过的数据)。
现在我们有了对象(新实例化的或检索到的)。我们现在可以使用该对象并调用所需的方法。
使用完对象后,就需要将其存储起来,以便在需要时可用。
听起来很复杂?其实不然。幸运的是,已经有现成的系统可以帮助我们处理令牌列表。接下来,我将介绍两个可以轻松使用的系统:Application
对象和文件系统。
使用 Web 服务的 Application 对象
每个 ASP.NET Web 应用程序(包括 Web 服务)都有一个名为 Application State 的设施。该设施通过 Application
属性进行访问,允许在请求之间存储各种对象。以这种方式存储的对象可供整个应用程序访问。
关于 Session State 及其存储方式需要进行深入讨论。实际上,您可以使用 State Server,甚至 SQL Server 来保存 State 数据。如果您正在开发大型应用程序,这是理想的选择。然而,对于小型应用程序,这些解决方案通常不是必需的。
我希望您清楚 Application
对象正是我们所需要的。对象可以像哈希表一样存储在 Application
对象中。您可以使用令牌(它是唯一的)作为键,并将对象存储为值。
这个解决方案很棒;然而,它确实有一个缺点:状态不是完全持久化的。当服务器重新启动时,状态会丢失。此外,Internet Information Services (IIS) 可能会回收 ASP.NET 工作进程。这意味着,有时您可能会丢失 Application
对象中的数据。
使用此设施进行编程非常简单,如下面的代码所示。
public string MethodCall_ver1(int token)
{
UserDefinedClass d = null;
//If the token not found in the Application
//object, we retrieve it
if (Application[token.ToString()] != null)
{
d = (UserDefinedClass)Application[token.ToString()];
}
else
{
//if the token was not found then we create a new object
d = new UserDefinedClass();
}
//Call methods on the object
//We save the object into the Application object
Application[token.ToString()] = d;
//The service returns
return string.Format("{0}", d.something.ToString());
}
我们只需要检查一个对象是否存储在由令牌指定的地址。如果没有,则实例化一个新对象。使用完它之后,将其存储在 Application
对象中。
如果您发起的请求时间间隔较短,并且不太在意数据丢失,那么此解决方案就很棒。然而,当您希望在任何情况下都保留请求之间的状态,并且不想使用额外的服务器时,可以使用下面介绍的解决方案。
使用文件系统
文件系统是系统重启之间存储对象的绝佳方式。此外,即使文件系统是树状结构的,仍然有一种方法可以唯一地标识文件夹内的文件:通过使用文件名。因此,令牌可以作为文件名使用。
当请求到来时,方法会查找一个名为令牌的文件。思路保持不变,只是实现方式发生了变化。您不必在 Application
对象中查找,而是检查文件是否存在。不过有一个问题。我们需要将对象从内存中取出并存储在文件系统中。这意味着我们需要调用一个称为序列化的过程,将对象的字节存储到文件流中。反向过程称为反序列化,它包括从流中读取对象的字节并重新创建对象。
幸运的是,.NET 框架允许我们非常简单地做到这一点!BinaryFormatter
类就能做到这一点。下面的代码展示了这一点。正如您所看到的,唯一改变的是我们保存和检索对象的方式。
//We have a setting in web.config which tells
//us where should we store the files.
// The setting is called TemporaryDirectory
//Check to see if the file if the specified directory
//exists. If it does, this is not the first request and we retrieve it
if (File.Exists(string.Format("{0}{1}.dat",
System.Configuration.ConfigurationManager.
AppSettings["TemporaryDirectory"], id )))
{
//Get a FileStream to point to that file
FileStream sr = new FileStream(string.Format("{0}{1}.dat",
System.Configuration.ConfigurationManager.
AppSettings["TemporaryDirectory"], id),
FileMode.Open);
//Create an object used in serializing/deserializing objects
BinaryFormatter bf = new BinaryFormatter();
//deserialize the object
d = (UserDefinedClass)bf.Deserialize(sr);
sr.Close();
}
else
{
//if the file does not exist then we create a new object
d = new UserDefinedClass();
}
//Call methods on the object
//We serialize the object back to the file system
FileStream sw = new FileStream(string.Format("{0}{1}.dat",
System.Configuration.ConfigurationManager.
AppSettings["TemporaryDirectory"], id),
FileMode.OpenOrCreate);
BinaryFormatter abf = new BinaryFormatter();
abf.Serialize(sw, d);
sw.Close();
//The service returns
return string.Format("{0}", d.something.ToString());
TemporaryDirectory
存储在 web.config 中,该文件中的条目是
<add key ="TemporaryDirectory"
value="C:\Inetpub\wwwroot\WebServicePersistent\temp\"/>
为了让我们拥有一个在请求之间持久化状态的 Web 服务,只需要做到这些。
此解决方案的优点在于,即使在系统崩溃和系统重启之后,状态也会得到持久化。这一点非常棒。然而,我们面临授权访问的问题。服务器上存储的文件可能会被不怀好意的人删除。这意味着,如果对象包含敏感数据,我们将面临数据泄露。为了避免这种情况,我们必须在存储序列化对象的文件夹上设置适当的 ACL(访问控制列表),并确保 IIS 不会将它们提供给任何人!
测试解决方案
好吧,要做到这一点,我们只需使用相同的令牌反复调用 Web 服务,并观察“something”属性如何持续增加。
性能考量
虽然将对象存储在 Application
对象中似乎是更好的解决方案,但随着需要服务的请求数量的增加,所有对象都将存储在服务器内存中。这可能会是一个大问题,甚至可能导致 DDoS 攻击。
另一方面,在使用基于文件的方法时,每次读写磁盘都会产生一定的开销。然而,内存仅在需要时分配,因此不会发生 DDoS。
结论
如果您希望在 Web 服务的连续调用之间保持持久化状态,您可以采用我提出的解决方案之一。您可以选择使用 Application
对象来存储对象,或者序列化对象并将它们存储在磁盘上的文件中。
Application
对象方法更适合访问频繁的小对象,而文件方法更适合访问不那么频繁的大对象。
无论如何,您决定实现的解决方案完全取决于您和您开发的应用程序的技术要求。
祝您编码愉快!