XML в MS SQL Server 2000 и технологиях доступа к данным
Страница 13. Доступ к SQL Server по HTTP


Доступ к SQL Server по HTTP

Насколько вы заметили, в п.1 среди одного из ключевых преимуществ поддержки XML в СУБД называлась возможность доступа по HTTP. Опять же, насколько вы заметили, HTTP у нас до сих пор, в общем-то, не пахло. Настало время исправить этот недостаток. Напрямую по HTTP с SQL Server общаться нельзя. Во всяком случае пока между клиентом и SQL Server-ом приходится иметь IIS. В состав SQLXML 3.0 входит ISAPI-фильтр ...\Program Files\Common Files\System\Ole DB\sqlis3.dll, который принимает на себя обработку методов GET, HEAD, POST, вытаскивает из них тело запроса и отправляет его на SQL Server. SQL Server его обрабатывает, ответ в виде XML передается Web-серверу, а от него по HTTP - клиенту. Таким образом, второй участок пути (IIS - SQL Server) относится к локальному взаимодействию с SQL Server, с которым, я надеюсь, все ясно, т.к. им мы занимались на протяжении 12-ти предыдущих параграфов. Чтобы разобраться с первым участком (клиент - IIS), нам нужно понять две вещи: как правильно упаковать запрос в GET и POST-обращения и как их отправить фильтру. С фильтром все происходит классически: на IISе создается виртуальная директория, к которой он подвязывается. Для ее создания можно воспользоваться графической утилитой Configure IIS Support в SQLXML 3.0 (в отличие от аналогичной утилиты в составе SQL Server она поддерживает все те новшества, которые были представлены в этом релизе), либо сделать это программным путем, написав скрипт, похожий на тот, что представлен ниже.


static void Main()
{
const string SQLSrv = "(local)";
const string DBName = "Northwind";
string SQLVirtDirName = "SQLXML3";
string SQLVirtDirPhysPath = Directory.GetParent(Directory.GetCurrentDirectory()).FullName;

//Получаем ссылку на основной объект
SQLVDIRLib.SQLVDirControl3 sqlVDirCtl = new SQLVDIRLib.SQLVDirControl3();

//Соединяемся с сервером localhost и дефолтовым web-сайтом
sqlVDirCtl.Connect("localhost", "1");

//Получаем коллекцию виртуальных директорий
SQLVDIRLib.IVRootEnum2 sqlVDirs = sqlVDirCtl.SQLVDirs;

//Если вирт.дир. с таким именем существует, удаляем
if (sqlVDirs.get_Exists(SQLVirtDirName))
sqlVDirs.xxxRemoveVirtualDirectory(SQLVirtDirName);

//Создаем новую вирт.дир.
SQLVDIRLib.ISQLVDir2 sqlVDir = sqlVDirs.AddVirtualDirectory(SQLVirtDirName);

//1-я закладка New Virtual Directory Properties
sqlVDir.PhysicalPath = SQLVirtDirPhysPath;

//2-я закладка New Virtual Directory Properties
sqlVDir.SecurityMode = SQLVDIRLib.tagSecurityModes.smINTEGRATED;

//3-я закладка New Virtual Directory Properties
sqlVDir.ServerName = SQLSrv; sqlVDir.DatabaseName = DBName;

//4-я закладка New Virtual Directory Properties
sqlVDir.AllowFlags =
SQLVDIRLib.tagAllowFlags.afURL_QUERIES | SQLVDIRLib.tagAllowFlags.afTEMPLATES |
SQLVDIRLib.tagAllowFlags.afXPATH | SQLVDIRLib.tagAllowFlags.afPOST;

//5-я закладка New Virtual Directory Properties
//Получить коллекцию виртуальных имен для данной вирт.дир.
SQLVDIRLib.IVirtualNames2 sqlVDirVNames = sqlVDir.VirtualNames;
//Добавляем новые виртуальные имена
//Параметры: название поддир., ее тип, отн.физ.путь (там, где не нужно - пустая строка)
sqlVDirVNames.xxxAddVirtualName("dbobj", SQLVDIRLib.tagVirtualTypes.vtDBOBJECT, "");
sqlVDirVNames.xxxAddVirtualName("schem", SQLVDIRLib.tagVirtualTypes.vtSCHEMA, "Schemas");
sqlVDirVNames.xxxAddVirtualName("templ", SQLVDIRLib.tagVirtualTypes.vtTEMPLATE, "Templates");

SQLVDIRLib.IVirtualName sqlWebSvc = sqlVDirVNames.xxxAddVirtualName("websvc",
SQLVDIRLib.tagVirtualTypes.vtSOAP, "WebService");
sqlWebSvc.WebService = "SQLSoapSample"; sqlWebSvc.Domain = "localhost";

sqlWebSvc.SoapMethods.AddStoredProcMethod("ЗаказыКлиентаЗаГод", "CustomerOrdersForYear", 1, 1,
SQLVDIRLib.tagSoapMethodOutput.smoDATASET);
sqlWebSvc.SoapMethods.AddTemplateMethod("ПримерВызоваШаблона",
"..\\Templates\\XMLTemplate1.xml", 1);

sqlWebSvc.SoapMethods.GenerateConfig(0, 0); sqlWebSvc.SoapMethods.GenerateWSDL();

//Отсоединяемся от сервера
sqlVDirCtl.Disconnect();
}
Скрипт 14

Некоторые пояснения к тому, что здесь делалось. Для начала, чтобы воспользоваться функциональностью объекта SQLVDirControl из .NET Framework, нужно импортировать библиотеку классов соответствующей СОМовской dll'и - ...\Program Files\Common Files\System\Ole DB\sqlvdr3.dll. Процесс работы с этим объектом практически повторяет шаги, выполняемые из графического интерфейса визарда по созданию виртуальной директории SQL Server. Для обслуживания анонимных соединений с SQL Server по HTTP следует выбрать учетную запись Windows, авторизованную на доступ к SQL Server, либо SQLный логин, под которыми анонимусы будут ходить на SQL Server. В локальных интранет-сценариях лучше выбирать Windows Integrated Authentication, когда пользователь под своей доменной учетной записью сквозным образом авторизуется на IIS и далее на SQL Server (что соответствует его интегрированному или смешанному режиму безопасности). Basic Authentication (Clear Text) to SQL Server account, как следует из названия, будет запрашивать у пользователя при обращении к виртуальной директории его логин и пароль на SQL Server.

Так же, как базовая аутентификация на IISе, они передаются в открытом виде, поэтому при выборе этого сценария следует применять HTTPS. Виртуальная директория, естественно, должна быть привязана к определенному экземпляру SQL Server (он может стоять на машине, отличной от той, где установлен IIS) и к определенной базе данных на нем. Естественно, все механизмы проверки полномочий при обращении к объектам сервера и совершении на нем каких-либо действий, остаются в силе, однако еще до того, как дело дойдет до SQL Server, можно ввести дополнительные ограничения на уровне виртуальной директории. Опция Allow URL queries (SQL, template) разрешает непосредственно по HTTP запрашивать SQL Server при помощи SELECT ... FOR XML или передав текст шаблона. По умолчанию она запрещена, чтобы кто ни попадя из Интернет не приставал к SQL Server с произвольными запросами. Конечно, если у человека нет прав, он и так будет послан, но зачем напрягать сервер и заставлять его тратить время на проверку. Если вы все-таки хотите открыть предыдущую опцию только для запросов на чтение, запретите Allow posted updategrams - при этом будут отвергаться все ad hoc (т.е. те, которые приходят от пользователя, как было показано в конце п.10, а не те, что подготовили вы и сохранили в поддиректории типа template) шаблоны типа UpdateGrams. Allow XPath - то же, что первая опция, но для ad hoc XPath-запросов. Allow template queries - разрешить пользователям вызывать хранящиеся на сервере шаблоны (а не направлять ему свои). Очень похоже на ситуацию, когда при классическом доступе пользователям даются права только на вызовы определенных хранимых процедур, чтобы даже с теми объектами, на которые у них есть права, они не творили, что хотели, а действовали в рамках предусмотренных бизнес-правил. Allow POST - поскольку очень длинный запрос в GET не пролезет, то можно сделать доступной посылку запросов методом POST. Maximum size of POST queries (in kilobytes) позволяет тем не менее ограничить сверху длину запроса.

Виртуальная директория может иметь подкаталоги следующих типов. Template - в них хранятся шаблоны (п.10) - параметризованные сочетания SQL- и XPath-запросов, которые пользователи могут вызывать подобно хранимым процедурам. Schema - для хранения аннотированных схем (п.9), определяющих различные XML-представления реляционной базы данных, привязанной к текущей виртуальной директории, что дает возможность запрашивать ее при помощи XPath. Dbobject - для адресации XPath-запросов напрямую без аннотированной схемы к объектам БД (используется представление по умолчанию, рассмотренное в п.8). Поскольку в данном случае схем хранить не требуется, никакой физической директории ему не отвечает. SOAP - рассмотрение этого типа подкаталогов, равно как и все, что касается конфигурирования поддержки Web-сервисов, мы отложим до следующего параграфа.

После того, как виртуальная директория создана, все способы XML-взаимодействия с SQL Server: запросы SELECT ... FOR XML, XPath-запросы напрямую и через аннотированные схемы, XML-шаблоны, включая UpdateGrams, - становятся доступны через HTTP.

FOR XML-запрос из Скриптов 3, 4 передается, например, так:

https://localhost/SQLXML3?sql=SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE c.ContactName = ? and year(o.OrderDate) = ? FOR XML AUTO&x=Maria Anders&y=1997&root=root
 
Здесь SQLXML3 - название виртуальной директории SQL Server, параметр sql содержит текст FOR XML-запроса, параметры x и y передают значения параметров запроса (имена параметров запроса в данном случае несущественны в отличие от, например, шаблонов), параметр root - название корневого тэга. В параметре xsl может передаваться имя локального файла, содержащего XSL-преобразование с тем, чтобы результаты запроса сразу получались отфильтрованными и отформатированными. Скрипт 15 демонстрирует отправку этого запроса методом GET, как если бы вышеприведенная строка была просто набрана в URL-строке браузера. Web-сервер отвечает Stream'ом, в котором передается XML-результат запроса от SQL Server. Этот Stream загружается в XML-документ, с которым затем работает приложение.

static void Execute_FORXMLQuery_HTTPGET()
{
HttpWebRequest rqst = (HttpWebRequest) WebRequest.Create("https://localhost/SQLXML3?sql=SELECT
c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID
WHERE c.ContactName = ? and year(o.OrderDate) = ?
FOR XML AUTO&x=Maria Anders&y=1997&root=root");
rqst.Method = "GET";
rqst.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse rspn = (HttpWebResponse) rqst.GetResponse();
Stream s = rspn.GetResponseStream();
XmlDocument xml = new XmlDocument();
xml.Load(s); rspn.Close();
...
}
Скрипт 15

Скрипт 16 делает то же самое методом POST. Строка запроса передается не в URL, а во входном Stream'е, предварительно запрошенном у Web-сервера. Результаты получаются и обрабатываются аналогично предыдущему примеру.


static void Execute_FORXMLQuery_HTTPPOST()
{
string query = "sql=SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID WHERE c.ContactName = ?
and year(o.OrderDate) = ? FOR XML AUTO&x=Maria Anders&y=1997&root=root";
HttpWebRequest rqst = (HttpWebRequest) WebRequest.Create("https://localhost/SQLXML3");
rqst.Method = "POST";
rqst.Credentials = CredentialCache.DefaultCredentials;
rqst.ContentLength = query.Length;
StreamWriter sw = new StreamWriter(rqst.GetRequestStream());
sw.Write(query); sw.Close();
HttpWebResponse rspn = (HttpWebResponse) rqst.GetResponse();
XmlDocument xml = new XmlDocument();
xml.Load(rspn.GetResponseStream()); rspn.Close();
...
}
Скрипт 16

Форма, представленная в Скрипте 17, функционально эквивалентна 16. В случае формы параметры Web-запроса передаются в полях (связка "имя параметра / значение" соответствует атрибутам name / value). Htm-файл с формой может находиться в любом локальном каталоге, т.к. он служит только средством передачи Web-запроса с клиента на Web-сервер. При ссылках на аннотированные схемы (например, когда из формы выполняется не SQL-запрос, а шаблон) следует иметь в виду, что схемы должны находиться либо в текущем каталоге, либо в каком-нибудь из вложенных в него подкаталогов. Излишне напоминать, что в случае использования русских названий и других расширенных символов файл нужно сохранять не в ANSI, а как UTF-8 или подобном формате.


<head>
<TITLE>Пример FOR XML через POST из формы</TITLE>
</head>
<body>
Демонстрирует отправку FOR XML-запроса методом POST из формы
<form action="https://localhost/sqlxml3" method="POST">
<B>Имя клиента</B> <input type="text" name="name">
<br>
<B>Год</B> <input type="text" name="year">
<input type="hidden" name="contenttype" value="text/xml">
<input type="hidden" name="sql" value="
SELECT c.ContactName, c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders o
ON c.CustomerID = o.CustomerID WHERE c.ContactName like ? + '%' and year(o.OrderDate) = ?
FOR XML AUTO
">
<input type="hidden" name="Root" value="Корень">
<p><input type="submit" value="Сабмит">
</form>
</body>
Скрипт 17

Все остальные типы XML-запросов к SQL Server передаются по HTTP совершенно аналогично Скриптам 15, 16. Различия составляют только правила формирования строки запроса к Web-серверу. Вот пример передачи по HTTP прямого XPath-запроса из п.8:

https://localhost/SQLXML3/dbobj/Customers[@ContactName>=$НачБуква]/@ContactName?НачБуква='X'
Dbobj - подкаталог виртуальной директории типа Dbobject, определенной при создании виртуальной директории SQLXML3. XPath-запрос входит в URI, параметром служит параметр XPath-запроса.

Вот пример XPath-запроса через аннотированную схему, из п.9 (Скрипт 10).

https://localhost/SQLXML3/schem/SQLSchema1.xsd/
Клиент[Адрес/Страна='Spain'%20or%20Адрес/Страна='France']?root=Клиенты
При желании можно передать параметры:
https://localhost/SQLXML3/schem/SQLSchema1.xsd/
Клиент[Адрес/Страна=$Country]?Country=Spain&root=Клиенты
Schem - виртуальный подкаталог типа Schema, предполагается, что файл с аннотированной схемой SQLSchema1.xsd находится в нем. В качестве еще одного параметра в строке URL можно передавать ...&xsl=<Путь к файлу с XSLT-преобразованием>.
Шаблоны должны храниться в подкаталоге типа Template. Обращение к шаблону происходит следующим образом:

https://localhost/SQLXML3/templ/XmlTemplate1.xml?Колво=20
По сути, это HTTP-реализация Скрипта 11: происходит вызов шаблона XmlTemplate1 и передача ему одного из параметров (для другого используется значение по умолчанию). Текст шаблона приводился на рис.6. Кроме того, как мы видели в завершение п.10, можно вызывать не только шаблоны, хранящиеся на сервере, но (при наличии прав), передать в HTTP-запросе текст собственного шаблона, который сервер выполнит аналогично хранимым шаблонам (т.н. ad hoc-шаблон). Это очень похоже на передачу FOR XML-запроса, только вместо параметра sql используется параметр template:
https://localhost/SQLXML3?template=<Root xmlns:sql='urn:schemas-microsoft-com:xml-sql'><sql:query> SELECT TOP 1 * FROM Employees FOR XML AUTO</sql:query></Root>
Большие ad-hoc шаблоны проще передавать методом POST:

static void Execute_FORXMLQuery_HTTPPOST()
{
string query = "template=<Солянка xmlns:sql='urn:schemas-microsoft-com:xml-sql'>
<sql:header><sql:param name='q'></sql:param><sql:param name='s'>1000
</sql:param></sql:header>
<sql:query client-side-xml='0'>SELECT TOP 1 * FROM Employees FOR XML AUTO</sql:query>
<sql:query client-side-xml='1'>SELECT c.ContactName, COUNT(o.OrderDate) AS 'Кол-во' F
ROM Customers c INNER JOIN Orders o ON c.CustomerID = o.CustomerID GROUP BY c.ContactName
HAVING COUNT(o.OrderDate) >= @q FOR XML NESTED</sql:query>
<sql:xpath-query mapping-schema='Schemas\\SQLSchema1.xsd'>
Клиент[number(Заказы/Заказ/Стоимость)>$s]
</sql:xpath-query>
<sql:xpath-query mapping-schema='Schemas\\SQLSchema2.xsd'>Сотрудник</sql:xpath-query>
<Солянка>&q=20";
HttpWebRequest rqst = (HttpWebRequest) WebRequest.Create("https://localhost/SQLXML3");
rqst.Method = "POST";
rqst.ContentType = "application/x-www-form-urlencoded";
byte[] postData = System.Text.Encoding.UTF8.GetBytes(query);
rqst.ContentLength = postData.Length;
rqst.Credentials = CredentialCache.DefaultCredentials;
rqst.GetRequestStream().Write(postData, 0, postData.Length);
HttpWebResponse rspn = (HttpWebResponse) rqst.GetResponse();
XmlDocument xml = new XmlDocument();
xml.Load(rspn.GetResponseStream()); rspn.Close();
...
}
Скрипт 18

 
« Предыдущая статья   Следующая статья »