ok
[element:www.git] / ruby / GET.rb
1 class R
2
3   def GET
4     if file = [self,pathSegment].compact.find(&:f) # file exists, but client (or server) might want another MIME
5       a = @r.accept.values.flatten
6       accepted = a.empty? || (a.member? file.mimeP) || (a.member? '*/*')
7       return file.env(@r).fileGET unless !accepted || MIMEcook[file.mimeP]
8     end # enable conneg-hint paths:
9     uri = stripDoc # doc-format in extension
10     uri = uri.parent.as '' if uri.to_s.match(/\/index$/) # virtual index
11     uri.env(@r).resourceGET # continue at generic-resource URI
12   end
13
14   def HEAD
15     self.GET.do{|s,h,b|[s,h,[]]}
16   end
17
18   def OPTIONS
19     [200,{},[]]
20   end
21
22   def fileGET
23     @r['ETag'] = [m,size].h
24     condResponse mimeP,->{self}
25   end
26
27   def resourceGET # handler cascade
28     paths = pathSegment.cascade
29     ['http://'+@r['SERVER_NAME'],""].map{|h| # http://host/path first, then /path (mounted on all hosts)
30       paths.map{|p| GET[h + p].do{|fn|
31           fn[self,@r].do{|r|return r}}}}
32     response
33   end
34
35   def response # default handler
36     m = {'#' => {'uri' => '#', Type => R[HTTP+'Response']}} # Response
37   set = []
38     
39     # File set
40     fileFn = q['set'].do{|s| FileSet[s]} || FileSet['default']
41     fileFn[self,q,m].do{|files| set.concat files }
42
43     # Resource set
44     q['set'].do{|s|
45       ResourceSet[s].do{|resFn|
46         resFn[self,q,m].do{|resources|
47           resources.map{|resource|
48             set.concat resource.fileResources}}}}
49
50     return E404[self,@r,m] if set.empty?
51
52     @r['ETag'] = [q['view'].do{|v|View[v] && v}, # View
53                   set.sort.map{|r|[r, r.m]}, # entity version(s)
54                   @r.format].h                   # output MIME
55
56     condResponse @r.format, ->{
57       puts [uri, @r['HTTP_USER_AGENT'], @r['HTTP_REFERER']].join ' '
58
59       # RDF Model - all input formats are RDF and Writer exists for output MIME
60       if @r.format != "text/html" && ! set.find{|f| ! f.uri.match /\.(jsonld|nt|n3|rdf|ttl)$/} &&
61           format = RDF::Format.for(:content_type => @r.format)
62         graph = RDF::Graph.new
63         set.map{|r| graph.load r.d}
64         graph.dump format.to_sym
65
66       else # JSON Model
67         set.map{|r|r.env(@r).toGraph m}
68         Render[@r.format][m, @r]
69       end}
70   end
71   
72   def condResponse format, body
73     @r['HTTP_IF_NONE_MATCH'].do{|m|
74       m.strip.split(/\s*,\s*/).include?(@r['ETag']) && [304,{},[]]} ||
75     body.call.do{|body|
76       head = {'Content-Type' => format, 'ETag' => @r['ETag']}
77       head.update({'Cache-Control' => 'no-transform'}) if format.match /^(audio|image|video)/
78
79       body.class == R ? (Nginx ? [200,head.update({'X-Accel-Redirect' => '/fs' + body.path}),[]] : # Nginx
80                          Apache ? [200,head.update({'X-Sendfile' => body.d}),[]] : # Apache
81                          (f = Rack::File.new nil; f.instance_variable_set '@path', body.d # Rack
82                           f.serving(@r).do{|s,h,b|[s,h.update(head),b]})) :
83       [200,head,[body]]}
84   end
85
86 end