Вверх ↑
Этот топик читают: Гость
Ответов: 655
Рейтинг: 18
#1: 2018-03-04 15:12:10 ЛС | профиль | цитата
Добрый день!

Система Windows 8.1, web сервер Apache 2.2, выполняю запрос к CGI, в качестве скрипта консольное приложение hiasm.
Данные POST и GET успешно приходят, все работает. Понадобилось организовать загрузку файлов на сервер, пытаюсь разобраться как сохранить файл на стороне сервера.
Пример страницы:

‹!DOCTYPE html›‹html›
‹head›
‹meta charset="utf-8"›
‹title›Отправка файла на сервер‹/title›
‹/head›
‹body›
‹form enctype="multipart/form-data" action="http://localhost/cgi-bin/project18.exe" method="post"›
‹p›‹input type="file" name="f"›
‹input type="submit" value="Отправить"›‹/p›
‹/form›
‹/body›
‹/html›
Пример приложения:

Add(Console,2953706,119,105){
Method=1
Point(InParams)
link(onStart,16687104:doEvent1,[])
}
Add(StrList,10748523,217,105)
{
FileName="123.txt"
link(onChange,10748523:doSave,[(258,111)(258,156)(205,156)(205,146)])
link(Str,2953706:InParams,[(223,93)(308,93)(308,206)(139,206)])
}
Add(StrCatDelim,14134273,217,168)
{
Delimiter="\r\n\r\n"
Str1="Content-Type: text/html"
Str2="234234"
link(onStrCatDlm,4219790:In,[])
}
Add(LineBreak,15018086,63,105)
{
Caption="send"
link(Out,2953706:doWrite,[])
Primary=[4219790,203,63]
}
Add(Hub,16687104,168,105)
{
link(onEvent1,10748523:doAdd,[])
link(onEvent2,14134273:doStrCatDlm,[(193,118)(193,174)])
}

При отправке текстового файла 2000 строк, данные обрезаются на ~ 700 строках. На сколько мне известно данные посылаются с заголовком multipart т.е. в несколько частей, вот как раз первая часть данных поступает, а остальные нет. Помогите побороть.

Редактировалось 3 раз(а), последний 2018-03-04 15:13:38
карма: 0

0
vip
#1.1контекстная реклама от партнеров
Ответов: 4612
Рейтинг: 746
#2: 2018-03-05 12:03:23 ЛС | профиль | цитата
Gunnman писал(а):
На сколько мне известно данные посылаются с заголовком multipart т.е. в несколько частей
Никогда не обрабатывал запросы с помощью консольной программы. В консольную программу приходит полный, не отформатированный запрос (заголовки, пустая строка, boundary, содержимое файла)? Если да, то ты должен парсить этот запрос, чтобы вытащить содержимое файла. А то, какими частями выдаётся результат в консольной программе, зависит от какими порциями запрос приходит из сети, какими порциями Apache подаёт его в программу и какими порциями код чтения из консоли читает данные. В любом случае тебе надо накопить полный запрос, затем разобрать его. Из штатных компонентов MemoryStream подойдёт, но нужно будет как-то определять конец данных, чтобы знать, когда принят весь файл (например, по заголовку Content-Length).

Редактировалось 2 раз(а), последний 2018-03-05 12:05:59
карма: 26

0
Ответов: 655
Рейтинг: 18
#3: 2018-03-05 15:56:34 ЛС | профиль | цитата
Netspirit, проблему с накоплением данных я уже решил с помощью вашего компонента DataAccumulator. Нет, заголовки Apache не передает, он их помещает в переменные из которых я их получаю. Собственно все работает но столкнулся с ужасной производительностью.
На web странице я прикрепляю файл, который преобразуется в Base64, затем файл отправляется на сервер.
Данные приходят частично в виде URLEncode, например текстовый файл 123.txt с текстом "1234567890данные1234567890данные1234567890данные" браузером будет отправлен как:
"data:text/plain;base64,MTIzNDU2Nzg5MOTg7e375TEyMzQ1Njc4OTDk4O3t++UxMjM0NTY3ODkw5ODt7fvlMTIzNDU2Nzg5MOTg7e375TEyMzQ1Njc4OTDk4O3t++U="
а с точки Read элемента Console я эти данные получаю как:
filename=123.txt&data=data%3Atext%2Fplain%3Bbase64%2CMTIzNDU2Nzg5MOTg7e375TEyMzQ1Njc4OTDk4O3t%2B%2BUxMjM0NTY3ODkw5ODt7fvlMTIzNDU2Nzg5MOTg7e375TEyMzQ1Njc4OTDk4O3t%2B%2BU%3D
Вопрос: как сделать URLDecode? Я пробовал Charset...но при файле 5-6Mb процесс занимает почти минуту...
П.с. в самом компоненте Console есть THIConsole.Decode с комментарием "Преобразует символы, записанные в виде %2B к правильному виду", но она не вызывается с точки Read.

Пробовал так:
procedure THIConsole._var_Read;
var s:string;
begin
ReadLn(s);
dtString(_Data,Decode(s));
end;
Не заработало. Прошу помочь, подскажите как Decode в Console для точки Read задействовать или как URLDecode сделать быстрее чем в Charset...
карма: 0

0
Ответов: 655
Рейтинг: 18
#4: 2018-03-05 17:04:41 ЛС | профиль | цитата

Add(Console,2953706,186,100)
{
Method=1
link(onStart,7839409:doEvent1,[])
}
Add(WaitObject,13558677,428,114)
{
link(ObjHandle,14477716:ObjHandle,[])
}
Add(Events,14477716,428,65)
{
Name=""
}
Add(Hub,7839409,232,100)
{
OutCount=3
link(onEvent1,14477716:doCreate,[(265,106)(265,71)])
link(onEvent2,7914081:In,[])
link(onEvent3,13558677:doWait,[])
}
Add(LineBreak,13536257,114,175)
{
Caption="next"
link(Out,13204451:doConvert,[])
Primary=[7914081,167,-68]
}
Add(FileStream,13606055,329,161)
{
FileName="test.txt"
Mode=2
AutoCopy=0
Point(doPosition)
Point(doCopyFromStream)
}
Add(StreamConvertor,13204451,186,175)
{
Mode=7
link(onResult,964423:doEvent1,[])
link(Data,2953706:Read,[])
}
Add(DoData,16436871,261,175)
{
link(onEventData,13606055:doPosition,[])
link(Data,13606055:Size,[(267,149)(392,149)(392,197)(342,197)])
}
Add(Hub,964423,234,175)
{
link(onEvent1,16436871:doData,[])
link(onEvent2,13606055:doCopyFromStream,[])
}


Вот схема без всего лишнего, просто прием файла через STDIN и запись в файл, ооочень медленно. С чем это может быть связанно?
карма: 0

0
Ответов: 4612
Рейтинг: 746
#5: 2018-03-05 17:26:33 ЛС | профиль | цитата
Gunnman писал(а):
как URLDecode сделать быстрее чем в Charset...
Надо доработать компонент, хотя этот метод, вроде, и так достаточно оптимальный.

--- Добавлено в 2018-03-05 17:51:48

Проверил - у меня даже на медленном компьютере оно работает со скоростью 24 Мб/с, так что это не медленно.

Редактировалось 2 раз(а), последний 2018-03-05 17:51:48
карма: 26

0
Ответов: 655
Рейтинг: 18
#6: 2018-03-05 19:06:43 ЛС | профиль | цитата
Netspirit, да, проблема не в нем подтверждаю, не туда смотрел.
Подскажите, CGI использует STDIN и STDOUT для работы с скриптами, приложениями. Я передаю через в консольное приложение hiasm метод POST, именно тут и происходят основные тормоза, файл размером 5mb передается в районе минуты, загрузка ЦП при этом 25-40%...
Код для нашего Console как я понял взят тут http://www.delphimaster.ru/articles/cgi.html. Куда копать?
карма: 0

0
Ответов: 4612
Рейтинг: 746
#7: 2018-03-06 11:54:22 ЛС | профиль | цитата
STDIN, STDOUT (pipe) - это межпроцессный обмен данными, скорость может быть ниже, чем ожидалось. Посмотри мои компоненты PipeClient\PipeServer - с какой скоростью они обмениваются данными. Если больше, значит причина может быть в реализации чтения консоли в программе либо в скорости, с которой Apache туда данные записывает (может у него есть настройки, ограничивающие загрузку процессора/памяти).
карма: 26

0
Ответов: 655
Рейтинг: 18
#8: 2018-03-07 12:14:20 ЛС | профиль | цитата
Netspirit, Ваши pipes работаю отлично,пользуемся)))

Тормоза победил, дело было в антивирусе который рьяно проверял новые файлы. Не могли бы вы подсказать по компоненту Console следующий момент - я передаю данные методом POST, мне известен размер файла. Пытаюсь считать из с точки InParams необходимое кол-во байт.
В коде компонента вижу, что считывание STDIN происходит 1 раз при инициализации компонента. Однако Apache и любой другой Web сервер будет передать данные частями.

Вод процедура инициализации.



procedure THIConsole.InitParams;
var
ss,STR:string;
StdIn, Size, Actual: cardinal;
begin
if _prop_Method = 0 then begin
// Читаем переданные параметры из переменной окружения
SetLength( SS, 10000 );
GetEnvironmentVariable( 'QUERY_STRING', @SS[1], 2000 );
InParams := PChar( @SS[1] )
end else begin
// Читаем переданные параметры из STDIN
StdIn := GetStdHandle( STD_INPUT_HANDLE );
Size := SetFilePointer( StdIn, 0, nil, FILE_END );
SetFilePointer( StdIn, 0, nil, FILE_BEGIN );
if (Size <= 0) then Exit;
SetLength(InParams, Size);
ReadFile( StdIn, InParams[1], Size, Actual, nil );
end;
end;

Прошу помочь, я добавил верхнюю точку "PLength" (длинна данных для POST), как мне сделать чтобы InitParams читал до конца длинны данных из PLength? Сейчас считываются первые поступившие данные и все.
Вопрос номер 2 как вывести считанные данные не на нижнюю точку (InParams) а поток (допустим точка будет называться onPost)
карма: 0

0
Ответов: 4612
Рейтинг: 746
#9: 2018-03-07 12:20:44 ЛС | профиль | цитата
Gunnman писал(а):
В коде компонента вижу, что считывание STDIN происходит 1 раз при инициализации компонента.
Это не считывание STDIN, это считывание командной строки при запуске - она все равно не меняется в процессе работы приложеня. (Точнее, в зависимости от свойства Method: при "Post" таки читает из консоли, но по каким соглашениям это происходит не знаю) Чтение консоли происходит при запросе данных с точки Read:
procedure THIConsole._var_Read;
var s:string;
begin
ReadLn(s);
dtString(_Data,s);
end;
(я, правда, так и не понял, где находится функция ReadLn(), вероятно встроенная функция компилятора, которая читает из STDIN)
Редактировалось 6 раз(а), последний 2018-03-07 12:31:19
карма: 26

0
Ответов: 655
Рейтинг: 18
#10: 2018-03-07 12:32:08 ЛС | профиль | цитата
Netspirit, а командная строка это вроде и есть сам POST запрос , разве нет?
Вот тут http://mf.grsu.by/UchProc/konspekt/delphi_book/ch04/ch01 пункт 3.2 как раз описано про POST и STDIN

--- Добавлено в 2018-03-07 12:56:08

Я проверил при установке метода Post в Console, данные поступают на InParam.
Видимо мне действительно нужно сделать то что я описал в посте #8, не подскажете как это реализовать?

Редактировалось 2 раз(а), последний 2018-03-07 12:56:08
карма: 0

0
Ответов: 4612
Рейтинг: 746
#11: 2018-03-07 13:06:26 ЛС | профиль | цитата
Нужно читать не один раз, а в цикле. Пример чтения можно глянуть в компоненте WinExec:
THIWinExec.Read;

if not PeekNamedPipe(hPipeOutputRead,nil,0,nil,@Total,nil) then bError := true;
    if Total>0 then begin
bWait := false;
if ReadFile(hPipeOutputRead, pBuffer, 1024, Total, nil) then begin
pBuffer[Total] := #0;
_hi_OnEvent(_event_onConsoleResult, pBuffer);
end;
end;
Непонятно: а что, чтение в цикле из точки Read не даёт тот же результат (они же читают из одного и того же STDIN)? Просто соедини прочитанное из InParams с тем, что потом считаешь из Read (а лучше, поставить Method=Get, и всё считать из Read).
Редактировалось 2 раз(а), последний 2018-03-07 13:09:46
карма: 26

0
Ответов: 655
Рейтинг: 18
#12: 2018-03-07 13:29:51 ЛС | профиль | цитата
Непонятно: а что, чтение в цикле из точки Read не даёт тот же результат (они же читают из одного и того же STDIN)? Просто соедини прочитанное из InParams с тем, что потом считаешь из Read (а лучше, поставить Method=Get, и всё считать из Read).


Так то метод GET и строке состояния видно что отправляешь, + GET имеет ограничение на размер отправляемых данных, да и вообще он создан для получения данных, а мне требуется именно POST. Спасибо попробую прикрутить пример из WinExec

--- Добавлено в 2018-03-07 14:05:47


 while Actual <> 525051 do begin	
StdIn := GetStdHandle(STD_INPUT_HANDLE);
Size := SetFilePointer(StdIn, 0, nil, FILE_END);
SetFilePointer(StdIn, 0, nil, FILE_BEGIN);
SetLength(InParams,Size);
if (Size <= 0) then Exit;
if ReadFile(StdIn, InParams[1], Size, Actual, nil) then begin
_hi_OnEvent(_event_onPost, InParams);

подскажите где ошибка? вместо данных получаю пустоту

Редактировалось 1 раз(а), последний 2018-03-07 14:05:47
карма: 0

0
Ответов: 655
Рейтинг: 18
#13: 2018-03-07 16:19:55 ЛС | профиль | цитата
добавил точки PLength (Data) и onPost (event)

Как сделать цикл:

while (размер полученных данных) <> PLength do begin
StdIn := GetStdHandle(STD_INPUT_HANDLE);
Size := SetFilePointer(StdIn, 0, nil, FILE_END);
SetFilePointer(StdIn, 0, nil, FILE_BEGIN);
SetLength(InParams,Size);
if (Size <= 0) then Exit;
if ReadFile(StdIn, InParams[1], Size, Actual, nil) then begin
_hi_OnEvent(_event_onPost, InParams); - выдавать считанные данные в поток
карма: 0

0
Ответов: 4612
Рейтинг: 746
#14: 2018-03-07 17:17:49 ЛС | профиль | цитата
Сложно разбираться, не имея возможности тестировать результат. Вот схемка, в которой InlineCode использует возможности моих Pipe-компонентов (должны быть установлены, или в папке code лежать файл NSPipes.pas) для чтения STDIN:


Add(MainForm,2953706,21,105)
{
Caption="Console Out"
Position=1
link(onCreate,4153675:doStartRecv,[])
}
Add(Memo,16148154,231,119)
{
Left=5
Top=30
Width=380
Height=230
}
Add(InlineCode,4153675,147,119)
{
WorkPoints=#42:doStartRecv=Включить приём данных из STDIN|
EventPoints=#8:onResult|
Code=#15:unit HiAsmUnit;|0:|9:interface|0:|41:uses Windows, KOL, Share, Debug, NSPipes;|0:|4:type|33: THiAsmClass = class(TCustomPipe)|10: private|5: |12: protected|53: procedure DataReceived(Buf: TBuffer); override; |9: public|28: Data1, Data2:THI_Event;|28: onResult: THI_Event; |0:|58: procedure doStartRecv(var _Data: TData; Index: Word);|5: |24: constructor Create;|5: end;|0:|14:implementation|0:|31:constructor THiAsmClass.Create;|5:begin|51: inherited Create(GetStdHandle(STD_INPUT_HANDLE));|4:end;|0:|49:procedure THiAsmClass.DataReceived(Buf: TBuffer);|5:begin|39: _hi_OnEvent(onResult, Buf.GetString);|4:end;|0:|0:|66:procedure THiAsmClass.doStartRecv(var _Data: TData; Index: Word); |5:begin|17: StartReceiving;|4:end;|0:|4:end.|
link(onResult,16148154:doAdd,[])
}
Если программу запустить из WinExec.doConsoleExec, то данные, посылаемые в неё по doConsoleInpuт будут приниматься.

Аналогично, если поместить IC в консольную программу и запустить её из обычной командной строки. Только, поскольку чтение асинхронное, то в консольной программе надо не дать ей закрыться - с помощью Events+WaitObject:


Add(Console,2953706,56,105)
{
link(onStart,7839409:doEvent1,[])
}
Add(InlineCode,4153675,434,119)
{
WorkPoints=#42:doStartRecv=Включить приём данных из STDIN|
EventPoints=#8:onResult|
Code=#15:unit HiAsmUnit;|0:|9:interface|0:|41:uses Windows, KOL, Share, Debug, NSPipes;|0:|4:type|33: THiAsmClass = class(TCustomPipe)|10: private|5: |12: protected|53: procedure DataReceived(Buf: TBuffer); override; |9: public|28: Data1, Data2:THI_Event;|28: onResult: THI_Event; |0:|58: procedure doStartRecv(var _Data: TData; Index: Word);|5: |24: constructor Create;|5: end;|0:|14:implementation|0:|31:constructor THiAsmClass.Create;|5:begin|51: inherited Create(GetStdHandle(STD_INPUT_HANDLE));|4:end;|0:|49:procedure THiAsmClass.DataReceived(Buf: TBuffer);|5:begin|39: _hi_OnEvent(onResult, Buf.GetString);|4:end;|0:|0:|66:procedure THiAsmClass.doStartRecv(var _Data: TData; Index: Word); |5:begin|17: StartReceiving;|4:end;|0:|4:end.|
link(onResult,7027989:doEvent1,[])
}
Add(LineBreakEx,7207913,-7,105)
{
Caption="write"
Type=1
link(OnEvent,2953706:doWrite,[])
}
Add(DoData,16003574,448,77)
{
Data=String(Receiving... Type 'quit' to exit.\n)
link(onEventData,6736747:doWork,[])
}
Add(Hub,11466443,371,112)
{
link(onEvent1,16003574:doData,[(412,118)(412,83)])
link(onEvent2,4153675:doStartRecv,[])
}
Add(LineBreakEx,6736747,497,77)
{
Caption="write"
}
Add(InfoTip,14734582,161,21)
{
Info=#17:Не даём программе|23:завершиться без команды|
Width=169
Height=186
}
Add(WaitObject,13558677,280,140)
{
link(ObjHandle,14477716:ObjHandle,[])
}
Add(Events,14477716,280,70)
{
Name=""
}
Add(Hub,7839409,175,105)
{
OutCount=3
link(onEvent1,14477716:doCreate,[(205,111)(205,76)])
link(onEvent2,11466443:doEvent1,[])
link(onEvent3,13558677:doWait,[(205,125)(205,146)])
}
Add(LineBreakEx,15618268,224,77)
{
Caption="exit"
Type=1
link(OnEvent,14477716:doSet,[])
}
Add(LineBreakEx,2579876,644,119)
{
Caption="write"
}
Add(If_else,2523183,553,154)
{
Op1=String(quit\r\n)
link(onTrue,7248749:doWork,[])
}
Add(LineBreakEx,7248749,609,154)
{
Caption="exit"
}
Add(Hub,7027989,490,119)
{
link(onEvent1,8640077:doStrCat,[])
link(onEvent2,2523183:doCompare,[(528,132)(528,160)])
}
Add(StrCat,8640077,574,119)
{
Str1="Out>>> "
link(onStrCat,2579876:doWork,[])
}
(программа выдаёт обратно в консоль всё, что в неё посылают)
То же самое, только без сторонних компонентов с использованием точки Read:


Add(Console,2953706,56,105)
{
Title="Console read"
link(onStart,7839409:doEvent1,[])
}
Add(LineBreakEx,7207913,-49,105)
{
Caption="write"
Type=1
link(OnEvent,5526268:doStrCat,[])
}
Add(DoData,16003574,448,77)
{
Data=String(Receiving... Type 'quit' to exit.\n)
link(onEventData,6736747:doWork,[])
}
Add(Hub,11466443,371,112)
{
link(onEvent1,16003574:doData,[(412,118)(412,83)])
link(onEvent2,1368819:doStart,[])
}
Add(LineBreakEx,6736747,497,77)
{
Caption="write"
}
Add(InfoTip,14734582,161,21)
{
Info=#17:Не даём программе|23:завершиться без команды|
Width=169
Height=186
}
Add(WaitObject,13558677,280,140)
{
link(ObjHandle,14477716:ObjHandle,[])
}
Add(Events,14477716,280,70)
{
Name=""
}
Add(Hub,7839409,175,105)
{
OutCount=3
link(onEvent1,14477716:doCreate,[(205,111)(205,76)])
link(onEvent2,11466443:doEvent1,[])
link(onEvent3,13558677:doWait,[(205,125)(205,146)])
}
Add(LineBreakEx,15618268,224,77)
{
Caption="exit"
Type=1
link(OnEvent,14477716:doSet,[])
}
Add(LineBreakEx,2579876,1120,196)
{
Caption="write"
}
Add(StrCat,8640077,1050,196)
{
Str1="Out>>> "
link(onStrCat,2579876:doWork,[])
}
Add(Thread,1368819,679,119)
{
link(onExec,1735145:doRepeat,[])
}
Add(Repeat,1735145,791,119)
{
Op1=Integer(1)
Op2=Integer(1)
link(onRepeat,1149992:doValue,[])
}
Add(LineBreakEx,11811734,854,91)
{
Caption="read"
Type=2
}
Add(Memory,1149992,854,119)
{
Point(Data)
link(onData,4880918:doCompare,[])
link(Data,11811734:getVar,[])
}
Add(If_else,4880918,931,119)
{
Op1=String(quit)
link(onTrue,4930522:doEvent1,[])
link(onFalse,12772073:doData,[(982,132)(982,202)])
}
Add(Hub,4930522,1071,119)
{
link(onEvent1,12924904:doWork,[(1097,125)(1097,104)])
link(onEvent2,3851202:doWork,[])
}
Add(LineBreakEx,12924904,1113,98)
{
Caption="stop"
}
Add(LineBreakEx,15242824,735,126)
{
Caption="stop"
Type=1
link(OnEvent,1735145:doStop,[])
}
Add(LineBreakEx,3851202,1113,126)
{
Caption="exit"
}
Add(DoData,12772073,1001,196)
{
link(onEventData,8640077:doStrCat,[])
link(Data,1149992:Value,[(1007,174)(860,174)])
}
Add(InfoTip,7530493,651,42)
{
Info=#52:Ожидание ввода от пользователя в параллельном потоке|26:и выдача обратно в консоль|
Width=526
Height=207
}
Add(LineBreakEx,16443504,56,154)
{
Caption="read"
Type=3
link(_Data,2953706:Read,[])
}
Add(StrCat,5526268,7,105)
{
Str2="\n"
link(onStrCat,2953706:doWrite,[])
}

Редактировалось 3 раз(а), последний 2018-03-07 17:28:33
карма: 26

0
Ответов: 655
Рейтинг: 18
#15: 2018-03-07 17:27:26 ЛС | профиль | цитата
Netspirit, спасибо, но результат тот же - принимается пару только несколько Kb.
Как я уже говорил Apache передает данные частями, нужен цикл проверки поступления данных..
карма: 0

0
Сообщение
...
Прикрепленные файлы
(файлы не залиты)