Serving files with IdHTTPServer when the files are being written - delphi

I'm working with a TIdHTTPServer to serve files to clients, using the ResponseInfo->ServeFile function. This works fine for files that are "static": not being written by some other process. As far as I can see from the code, the ServeFile function internally uses a TIdReadFileExclusiveStream, which disallows me from reading a file being written, but I need to be able to send also files that are being written by some other process.
So, I moved to create a FileStream myself and use the ContentStream property to return it to the client, but I get a 0 bytes file in the client (for any file, being written or not), and I can't see what I'm missing or doing wrong. Here is the code I'm using on the OnCommandGet event handler:
AResponseInfo->ContentStream = new TFileStream(path, fmOpenRead | fmShareDenyNone);
AResponseInfo->ContentStream->Position = 0;
AResponseInfo->ContentLength = AResponseInfo->ContentStream->Size;
AResponseInfo->ResponseNo = 200;
AResponseInfo->WriteHeader();
AResponseInfo->WriteContent();
The ContentLength property at this point has a valid value (i.e., the file size when calling ContentStream->Size), and that's what I would like to send to the client, even if the file changes in between.
I have tried removing the WriteContent() function, the WriteHeader(), but the results are the same. I searched for some examples but the few I found are more or less the same than this code, so I don't know what's wrong. Most examples don't include the WriteContent() call, that's why I have tried removing them, but there doesn't seem to be any difference.
As a side note: the files being written take 24 hours to finish writing, but that's to be expected from the client side: I just need the bytes already written at the time of the request (even somewhat less is valid). The files will never get deleted: they will just keep getting bigger.
Any ideas?
Update
Using Fiddler, I get some warnings on protocol violations, that would be related to this. I get, for instance:
Content-Length mismatch: Response Header indicated 111,628,288 bytes, but server sent 41 bytes
The content length is correct, it's the file size, but I don't know what I'm doing wrong that makes the app sent just 41 bytes.

WriteHeader() and WriteContent() expect the ContentStream to be complete and unchanging at the time they are called. WriteHeader() creates a Content-Length header using the current ContentStream->Size value if the AResponseInfo->ContentLength property is -1 (you are actually setting the value yourself), and WriteContent() sends only as many bytes as the current ContentStream->Size value says. So your client is receiving 0 bytes because the file Size is still 0 at the time you are calling WriteHeader() and WriteContent().
Neither ServeFile() nor ContentStream are suitable for your needs. Since the file is being written live, you do not know the final file size when the HTTP headers are created and sent to the client. So you must use HTTP 1.1's chunked transfer coding to send the file data. That will allow you to send the file data in chunks as the file is being written, and then signal the client when the file is finished.
However, TIdHTTPServer does not natively support sending chunked responses, so you will have to implement it manually, eg:
TFileStream *fs = new TFileStream(path, fmOpenRead | fmShareDenyNone);
try
{
AResponseInfo->ResponseNo = 200;
AResponseInfo->TransferEncoding = "chunked";
AResponseInfo->WriteHeader();
TIdBytes buffer;
buffer.Length = 1024;
do
{
int NumRead = fs->Read(&buffer[0], 1024);
if (NumRead == -1) RaiseLastOSError();
if (NumRead == 0)
{
// check for EOF, unless you have another way to detect it...
Sleep(1000);
NumRead = fs->Read(&buffer[0], 1024);
if (NumRead <= 0) break;
}
// send the current chunk
AContext->Connection->IOHandler->WriteLn(IntToHex(NumRead));
AContext->Connection->IOHandler->Write(buffer, NumRead);
AContext->Connection->IOHandler->WriteLn();
}
while (true);
// send the last chunk to signal EOF
AContext->Connection->IOHandler->WriteLn("0");
// send any trailer headers you need, if any...
// finish the transfer encoding
AContext->Connection->IOHandler->WriteLn();
}
__finally
{
delete fs;
}

The final working code is:
std::unique_ptr< TFileStream >fs(new TFileStream(path, fmOpenRead | fmShareDenyNone));
fs->Position = 0;
__int64 size = fs->Size;
AResponseInfo->ContentLength = size;
AResponseInfo->ResponseNo = 200;
AResponseInfo->WriteHeader();
AContext->Connection->IOHandler->Write(fs.get(), size);
This allows the client to receive up to size bytes of the original file, even if the file is being written to at the same time.
For some reason passing the ContentStream did not return any content to the client, but doing the IOHandler->Write directly (which is what the ServeFile ends doing internally) works fine.

Related

boost::beast::http::request Sending a file as multipart/form-data

Just getting in to this using http_client_sync.cpp. I added a little bit of code to use the
http::file_body type.
I can't seem to figure out how to get the multipart boundaries inserted. Is there something in particular I need to do to make that happen? When I look at the POST in wireshark, the entire multipart/form data is in one big part. Relevant (I hope) snippet below. Modified from oroginal http_client_sync.cpp.
// Set up an HTTP POST request message
http::request<http::file_body> req{http::verb::post, target, version};
// use the full host:port here
req.set(http::field::host, fullhost.c_str());
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req.set(http::field::accept,"*/*");
req.set(http::field::content_length,contentLen);
req.set(http::field::content_type,"multipart/form-data; boundary=fd1c38d86c0a42ac933e7319e51882fd");
req.body() = std::move(body);
http::request_serializer<http::file_body, http::fields> sr{req};
// Send the HTTP request to the remote host
http::write(socket, sr);
I did also try this, but the result was the same
size_t len = http::write_header(socket, sr);
while(len!=0)
{
len = http::write_some(socket, sr);
}
The server I'm talking to is expecting multiple parts.
Many Thanks
Ok it turns out this is possible, and cured my issue. Probably not the "right" way to do it, but it works.
size_t len = http::write_header(socket, sr);
// where multipart header is a string containing the first boundary
// and the content disposition
boost::asio::write(socket,boost::asio::buffer( multipartHeader));
while(len!=0)
{
len = http::write_some(socket, sr);
}
// where multipart trailer is a string containing the final boundary
boost::asio::write(socket,boost::asio::buffer( multipartTrailer));
Happy to hear better solutions.

How to remove non-ascii char from MQ messages with ESQL

CONCLUSION:
For some reason the flow wouldn't let me convert the incoming message to a BLOB by changing the Message Domain property of the Input Node so I added a Reset Content Descriptor node before the Compute Node with the code from the accepted answer. On the line that parses the XML and creates the XMLNSC Child for the message I was getting a 'CHARACTER:Invalid wire format received' error so I took that line out and added another Reset Content Descriptor node after the Compute Node instead. Now it parses and replaces the Unicode characters with spaces. So now it doesn't crash.
Here is the code for the added Compute Node:
CREATE FUNCTION Main() RETURNS BOOLEAN
BEGIN
DECLARE NonPrintable BLOB X'0001020304050607080B0C0E0F101112131415161718191A1B1C1D1E1F7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF1F2F3F4F5F6F7F8F9FAFBFCFDFEFF';
DECLARE Printable BLOB X'20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020';
DECLARE Fixed BLOB TRANSLATE(InputRoot.BLOB.BLOB, NonPrintable, Printable);
SET OutputRoot = InputRoot;
SET OutputRoot.BLOB.BLOB = Fixed;
RETURN TRUE;
END;
UPDATE:
The message is being parsed as XML using XMLNSC. Thought that would cause a problem, but it does not appear to be.
Now I'm using PHP. I've created a node to plug into the legacy flow. Here's the relevant code:
class fixIncompetence {
function evaluate ($output_assembly,$input_assembly) {
$output_assembly->MRM = $input_assembly->MRM;
$output_assembly->MQMD = $input_assembly->MQMD;
$tmp = htmlentities($input_assembly->MRM->VALUE_TO_FIX, ENT_HTML5|ENT_SUBSTITUTE,'UTF-8');
if (!empty($tmp)) {
$output_assembly->MRM->VALUE_TO_FIX = $tmp;
}
// Ensure there are no null MRM fields. MessageBroker is strict.
foreach ($output_assembly->MRM as $key => $val) {
if (empty($val)) {
$output_assembly->MRM->$key = '';
}
}
}
}
Right now I'm getting a vague error about read only messages, but before that it wasn't working either.
Original Question:
For some reason I am unable to impress upon the senders of our MQ
messages that smart quotes, endashes, emdashes, and such crash our XML
parser.
I managed to make a working solution with SQL queries, but it wasted
too many resources. Here's the last thing I tried, but it didn't work
either:
CREATE FUNCTION CLEAN(IN STR CHAR) RETURNS CHAR BEGIN
SET STR = REPLACE('–',STR,'–');
SET STR = REPLACE('—',STR,'—');
SET STR = REPLACE('·',STR,'·');
SET STR = REPLACE('“',STR,'“');
SET STR = REPLACE('”',STR,'”');
SET STR = REPLACE('‘',STR,'&lsqo;');
SET STR = REPLACE('’',STR,'’');
SET STR = REPLACE('•',STR,'•');
SET STR = REPLACE('°',STR,'°');
RETURN STR;
END;
As you can see I'm not very good at this. I have tried reading about
various ESQL string functions without much success.
So in ESQL you can use the TRANSLATE function.
The following is a snippet I use to clean up a BLOB containing non-ASCII low hex values so that it then be cast into a usable character string.
You should be able to modify it to change your undesired characters into something more benign. Basically each hex value in NonPrintable gets translated into its positional equivalent in Printable, in this case always a full-stop i.e. x'2E' in ASCII. You'll need to make your BLOB's long enough to cover the desired range of hex values.
DECLARE NonPrintable BLOB X'000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F';
DECLARE Printable BLOB X'2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E2E';
SET WorkBlob = TRANSLATE(WorkBlob, NonPrintable, Printable);
BTW if messages with invalid characters only come in every now and then I'd probably specify BLOB on the input node and then use something similar to the following to invoke the XMLNSC parser.
CREATE LASTCHILD OF OutputRoot DOMAIN 'XMLNSC'
PARSE(InputRoot.BLOB.BLOB CCSID InputRoot.Properties.CodedCharSetId ENCODING InputRoot.Properties.Encoding);
With the exception terminal wired up you can then correct the BLOB's of any messages containing parser breaking invalid characters before attempting to reparse.
Finally my best wishes as I've had a number of battles over the years with being forced to correct invalid message content in the "Integration Layer" after all that's what it's meant to do.

How to get a parsable response from SendCmd in Indy?

If I use TIdImap4.SendCmd to manually send an unsupported command to Indy, I'm a little bit confused how I retrieve the full response to manually parse it.
I'm sending the following command to manually request the Gmail labels for a message since this is not yet supported by Indy:
IMAP.SendCmd(ImapCmdNum(),'UID FETCH '+uid+' (X-GM-LABELS)',['OK','BAD','NO'], false);
After calling this command, I checked my Indy Log file and it's successfully receiving an appropriate response from the server:
Sent 9/19/2015 11:10:40 AM: C5 UID FETCH 2385 (X-GM-LABELS)<EOL>
Recv 9/19/2015 11:10:40 AM: * 542 FETCH (X-GM-LABELS (testlabel) UID 2385)<EOL>C5 OK Success<EOL>
But now I can't seem to get any part of that response other than 'OK' from Indy. I've tried the following in the debugger and none of them have the raw response or anything else I could possibly manually parse:
IMAP.LastCmdResult = ('OK', $2521E60, nil, $2521EC0)
IMAP.LastCmdResult.Text = ()
IMAP.LastCmdResult.Code = 'OK'
IMAP.LastCmdResult.NumericCode = 0
IMAP.LastCmdResult.FormattedReply = ()
From the SendCmd documentation:
SendCmd is an overloaded function used to send the command specified
in AOut to the peer connection.
SendCmd uses IOHandler to write the command in AOut to the peer
connection.
AResponse indicates the response allowed for the command.
SendCmd calls GetResponse to determine if the response from the peer
connection is allowed. If the response is not allowed, an exception is
raised during processing in GetResponse.
When AResponse is contains -1, GetResponse is called with an empty
array to indicate that any response code is permitted for the command.
Otherwise, the value in AResponse is used to valid the response code.
Use LastCmdResult to access the numeric and text portions of the
response for the command.
My understanding of this is that I should be using LastCmdResult to access various "portions of the response", but none of them have the raw response or any part of the response except "OK", so how do I get something parsable from the response to SendCmd?
The text you are looking for is, in fact, in the LastCmdResult.Text property. The debugger is not showing it to you, but that is where the label data is.
As I told you 2 months ago in comments to my other answer that you linked to:
Look at the implementation of TIdIMAP4.UIDRetrieveFlags(). It calls SendCmd() followed by ParseLastCmdResult() to parse the returned flags. You will have to replicate the same logic, substituting fdGmailLabels where fdFlags is currently being used (minus the call to ParseMessageFlagString() that is parsing the flags string to a TIdMessageFlagsSet).
If you look at the implementation of TIdIMAP4.UIDRetrieveFlags() and then look at your code, you are not even calling SendCmd() correctly to begin with. You are passing the wrong value to the ATag parameter (unless ImapCmdNum() is simply calling TIdIMAP4.NewCmdCounter - TIdIMAP4 needs to generate the command counters so it can match them to the replies), and moe importantly you are passing the wrong values to the AExpectedResponses parameter.
Try this instead (I tested it and it works):
type
TIdIMAP4Access = class(TIdIMAP4);
TIdIMAPLineStructAccess = class(TIdIMAPLineStruct);
var
uid: string;
labels: string;
begin
...
uid := ...;
labels := '';
IMAP.SendCmd('UID FETCH ' + uid + ' (X-GM-LABELS)', ['FETCH','UID']);
if IMAP.LastCmdResult.Code = IMAP_OK then
begin
if IMAP.LastCmdResult.Text.Count > 0 then
begin
// The requested data is in IMAP.LastCmdResult.Text[0].
// You can either parse it manually, or use the below
// code to let TIdIMAP4 parse it for you...
if TIdIMAP4Access(IMAP).ParseLastCmdResult(IMAP.LastCmdResult.Text[0], 'FETCH', ['X-GM-LABELS']) then begin
labels := TIdIMAPLineStructAccess(TIdIMAP4Access(IMAP).FLineStruct).IMAPValue;
end;
end;
end;
...
end;

Save SpreadsheetDocument in the DB

I have a valid SpreadsheetDocument object created from the stream. I can manipulate it (f.e. add new row). After my changes I need to save this changed document in SQL Server as varbinary and later read it for SQL Server to manipulate further.
Could you provide some example how to achieve it?
I know how to put/read data from SQL Server. What I'm looking for is the way somehow to convert SpreadsheetDocument to byte array and to create back SpreadsheetDocument from byte array for SQL Server.
I'm using Open XML SDK 2.0
Thanks a lot,
Alexander
Not quite the same but I needed to load an Excel template into memory, modify it and send it over HTTP using IIS. I did it by loading the data into a memory stream, then doing the modifications (that seems to be the way Microsoft recomend here:
http://msdn.microsoft.com/en-us/library/ee945362%28v=office.11%29.aspx
This might help you:
MemoryStream ms = new MemoryStream();
byte [] byteArray = System.IO.File.ReadAllBytes("document.xslm");
ms.Write(byteArray, 0, byteArray.Length);
ms.Position = 0;
using (SpreadsheetDocument doc = SpreadsheetDocument.Open(ms, true))
{
<Do stuff>
}
return File(ms.ToArray(), "application/vnd.ms-excel.sheet.macroEnabled.12", "output.xlsm");
Obviously the last line is what I needed to do, you're going to need to save the stream to the database.

How to receive UDP data in Vala?

another Vala problem occured: I try to send and receive data via UDP. The sending works and via Wireshark I can see that the server sends the expected result. Problem is: My program doesn't get the data.
I checked and I can see that, when a socket has been created to send the UDP data, the specific port stays open, which is confirmed by Wireshark because my PC doesn't send any of those ICMP messages back to the server.
What I got so far:
try
{
SocketClient mySocket = new SocketClient();
mySocket.protocol = SocketProtocol.UDP;
mySocket.type = SocketType.DATAGRAM;
var conn = mySocket.connect (new InetSocketAddress(addr,targetPort));
conn.output_stream.write(themessage_in_a_uint8_array);
DataInputStream response = new DataInputStream (conn.input_stream);
string resp ="";
char myChar;
try
{
do
{
myChar = (char)response.read_byte();
print ("Response" + myChar.to_string());
}while(true);
}
catch(Error e)
{
print(e.message);
}
}
catch(Error e)
{print(e.message);}
What currently happens: The message is send, the string 'Response' is printed once into the console and after that it just loops.
If I check response.get_available() it returns 0.
I can check with lsof | grep used_portnumber and sure enough, the used socket stays open. What am I doing wrong?
I am not sure but this is what I suspect:
UDP is a datagram protocol (data is explicitly chopped into data). Server have sent one datagram to client. Now in BSD Sockets (and after it everywhere) if the underlaying socket have datagram type then read reads the full packet. If the buffer have insufficient length the message is truncated.
The solution is read in one byte. For example
uint8[] buffer = new uint8[1 << 16]; // Maximum UDP length - we don't loose anything
unowned string locale;
bool need_convert = GLib.get_charset (out locale);
do {
ssize_t len = response.read (buffer);
string text;
if (need_convert) {
text = GLib.convert ((string)buffer, len, locale, "UTF-8");
} else {
text = (string)buffer;
}
stdout.print("Response " + text);
} while (true);
Edit I have change the code to print UTF-8 text - without assuming current locale is "UTF-8"-based.
PS 1 This is my guess as it is one gotcha of BSD Sockets (also Winsockets and everything that builds on this) that come to my mind. Please be graceful if the question will be more specific (i.e. it is not the answer to question).
PS 2 In general I would recommend against mixing bytes and chars. While in ASCII-compatible encodings (ISO, UTF-8) sending ASCII subset of chars is safe it will bite when attempt on CJK encodings or if sender will send 'ą' by UTF-8 and sender will treat it as ISO-8859-2 (where this character have different encoding). I assume it is for the toy-examples only. If not you may want to read What Every Programmer Absolutely, Positively Needs To Know About Encodings And Character Sets To Work With Text.

Resources