1
/****************************************************************************
3
| Copyright (c) 2007 Novell, Inc.
6
| This program is free software; you can redistribute it and/or
7
| modify it under the terms of version 2 of the GNU General Public License as
8
| published by the Free Software Foundation.
10
| This program is distributed in the hope that it will be useful,
11
| but WITHOUT ANY WARRANTY; without even the implied warranty of
12
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
| GNU General Public License for more details.
15
| You should have received a copy of the GNU General Public License
16
| along with this program; if not, contact Novell, Inc.
18
| To contact Novell about this file by physical or electronic mail,
19
| you may find current contact information at www.novell.com
21
| Author: Mike Lasky (mlasky@novell.com)
22
|***************************************************************************/
25
using System.Collections;
26
using System.Collections.Specialized;
31
using System.Web.Services;
32
using System.Web.SessionState;
34
namespace Novell.iFolderWeb.Admin
37
/// Summary description for LogTailHandler.
39
public class LogTailHandler : IHttpHandler, IRequiresSessionState
44
/// Used to log messages.
46
private static readonly iFolderWebLogger log = new iFolderWebLogger(
47
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name );
50
/// Maximum read buffer size.
52
private const int BufferSize = 128 * 1024;
56
#region Private Methods
59
/// Gets the size of the specified file.
61
/// <param name="web">iFolderAdmin object.</param>
62
/// <param name="fileName">The name of the file to get the size for.</param>
63
/// <returns>The current size of the specified file.</returns>
64
private long GetFileSize( iFolderAdmin web, string fileName )
68
UriBuilder uri = new UriBuilder( web.Url );
69
uri.Path = String.Format( "/simias10/admindata/{0}?size=1", fileName );
71
HttpWebRequest webRequest = WebRequest.Create( uri.Uri ) as HttpWebRequest;
72
webRequest.Method = "GET";
73
webRequest.PreAuthenticate = true;
74
webRequest.Credentials = web.Credentials;
75
webRequest.CookieContainer = web.CookieContainer;
77
HttpWebResponse webResponse = webRequest.GetResponse() as HttpWebResponse;
80
StreamReader sr = new StreamReader( webResponse.GetResponseStream(), Encoding.GetEncoding( "utf-8" ) );
81
fileSize = Convert.ToInt64( sr.ReadLine() );
92
/// Gets a line from the specified buffer. The line must be bounded by an eol
93
/// at the end and at the beginning of the data in order to be a complete line.
94
/// The eol at the beginning of the line is not returned with the data.
96
/// <param name="buffer">Buffer that contains line data.</param>
97
/// <param name="eob">Index to the end of the buffer data.</param>
98
/// <param name="strict">If false line does not need start eol bounds character.</param>
99
/// <returns>A byte array containing a line with a terminating eol char if
100
/// successful. Otherwise an empty buffer is returned.</returns>
101
private byte[] GetLineData( byte[] buffer, int eob, bool strict )
103
bool foundEol = false;
104
byte[] data = new byte[ 0 ];
108
// If the last chars in the buffer are not eols, discard them until an eol is found.
109
for( index = eob, eol = 0; index >= 0; --index )
111
// Look for both types of line terminators.
112
if ( ( buffer[ index ] == '\r' ) || ( buffer[ index ] == '\n' ) )
114
if ( foundEol == false )
126
// See if any eol chars were found.
129
// Need to find another eol before we can count this as a complete line.
131
for ( sol = index; sol >= 0; --sol )
133
if ( ( buffer[ sol ] == '\r' ) || ( buffer[ sol ] == '\n' ) )
135
// Point back at the start of the line.
141
// See if the start of the line was found.
144
int length = ( eol - sol ) + 1;
145
data = new byte[ length ];
146
Array.Copy( buffer, sol, data, 0, length );
150
int length = eol + 1;
151
data = new byte[ length ];
152
Array.Copy( buffer, 0, data, 0, length );
160
/// Gets the number of lines to read from the request.
162
/// <param name="request">HttpRequest object</param>
163
/// <returns>The number of lines from the url query string.</returns>
164
private int GetLineCount( HttpRequest request )
166
// Default the number of lines.
169
// New requests will contain the number of lines to send back.
170
NameValueCollection query = request.QueryString;
171
if ( query[ "lines" ] != null )
173
// This is a new request. Save the request information on the session.
174
lines = Convert.ToInt32( query[ "lines" ] );
175
if ( lines > 512 ) lines = 512;
182
/// Gets the specified tail data.
184
/// <param name="web">iFolderAdmin object</param>
185
/// <param name="fileName">The name of the file to tail.</param>
186
/// <param name="fileLength">The current length of the file.</param>
187
/// <param name="ti">The tail information saved on the session.</param>
188
/// <param name="length">Receives the total length of the data in the ArrayList.</param>
189
/// <returns>An array of byte arrays containing file data.</returns>
190
private ArrayList GetTailData( iFolderAdmin web, string fileName, long fileLength, TailInfo ti, out int length )
192
ArrayList tailLines = new ArrayList( ti.Lines );
195
// Build the path to the log handler.
196
UriBuilder uri = new UriBuilder( web.Url );
197
uri.Path = String.Format( "/simias10/admindata/{0}", fileName );
199
// Is this a first request?
200
if ( ti.Offset == -1 )
202
// Have to guess how much data to request.
203
byte[] buffer = new byte[ ti.Lines * 256 ];
205
// Read one buffer size from the end of the file.
206
long readLength = ( fileLength > buffer.Length ) ? buffer.Length : fileLength;
208
// Calculate the offset to read from.
209
ti.Offset = fileLength - readLength;
211
// Add the query string part.
212
uri.Query = String.Format( "offset={0}&length={1}", ti.Offset, readLength );
214
// Build the web request to get the data.
215
HttpWebRequest webRequest = WebRequest.Create( uri.Uri ) as HttpWebRequest;
216
webRequest.Method = "GET";
217
webRequest.PreAuthenticate = true;
218
webRequest.Credentials = web.Credentials;
219
webRequest.CookieContainer = web.CookieContainer;
221
HttpWebResponse webResponse = webRequest.GetResponse() as HttpWebResponse;
224
Stream sr = webResponse.GetResponseStream();
225
int bytesRead = sr.Read( buffer, 0, ( int )readLength );
228
// Get the specified number of lines until the data is all read.
229
for ( int lines = 0, eob = bytesRead - 1;
230
( lines < ti.Lines ) && ( eob >= 0 );
233
byte[] line = GetLineData( buffer, eob, true );
234
if ( line.Length > 0 )
236
tailLines.Add( line );
238
length += line.Length;
242
// No lines exist in the buffer. Return no data.
247
// If any data was returned, update the offset.
248
if ( tailLines.Count > 0 )
250
ti.Offset += bytesRead;
262
// See if there is data to read.
263
long readLength = fileLength - ti.Offset;
264
if ( readLength > 0 )
266
// Make sure the read request is not too large.
267
if ( readLength > BufferSize )
269
readLength = BufferSize;
272
// Read to the end of the file.
273
byte[] buffer = new byte[ readLength ];
275
// Add the query string part.
276
uri.Query = String.Format( "offset={0}&length={1}", ti.Offset, readLength );
278
// Build the web request to get the data.
279
HttpWebRequest webRequest = WebRequest.Create( uri.Uri ) as HttpWebRequest;
280
webRequest.Method = "GET";
281
webRequest.PreAuthenticate = true;
282
webRequest.Credentials = web.Credentials;
283
webRequest.CookieContainer = web.CookieContainer;
285
HttpWebResponse webResponse = webRequest.GetResponse() as HttpWebResponse;
288
Stream sr = webResponse.GetResponseStream();
289
int bytesRead = sr.Read( buffer, 0, ( int )readLength );
292
// Get the specified number of lines until the data is all read.
293
for ( int eob = bytesRead - 1; eob >= 0; )
295
byte[] line = GetLineData( buffer, eob, false );
296
if ( line.Length > 0 )
298
tailLines.Add( line );
300
length += line.Length;
304
// No lines exist in the buffer. Return no data.
309
// If any data was returned, update the offset.
310
if ( tailLines.Count > 0 )
328
/// Gets or creates a TailInfo object that contains information about tailing
329
/// the specified file.
331
/// <param name="context">HttpContext object</param>
332
/// <param name="fileName">The name of the file to tail.</param>
333
/// <param name="lines">The number of lines to return on the initial request.</param>
334
/// <returns>A TailInfo object that is saved on the session.</returns>
335
private TailInfo GetTailInfo( HttpContext context, string fileName, int lines )
339
// Is there already tail information on the session?
340
Hashtable ht = context.Session[ "TailInfo" ] as Hashtable;
343
context.Session[ "TailInfo" ] = ht = new Hashtable();
346
// See if there is information for this file in the hashtable.
347
if ( ht.ContainsKey( fileName ) )
349
ti = ht[ fileName ] as TailInfo;
353
ht[ fileName ] = ti = new TailInfo( lines, -1 );
361
#region IHttpHandler Members
364
/// Enables processing of HTTP Web requests by a custom HttpHandler
365
/// that implements the IHttpHandler interface.
367
/// <param name="context">An HttpContext object that provides
368
/// references to the intrinsic server objects (for example,
369
/// Request, Response, Session, and Server) used to service HTTP requests.</param>
370
public void ProcessRequest( HttpContext context )
372
// Setup the response.
373
HttpRequest request = context.Request;
374
HttpResponse response = context.Response;
378
// Only respond to GET method.
379
if ( String.Compare( request.HttpMethod, "GET", true ) == 0 )
381
// The file name of the url is the file that is to be downloaded.
382
string fileName = Path.GetFileName( request.Url.LocalPath );
384
iFolderAdmin web = context.Session[ "Connection" ] as iFolderAdmin;
385
if ( web == null ) context.Response.Redirect( "Login.aspx" );
387
// Get the size of the file.
388
long fileSize = GetFileSize( web, fileName );
390
// New requests will contain the number of lines to send back.
391
int lines = GetLineCount( request );
393
// Get information about tailing the specified file.
394
TailInfo ti = GetTailInfo( context, fileName, lines );
396
// Get the line data.
398
ArrayList lineData = GetTailData( web, fileName, fileSize, ti, out length );
400
// Setup the response.
402
response.BufferOutput = false;
403
response.Cache.SetCacheability( HttpCacheability.NoCache );
404
response.ContentType = "text/plain";
405
response.AddHeader("Content-Length", length.ToString() );
407
foreach( byte[] line in lineData )
409
response.OutputStream.Write( line, 0, line.Length );
416
log.Debug( context, "Error: Invalid http method - {0}", request.HttpMethod );
417
response.StatusCode = ( int )HttpStatusCode.BadRequest;
421
catch ( Exception ex )
423
log.Debug( context, "Error: {0}", ex.Message );
424
log.Debug( context, "Stack trace: {0}", ex.StackTrace );
425
response.StatusCode = ( int ) HttpStatusCode.InternalServerError;
431
/// Gets a value indicating whether another request can use the IHttpHandler instance.
433
public bool IsReusable
440
#region TailInfo Class
443
/// Class used to keep track of per file tail information.
445
private class TailInfo
447
#region Class Members
450
/// Last offset read from file.
455
/// Number of lines to initially read.
464
/// Gets or sets the last offset that the file was read from.
468
get { return offset; }
469
set { offset = value; }
473
/// Gets the number of lines to initially read from the file.
477
get { return lines; }
487
/// <param name="lines">Number of lines initially requested.</param>
488
/// <param name="offset">Current read offset of the file.</param>
489
public TailInfo( int lines, long offset )
492
this.offset = offset;