Skip to content
This repository has been archived by the owner on Jan 25, 2019. It is now read-only.

Static file request support #20

Closed
leledumbo opened this issue Mar 10, 2013 · 17 comments
Closed

Static file request support #20

leledumbo opened this issue Mar 10, 2013 · 17 comments

Comments

@leledumbo
Copy link
Collaborator

fpweb has fpwebfile that defines default request for static files. I currently define my own action for this:

unit Static;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, HTTPDefs, BrookAction;

type

  { TStaticFileAction }

  TStaticFileAction = class(TBrookAction)
  private
    FMimeType: String;
  public
    procedure Request(ARequest: TRequest; AResponse: TResponse); override;
    procedure Get; override;
  end;

implementation

uses
  AppUtils, // for RelativeToAbsolute below
  fpmimetypes;

{ TStaticFileAction }

procedure TStaticFileAction.Request(ARequest: TRequest; AResponse: TResponse);
begin
  inherited Request(ARequest,AResponse);
  AResponse.ContentType := FMimeType;
end;

procedure TStaticFileAction.Get;
var
  FilePath: String;
begin
  FilePath := RelativeToAbsolute(
    IncludeTrailingPathDelimiter(Values['dir'].AsString) +
    Values['file'].AsString
  );
  if FileExists(FilePath) then
    with TStringList.Create do
      try
        LoadFromFile(FilePath);
        FMimeType := MimeTypes.GetMimeType(ExtractFileExt(FilePath));
        Write(Text);
      finally
        Free;
      end;
end;

initialization
  MimeTypes.LoadFromFile('mime.types');
  TStaticFileAction.Register('/static/:dir/:file');

end.

Is there any better way?

@silvioprog
Copy link
Owner

Other day I tried to use static files. I had seen fpmimetypes and even tried to improve the brookhttpapp broker, but, unfortunately, fpmimetypes is coupled in fpweb. :(

His solution seems the best to use fpmimetypes. Can you to create a small example and provide it on the demos folder?

Thanks.

@leledumbo
Copy link
Collaborator Author

This is as general as I can make:

unit Static;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils;

// This is the only thing that user may know from this unit
procedure RegisterDirectory(ARequestPath,ADirectory: String);

implementation

uses
  StrUtils, ghashmap, fpmimetypes, HTTPDefs, BrookAction;

resourcestring
  EmptyRequestPathErrMsg = 'Request path may not be empty';
  DirectoryNotExistErrMsg = 'Directory not exists: %s';

type

  { TStringHash }

  TStringHash = class
    class function hash(s: String; n: Integer): Integer;
  end;

  TRequestDirectoryMap = specialize THashmap<String,String,TStringHash>;

  { TStaticFileAction }

  TStaticFileAction = class(TBrookAction)
  public
    procedure Get; override;
  end;

var
  RequestDirectoryMap: TRequestDirectoryMap;

{ TStringHash }

class function TStringHash.hash(s: String; n: Integer): Integer;
var
  c: Char;
begin
  Result := 0;
  for c in LowerCase(s) do
    Inc(Result,Ord(c));
  Result := Result mod n;
end;

{ TStaticFileAction }

procedure TStaticFileAction.Get;
var
  LastSlashPos: Integer;
  PathInfo,FilePath,Buffer: String;
  ContentType: String;
begin
  PathInfo := GetRequest.PathInfo;
  LastSlashPos := RPos('/',PathInfo);
  System.Delete(PathInfo,LastSlashPos + 1,Length(PathInfo) - LastSlashPos);

  FilePath := RequestDirectoryMap[PathInfo] + Values['file'].AsString;
  if FileExists(FilePath) then begin
    ContentType := MimeTypes.GetMimeType(ExtractFileExt(FilePath));
    if ContentType = '' then
      ContentType := 'application/octet-stream';
    GetResponse.ContentType := ContentType;
    with TFileStream.Create(FilePath,fmOpenRead) do
      try
        SetLength(Buffer,Size);
        Read(Buffer[1],Size);
        Self.Write(Buffer);
      finally
        Free;
      end;
  end;
end;

procedure RegisterDirectory(ARequestPath,ADirectory: String);
begin
  if Length(ARequestPath) = 0 then raise Exception.Create(EmptyRequestPathErrMsg);
  if not DirectoryExists(ADirectory) then raise Exception.CreateFmt(DirectoryNotExistErrMsg,[ADirectory]);

  // add required slashes
  if ARequestPath[1] <> '/' then ARequestPath := '/' + ARequestPath;
  if ARequestPath[Length(ARequestPath)] <> '/' then ARequestPath := ARequestPath + '/';

  RequestDirectoryMap[ARequestPath] := IncludeTrailingPathDelimiter(ADirectory);
  TStaticFileAction.Register(ARequestPath + ':file');
end;

initialization
  RequestDirectoryMap := TRequestDirectoryMap.Create;

finalization
  RequestDirectoryMap.Free;

end.

The responsibility is now leveraged to the user of this unit to call:

MimeTypes.LoadFromFile('mime.types'); // for correct content-type
RegisterDirectory('css','/full/path/to/css');
RegisterDirectory('js','relative/path/may/work/js');
RegisterDirectory('img','/top/level/images');
RegisterDirectory('img/messages','/top/level/images/messages');

I'll make a simple demo, but I need to know where to put this unit and what better name it should have. Note that as the example RegisterDirectory shows, mapping works only for the given directory, excluding directories below it. For that to work, you must register each (sub)*directory #read-it-as-regex.

@silvioprog
Copy link
Owner

Friend, I'll make a demo with this nice code and provides it on the demos folder. :)

It would be interesting implement a broker for use this together with BrookFCLHTTPAppBroker/BrookFCLHTTPDaemonBroker broker.

@leledumbo
Copy link
Collaborator Author

It would be interesting implement a broker for use this together with BrookFCLHTTPAppBroker/BrookFCLHTTPDaemonBroker broker.

Do you mean to have 2 brokers active at the same time? Is that possible?

@silvioprog
Copy link
Owner

Yep. Eg:

uses
  BrookFCLCGIBroker, BrookSQLdbBroker, ...

Or (pseudo code):

uses
  BrookFCLHTTPAppBroker, BrookFCLMIMETypesBroker, ...

Can you implement this idea? Feel free if yes! :)

@leledumbo
Copy link
Collaborator Author

Hmm... I'll have to dig in the code first, I'm not sure how 2 brokers can be active at the same time...

@jcmoraisjr
Copy link

you can use as much brokers as you want at the same time providing you dont implement the same service more than once, otherwise it'd lead to ambiguity or something like "service already registered" error.

@leledumbo
Copy link
Collaborator Author

you can use as much brokers as you want at the same time providing you dont implement the same service more than once, otherwise it'd lead to ambiguity or something like "service already registered" error.

Yes, that's what I'm afraid of. For mimetypes broker, it should be easy as it doesn't deal with request handling. But static file request broker will do request handling in the same way as CGI/FCGI/HTTPApp/DaemonApp broker (actually, it's the sub of them as it depends on either of them). So could I simply use Static unit implementation above renamed as BrookStaticFileBroker?

@silvioprog
Copy link
Owner

BrookStaticFileBroker (so, StaticFile or StaticFiles? :/ ) is a good name for this new broker.
But this need not necessarily be a broker. You can create a separate class to be used as (pseudo code):

mycustomactns.pas
TCustomAction = class(TBrookAction) // or TBrookDBAction, TBrookRetrieveAction etc etc etc.

procedure TCustomAction.Create;
begin
  inherited Create;
  FStaticFiles := TBrookStaticFiles.Create;
end;

procedure TCustomAction.Destroy;
begin
  FStaticFiles.Free;
  inherited Destroy;
end;

procedure TCustomAction.Request(ARequest: TRequest; AResponse: TResponse);
begin
  FStaticFiles.Execute(ARequest, AResponse);
  inherited;
end;
mycactns.pas
TMyAction = class(TCustomAction)

procedure TMyAction.Get;
begin
  something
end;

Interesting?

@leledumbo
Copy link
Collaborator Author

Too much user code IMO, better just register directory and corresponding request path.

@tcaduto
Copy link

tcaduto commented Apr 23, 2013

Hi trying to get the static unit to work, but where do I find the mime.types file?

Nevermind, copied it from a linux box.

@leledumbo
Copy link
Collaborator Author

In case anybody need one: https://dl.dropboxusercontent.com/u/22124591/mime.types

silvioprog added a commit that referenced this issue May 29, 2013
@silvioprog
Copy link
Owner

Done. (1251c7c)

@leledumbo , can you upload to working branch a very small sample showing how to use the "brookstaticfilebroker.pas" unit please?

@leledumbo
Copy link
Collaborator Author

Will do tonight if I'm not exhausted

EDIT: Done, but I think I did something wrong, please check

@silvioprog
Copy link
Owner

I saw and it worked fine on my Win8 64. It is a nice demo. :)

I refactored some code and also updated the brokers unit for can compile the project. Please check if the current implementation in brookstaticfilebroker unit is the last implementation that you was implemented. I renamed the register method too.

@leledumbo
Copy link
Collaborator Author

Please check if the current implementation in brookstaticfilebroker unit is the last implementation that you was implemented

Yep, that seems so.

@silvioprog
Copy link
Owner

Thank you very much! :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants