Этюды на ASP NET2. Наблюдаем за своим домом с работы.
Сейчас большинство новых вэб-камер выпускется с IP-интерфейсом, например камеры от Genius. Они имеют прошитый web-серверок и предоставляют пользователю изображение по iP-адресу, например при входе на адрес http://192.168.0.233 мы и увидим собственно изображение.
Но как же показать это изображение на своем сайте?
- Во-первых, сайт как правило виден по внешнему адресу домашней локалки или офиса (и этот единственный адрес уже занят собственно сайтом - или фаерволом, публикующим сайт) - ну не покупать же второй внешний айпишник специально для веб-камеры?
- Во-вторых, надо же как-то обновлять изображение на сайте с web-камеры - ну не будет же юзер нажимать каждые полчекунды рефреш в браузере!
- В-третьих, при каждом рефреше браузера камера переспрашивает логин/пароль - ну не вводить же их каждый раз
- И в-четвертых - как же мултиплексировать на внешнем айпишнике множество камер? Айпишник-то один, а камер например двадцать.
Собственно, это и есть та самая практическая задачка, которую я однажды получил. Я решил ее буквально за пять минут - но правда, как говорят - кроме этих этих пяти минут потребовалась еще вся предыдущая жизнь. Решение этой задачки необычайно просто и, поскольку это явно практическая задачка - и возникает она у миллионов - я решил выложить публично ее решение в виде отдельного этюда.
Итак, создадим проект нового сайта в Visual Studio 2008. Для начала добавим в него (в App_Code) вот такой новый класс - который будет выдергивать рисунки с нашей камеры:
00001: Imports Microsoft.VisualBasic 00002: 00003: Public Class Class1 00004: Public Shared Property MaxImageLength() As Integer 00005: Get 00006: Return _MaxImageLength 00007: End Get 00008: Set(ByVal value As Integer) 00009: _MaxImageLength = value 00010: End Set 00011: End Property 00012: Shared _MaxImageLength As Integer = 150000 00013: 00014: 'считывает не более MaxImageLength байт без сообщений об ошибках - для пакетов (плюс добавлена аутентификация) 00015: Public Shared Function GetImage(ByVal URL As String, ByVal BasicAU_Name As String, ByVal BasicAU_Pass As String) As Byte() 00016: Try 00017: 'запрос по HTTP 00018: Dim PageRequest As System.Net.HttpWebRequest = CType(System.Net.WebRequest.Create(URL), System.Net.HttpWebRequest) 00019: Dim NetCredential As New Net.NetworkCredential(BasicAU_Name, BasicAU_Pass) 00020: PageRequest.Credentials = NetCredential 00021: 'Отправлен запрос 00022: Dim PageResponse As System.Net.HttpWebResponse = PageRequest.GetResponse 00023: 'Получен ответ 00024: Dim Reader As New System.IO.BinaryReader(PageResponse.GetResponseStream()) 00025: Dim GIF As Array = Reader.ReadBytes(_MaxImageLength) 00026: Reader.Close() 00027: 'Загружено в память 00028: Return GIF 00029: Catch ex As Exception 00030: Throw New Exception(ex.Message) 00031: End Try 00032: End Function 00033: 00034: 'Считывает с сообщениями об ошибках - для отддельных считываний 00035: Public Shared Function GetImage(ByVal URL As String) As Byte() 00036: Try 00037: 'запрос по HTTP 00038: Dim PageRequest As System.Net.HttpWebRequest = CType(System.Net.WebRequest.Create(URL), System.Net.HttpWebRequest) 00039: 'Отправлен запрос 00040: Dim PageResponse As System.Net.HttpWebResponse = PageRequest.GetResponse 00041: 'Получен ответ 00042: Dim Reader As New System.IO.BinaryReader(PageResponse.GetResponseStream()) 00043: Dim GIF As Array = Reader.ReadBytes(_MaxImageLength) 00044: Reader.Close() 00045: 'Загружено в память 00046: Return GIF 00047: Catch ex As Exception 00048: Throw New Exception(ex.Message) 00049: End Try 00050: End Function 00051: 00052: End Class
Как видите, это достаточно универсальный модуль с полиморфной функцией, которая позволит нам (при необходимости с аутенфикацией) дернуть с камеры рисункок. В режиме пакетной работы - как например для пауков - нам не нужны тут прерывания, но нужно ограничение по длине респонза - для борьбы с антипаучьими технологиями сайтов. Для нашего применения - эти усложнения нам не будут нужны.
Следующий компонент нашей мини-пирамидки - это хандлер, из которого собственно и будет вызываться наш класс. Итак, добавим в наш проект хандлер PROXY.ASHX:
00001: <%@ WebHandler Language="VB" Class="proxy" %> 00002: 00003: Imports System 00004: Imports System.Web 00005: 00006: Public Class proxy : Implements IHttpHandler 00007: 00008: Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest 00009: context.Response.ContentType = "image/bmp" 00010: Try 00011: If HttpContext.Current.Request("login") Is Nothing And HttpContext.Current.Request("pass") Is Nothing Then 00012: HttpContext.Current.Response.BinaryWrite(Class1.GetImage(HttpContext.Current.Request("Url"))) 00013: Else 00014: HttpContext.Current.Response.BinaryWrite(Class1.GetImage(HttpContext.Current.Request("Url"), HttpContext.Current.Request("login"), HttpContext.Current.Request("pass"))) 00015: End If 00016: Catch ex As Exception 00017: context.Response.ContentType = "text/plain" 00018: context.Response.Write(ex.Message) 00019: End Try 00020: End Sub 00021: 00022: Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable 00023: Get 00024: Return False 00025: End Get 00026: End Property 00027: 00028: End Class
И далее создадим две странички - одну для одноразового тестового обращения к веб-камере - Default.aspx и вторую - для периодических обращений - которую мы уже и будем дергать со своего сайта, который висит на внешнем интерфейсе.
00001: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %> 00002: 00003: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 00004: 00005: <html xmlns="http://www.w3.org/1999/xhtml"> 00006: <head runat="server"> 00007: <title>Web Proxy</title> 00008: </head> 00009: <body> 00010: <form id="form1" runat="server"> 00011: <div> 00012: <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> 00013: <asp:View ID="View1" runat="server"> 00014: <asp:Label ID="Label1" runat="server" Text="URL рисунка (http://сервер/каталог/рисунок)"></asp:Label> 00015: <br /> 00016: <asp:TextBox ID="TextBox1" runat="server" Width="700px"></asp:TextBox> 00017: <br /> 00018: <asp:Label ID="Label2" runat="server" Text="Логин"></asp:Label> 00019: <br /> 00020: <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox> 00021: <br /> 00022: <asp:Label ID="Label3" runat="server" Text="Пароль"></asp:Label> 00023: <br /> 00024: <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox> 00025: <br /> 00026: (если оба поля оставлены пустыми - будет сделана попытка считать рисунок без аутентификации) 00027: <br /> 00028: <br /> 00029: <asp:Button ID="Button1" runat="server" Text="Считать" /> 00030: </asp:View> 00031: <asp:View ID="View2" runat="server"> 00032: <asp:Image ID="Image1" runat="server" /> 00033: <br /> 00034: <asp:Button ID="Button2" runat="server" Text="Повторить" /> 00035: </asp:View> 00036: </asp:MultiView> 00037: 00038: </div> 00039: </form> 00040: </body> 00041: </html> 00001: 00002: Partial Class _Default 00003: Inherits System.Web.UI.Page 00004: 00005: Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 00006: MultiView1.ActiveViewIndex = 1 00007: Response.Redirect("proxy.ashx?url=" & TextBox1.Text & "&login=" & TextBox2.Text & "&pass=" & TextBox3.Text) 00008: End Sub 00009: 00010: Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click 00011: MultiView1.ActiveViewIndex = 0 00012: End Sub 00013: End Class 00001: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Index.aspx.vb" Inherits="Index" %> 00002: 00003: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 00004: 00005: <html xmlns="http://www.w3.org/1999/xhtml"> 00006: <head runat="server"> 00007: <title>Web-Camera</title> 00008: </head> 00009: <body> 00010: <form id="form1" runat="server"> 00011: <div> 00012: 00013: </div> 00014: </form> 00015: </body> 00016: </html> 00001: 00002: Partial Class Index 00003: Inherits System.Web.UI.Page 00004: 00005: Protected Sub Index_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00006: Response.Redirect("proxy.ashx?url=http://192.168.0.233/jpg/image.jpg&login=webuser&pass=webpass") 00007: End Sub 00008: End Class
В итоге этих простеньких манипуляций у нас должен получится вот такой крохотный сайтик:
И он должен работать! Вводя правильную аутентификацию - мы сможем увидеть на нем изображение с Web-камеры.
Осталось вытащить это изображение из локалки на внешний айпишник - и сделать периодическое обновление этого изображения.
Для этого на нашем внешнем сайте (на котором могут переключаться и десятки и сотни вот таких веб-камер) - добавляем вот такой код:
00101: 00102: <img src="http://XXX.YYY.ZZZ.NNN:MMM/proxy.ashx?url=http://192.168.0.233/jpg/image.jpg&login=webuser&pass=webpass" id="Image1" width="800px" height="600px" /> 00103: <asp:Image ID="ImageButton1" runat="server" ImageUrl="~/video1.gif" /> 00104: 00105: 00106: <script language="javascript" type="text/javascript"> 00107: function timer_process() 00108: { 00109: var Image1 = document.getElementById('Image1'); 00110: Image1.src='http://XXX.YYY.ZZZ.NNN:MMM/proxy.ashx?url=http://192.168.0.233/jpg/image.jpg&login=webuser&pass=webpass&i=' + (new Date()).getTime(); 00111: window.setTimeout("timer_process()", 500); 00112: 00113: } 00114: 00115: </script>
Это собственно единократная загрузка рисунка и Язва-скрип процедурка устаревания странички по таймауту.
Обратите внимание тут на пару моментов - XXX.YYY.ZZZ.NNN:MMM - это наш внешний айпишник и порт - где мы запустили наш сайт, созданный на предыдущем шаге. Или порт откуда мапируюся реквесты фаерволом на наш прокси-сайт, созданный выше. Кроме того, обратите внимание как тут я решил вопрос с кешированием рисунков в браузере - просто докрутил еще один параметр.
И чего тут не хватает? А не хватает последнего ключика головоломки - кода, запускающего процесс устаревания странички timer_process().
Для разнообразия внесем запускающий код в программный текст странички:
00001: 00002: Partial Class video1 00003: Inherits System.Web.UI.Page 00004: 00005: Protected Sub video1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00006: If Not IsPostBack Then 00007: ClientScript.RegisterClientScriptBlock(Me.GetType, "StartTimer", "<script language='javascript' type='text/javascript'>" & vbCrLf & _ 00008: "window.setTimeout('timer_process()', 500);" & vbCrLf & _ 00009: "</script>") 00010: End If 00011: End Sub 00012: End Class
Вот теперь все сложилось. Причем даже так, что формируя параметры вызова PROXY.ASHX, мы можем с этой внешней странички нашего адреса видеть даже десятки или сотни камер - висящих у нас на внутренних адресах - в моем случае это для первой камеры 192.168.0.233.
Пример практического применения? Пожалуйста.
Ставите дома 10 камер (каждая на своем внутреннем айпишнике). И с работы входите по своему внешнему айпишнику на свой сайт - и наблюдаете с 10-ти позиций, чем там занимается дома ваша жена в ваше отсутствие. Для этого надо лишь пару простейших IP-камер, хабчик, и какой-нибудь нешумный barebone - в который и надо загрузить описанные тут проги. В реальности описанная здесь система видеонаблюдения может быть скомбинирована с электронным замком.
|