Имя пользователя:
Пароль:  
Помощь | Регистрация | Забыли пароль?  

Показать сообщение отдельно

Старожил


Сообщения: 415
Благодарности: 257

Профиль | Отправить PM | Цитировать


Цитата Foreigner:
Не совсем понятно зачем выводить по одному байту »
Согласно спецификации. От клиента к серверу в заголовках запроса может придти любой диапазон байтов, и не обязательно, что он будет кратным использованному размеру блока для dd. Соответственно, обеспечить точный вывод запрошенного клиентом я могу только при блоке в 1 байт, но при этом очень проседает производительность. Текущая версия реализации выглядит так:
Код скрипта
Код: Выделить весь код
@Echo Off

:: Обрабатываем аргументы
Set Data=%*
:Next
For /F "tokens=1,* delims=;" %%A In ("%Data%") Do (
	For /F "tokens=1,2 delims==" %%C In ("%%A") Do (Set %%C=%%D)
	If Not "%%B"=="" Set Data=%%B&GoTo :Next
)
:: Обрабатываем get\post параметры
rem Выпилено на время теста, работаем только с заголовками запроса
:: Обрабатываем куки
If Not Defined H-Cookie Set "H-Cookie=dummy"]
:: 
Set H-Cookie|Findstr /C:"<" /C:">" /C:"&" /C:"|" /C:^"\^"^" /C:"(" /C:")" /C:"\^">nul&&(Call HTTP302Header err_badcookie&Exit)
For %%A In ("%H-Cookie:;=" "%") Do For /F "tokens=1,* delims==" %%A In ("%%~A") Do Set "%%A=%%B"
:: Проверяем авторизацию и доступ
If Not Defined Code Exit /B 3 &:: err_nosession
For %%A In (User Access LastSeen) Do Set "%%A="
For /F "tokens=1-3 delims=|" %%A In ('Call GetUserInfo %H-REMOTE_ADDR% %Code%') Do (
	Set "UserName=%%A"
	Set "Access=%%B"
	Set "LastLogin=%%C"
)
If Not Defined Access Exit /B 3 &:: err_nosession
If %Access% LSS 5 Exit /B 5 &:: err_noaccess


:: ДЛЯ ОТЛАДКИ
:: В рабочей копии скрипта все эти данные будут взяты из БД
Set File=D:\Projects\cmdserv\wwwroot\test.mp3
For %%A In ("%File%") Do (
	Set name=%%~nxA
	Set size=%%~zA
)
Set timestamp=1442271745

:: Приводим имя файла к рабочей кодировке, для отладки, в продакшене имя сразу будет в юникоде
For /F "eol= delims=" %%A In ('Echo %name%^|recode cp866..utf8') Do Set "nameUTF8=%%A"
:: Конвертируем время для Last-Modified
For /F "delims=" %%A In ('UnixTime2HTTPDate %timestamp%') Do (Set timestamp=%%A)
:: Получаем текущее время
For /F "delims=" %%A In ('GetHTTPDate') Do (Set datetime=%%A)
:: Получаем MIME-тип, таблица для сопоставления по умолчанию указана в переменных окружения враппера
For /F "delims=" %%A In ('GetMIMEType "%name%"') Do Set "mimetype=%%A"


:: Если указан Range переходим к обработчику запроса
If Defined H-Range GoTo :Range

:: Отдаём заголовки и файл, если не поступало запросов на частичную отдачу
	Echo HTTP/1.1 200 OK
	Echo Server: OWS/%__OWS_version%
	Echo Date: %datetime%
	Echo Accept-Ranges: bytes
	Echo Content-Type: %mimetype%
	Echo Content-Disposition: attachment; filename="%nameUTF8%"; size=%size%; creation-date=%timestamp%; modification-date=%timestamp%; read-date=%datetime%
	Echo Last-Modified: %timestamp%
	Echo Content-Length: %size%
	Echo Connection: close
	Echo.
Type "%File%"
Exit


:Range
SetLocal EnableDelayedExpansion
:: Случайная строка для использования в качестве разделителя
For /F "delims=" %%A In ('GetRandomName charset=3 length=16') Do (Set boundary=%%A)
:: Максимальное значение диапазона = размер-1байт
For /F "delims=" %%- In ('cc %size%-1') Do Set MaxRange=%%-
:: Имя временного файла, используемого если в одном запросе несколько диапазонов
Set TmpFile="%Temp%\~part%boundary%.tmp"
:: Выделяем диапазоны из заголовка, если тип не bytes - отдаём HTTP 416
For /F "tokens=1,* delims==" %%A In ("%H-Range%") Do If /I "%%A"=="bytes" (Set "Ranges=%%B") Else (Call :Err416)
Set Parts=0
For %%A In (%Ranges%) Do (
	Set rejected=false
	Set Range="%%A"
	For /F "tokens=1,2 delims=-" %%B In ("!Range:-="-"!") Do (
		Set "rMin=%%~B"
		Set "rMax=%%~C"
		:: Разворачиваем диапазоны с пустыми значениями
		If "!rMax!"=="" Set rMax=%MaxRange%
		cc !rMax! ^>= %size%|ExitCode&&Set rMax=%MaxRange%
		If "!rMin!"=="" For /F "delims=" %%- In ('cc %MaxRange%-!rMax!+1') Do (
			Set rMax=%MaxRange%
			Set rMin=%%-
		)
	)
	:: Проверки
	If Not Defined rMin Set rejected=true
	If Not Defined rMax Set rejected=true
	cc !rMin! ^<= !rMax!|ExitCode||Set rejected=true
	:: Если диапазон проходит по всем условиям, записываем аргументы для функции, выдающей часть файла
	If "!rejected!"=="false" (
		Set /A Parts+=1
		Set $Range=!rMin!-!rMax!/%size%
		Set Skip=!rMin!
		For /F "delims=" %%- In ('cc !rMax!+1-!rMin!') Do Set Length=%%-
		Set Part[!Parts!]="!Skip!" "!Length!" "!$Range!" "%boundary%" "!Parts!"
	)

)
:: Есть заголовок Range, но ни один из диапазонов невалиден? Отдаём HTTP 416
If "%Parts%"=="0" Call :Err416
Echo HTTP/1.1 206 Partial Content
Echo Server: OWS/%__OWS_version%
Echo Date: %datetime%
Echo Last-Modified: %timestamp%
Echo Accept-Ranges: bytes
If "%Parts%"=="1" (
	Echo Content-Type: %mimetype%
) Else (
	Echo Content-Type: multipart/byteranges; boundary=%boundary%
)
If "%Parts%"=="1" (
	Echo Content-Length: !Length!
	Echo Content-Range: bytes !rMin!-!rMax!/%size%
	Echo.
	:: Нет смысла играться со временным файлом, если диапазон один - выводим напрямую
	Call :PartialContent !Part[1]! 1
) Else (
	For /L %%A In (1,1,!Parts!) Do (
		Call :PartialContent !Part[%%A]! !Parts!>>%TmpFile%
	)
	:: Подсчитываем размер получившихся частей ВМЕСТЕ с заголовками и разделителями
	Call :PartsSize %TmpFile%
	Echo.
rem	Выводим контент и удаляем временный файл
	Type "%TmpFile%"
	Del "%TmpFile%" 2>nul
)
EndLocal
Exit

:: Выдаём часть файла согласно аргументам
:PartialContent (skip, length, range, boundary, part, parts)
rem Echo Skip: "%~1", Length: "%~2", Range: "%~3" --%~4-- [%~5 of %~6]
If "%~6"=="1" (
	dd if="%File%" ibs=1 skip=%~1 count=%~2 2>nul
	Exit /B
)
Echo --%~4
Echo Content-Type: %mimetype%
Echo Content-Range: bytes %~3
Echo.
dd if="%File%" ibs=1 skip=%~1 count=%~2 2>nul
:: Если это последний блок, пишем финальный разделитель
If "%~5"=="%~6" <nul Set /P "Echo=--%~4--"
Exit /B

:PartsSize (file)
For %%A In ("%~1") Do Echo Content-Length: %%~zA
Exit /B

:Err416
	Echo HTTP/1.1 416 Range Not Satisfiable
	Echo Server: OWS/%__OWS_version%
	Echo Date: %datetime%
	Echo Accept-Ranges: bytes
	Echo Content-Range: bytes */%size%
	Echo.
Exit
Если не найду другого решения, буду писать алгоритм выбора размера блока для dd, отдавать часть максимально большими блоками, а остаток точно доводить побайтово.
UPD: Прошу прощения, я не совсем верно вас понял при первом прочтении сообщения. Вы предложили использовать один блок сразу нужного размера, и это до меня только сейчас дошло. Да, это может сработать, сейчас протестирую.

greg zakharov, к сожалению, те же проблемы с производительностью, файлы обычно бывают от десятка мегабайт до гигабайта, а запрашиваемая часть файла варьируется в зависимости от браузера либо другого софта, делающего запрос, но редко превышает треть или четверть общего размера.

Последний раз редактировалось Anonymоus, 27-11-2015 в 20:31.


Отправлено: 20:13, 27-11-2015 | #6