femtows 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/gitc.bat ADDED
@@ -0,0 +1,20 @@
1
+ @echo off
2
+ IF DEFINED %1=="" (
3
+ call giti
4
+ ruby -e "a=File.read('VERSION').split('.') ; a[-1]=(a.last.to_i+1).to_s; puts r=a.join('.'); File.open('VERSION','w') {|f| f.write(r)}"
5
+ echo %1 %2 %3 %4 %5 %6 %7 %8 %9 >> CHANGELOG.txt
6
+ git commit -a -m "%1 %2 %3 %4 %5 %6 %7 %8 %9"
7
+ git push
8
+ echo
9
+ echo call gitc.bat without args for make/post rubygem
10
+ goto :eof
11
+ )
12
+ :gem
13
+ rem ==== no args, generate gem and push it to rubygems.org
14
+
15
+ ruby -e "a=File.read('VERSION').split('.');a.pop ; a[-1]=(a.last.to_i+1).to_s; puts r=(a+[0]).join('.'); File.open('VERSION','w') {|f| f.puts(r)}"
16
+ cat VERSION >> CHANGELOG.txt
17
+
18
+ ruby -e "Dir.glob('femtows*.gem').each {|f| File.delete(f) }"
19
+ call gem build femtows.gemspec
20
+ call gem push femtows*.gem
data/guidemo.rb ADDED
@@ -0,0 +1,63 @@
1
+ require_relative '../../Ruiby/lib/ruiby.rb'
2
+ require_relative "lib/femtows.rb"
3
+ #require 'ruiby'
4
+ ruiby_require 'erubis','xxx'
5
+
6
+ class Server < Ruiby_gtk
7
+ def initialize(t,w,h)
8
+ $server=self
9
+ @port=8100
10
+ @root="/"
11
+ super
12
+ threader(50)
13
+ end
14
+ def component
15
+ stack {
16
+ flowi {
17
+ table(2,2) {
18
+ row { cell_right label("root") ; cell(@eroot=entry(@root)) }
19
+ row { cell_right label("port") ; cell(@eport=ientry(@port,:min=>0,:max=>65535,:by=>10)) }
20
+ }
21
+ button("Restart") {
22
+ @root,@port=@eroot.text,@eport.text.to_i
23
+ $ws.stop_browser if $ws
24
+ run_server
25
+ deflog("","","restart ok")
26
+ }
27
+ button("reset log") { @logt.buffer.text="resetted" }
28
+ }
29
+ @logt= slot(text_area(800,160,{font: "courier new 8"}))
30
+ }
31
+ end
32
+ def run_server
33
+ after(1) do
34
+ begin
35
+ $ws=WebserverRoot.new(@port,@root,"femto ws",10,300, {
36
+ "logg" => proc {|*par| deflog(*par) }
37
+ })
38
+ $ws.serve("/info") {|p|
39
+ [200,".html", "Femto demo<hr><a href='/'>site</a><hr>#{$ws.to_table(p)}" ]
40
+ }
41
+ deflog('','',"restarted!")
42
+ rescue
43
+ deflog(["","",$!.to_s])
44
+ end
45
+ end
46
+ end
47
+ def logs(mess)
48
+ @logt.append(mess)
49
+ if @logt.text.size>100*1000
50
+ @logt.text=@logt.text[500..-1]
51
+ end
52
+ end
53
+ end
54
+ def deflog(name,adr,*res)
55
+ mess= "%s |%15s | %s\n" % [Time.now.strftime("%Y-%m-%d %H:%M:%S"),adr,res.join(" ")]
56
+ gui_invoke { logs(mess) rescue print $!.to_s+ $!.backtrace.join("\n")+"\n /// "+mess}
57
+ end
58
+
59
+
60
+ ########################### main
61
+ Thread.abort_on_exception = false
62
+ BasicSocket.do_not_reverse_lookup = true
63
+ Ruiby.start { Server.new("Web server",800,200).run_server }
data/lib/femtows.rb ADDED
@@ -0,0 +1,256 @@
1
+ # encoding: utf-8
2
+ # FemtoWebServer : 232 LOC web server
3
+ #
4
+ # $ws=WebserverRoot.new(port,"/home/www","femto ws",10,300)
5
+ # ws.serve "/FOO" do |params|
6
+ # data=params["HEAD-DATA"]|| ""
7
+ # puts "Recu data len=#{data.length} : <#{data[0..1000]}>" if data
8
+ # [200,".json",""]
9
+ # end
10
+
11
+
12
+ require 'thread'
13
+ require 'socket'
14
+ require 'timeout'
15
+
16
+ #################### Tiny embeded webserver
17
+
18
+
19
+
20
+
21
+ class WebserverAbstract
22
+ def logg(*args)
23
+ if @cb_log then @cb_log.call(@name,*args) else puts(args.join(" ")) end
24
+ rescue
25
+ puts(args.join(" "))
26
+ end
27
+ def info(txt) ; logg("nw>i>",txt) ; end
28
+ def error(txt) ; logg("nw>e>",txt) ; end
29
+ def unescape(string) ; string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2}))/n) { [$1.delete('%')].pack('H*') } ; end
30
+ def escape(string) ; string.gsub(/([^ \/a-zA-Z0-9_.-]+)/) { '%' + $1.unpack('H2' * $1.size).join('%').upcase }.tr(' ', '+'); end
31
+ def hescape(string) ; escape(string.gsub("/./","/").gsub("//","/")) ; end
32
+ def observe(sleeping,delta)
33
+ @tho=Thread.new do loop do
34
+ sleep(sleeping)
35
+ nowDelta=Time.now-delta
36
+ l=@th.select { |th,tm| (tm[0]<nowDelta) }
37
+ l.each { |th,tm| info("killing thread") ; th.kill; @th.delete(th) ; tm[1].close rescue nil }
38
+ end ; end
39
+ end
40
+ def initialize(port,root,name,cadence,timeout,options)
41
+ raise("tcp port illegal #{port}") unless port.to_i>=80
42
+ raise("root not exist #{root}") unless File.exists?(root)
43
+ @cb_log= options["logg"]
44
+ @last_mtime=File.mtime(__FILE__)
45
+ @port=port.to_i
46
+ @root=root
47
+ @name=name
48
+ @rootd=root[-1,1]=="/" ? root : root+"/"
49
+ @timeout=timeout
50
+ @th={}
51
+ @cb={}
52
+ @redirect={}
53
+ info(" serveur http #{port} on #{@rootd} ready!")
54
+ observe(cadence,timeout*2)
55
+ @thm=Thread.new {
56
+ loop {
57
+ nbError=0
58
+ begin
59
+ session=nil
60
+ @server = TCPServer.new('0.0.0.0', @port)
61
+ @server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
62
+ while (session = @server.accept)
63
+ nbError=0
64
+ run(session)
65
+ end
66
+ rescue Exception => e
67
+ nbError+=1
68
+ error($!.to_s + " " + $!.backtrace[0..2].join(" "))
69
+ session.close rescue nil
70
+ @server.close rescue nil
71
+ end
72
+ sleep(3); info("restart accept")
73
+ }
74
+ }
75
+ end
76
+ def run(session)
77
+ if ! File.exists?(@root)
78
+ sendError(session,500,txt="root directory unknown: #{@root}") rescue nil
79
+ session.close rescue nil
80
+ else
81
+ Thread.new(session) do |sess|
82
+ @th[Thread.current]=[Time.now,sess]
83
+ request(sess)
84
+ @th.delete(Thread.current)
85
+ end
86
+ end
87
+ end
88
+ def serve(uri,&blk)
89
+ @cb[uri] = blk
90
+ end
91
+ def request(session)
92
+ request = session.gets
93
+ uri = (request.split(/\s+/)+['','',''])[1]
94
+ #info uri
95
+ service,param,*bidon=(uri+"?").split(/\?/)
96
+ params=Hash[*(param.split(/#/)[0].split(/[=&]/))] rescue {}
97
+ params.each { |k,v| params[k]=unescape(v) }
98
+ uri=unescape(service)[1..-1].gsub(/\.\./,"")
99
+ userpass=nil
100
+ if (buri=uri.split(/@/)).size>1
101
+ uri=buri[1..-1].join("@")
102
+ userpass=buri[0].split(/:/)
103
+ end
104
+ read_header(session,params)
105
+ do_service(session,request,uri,userpass,params)
106
+ rescue Exception => e
107
+ error("Error Web get on #{request}: \n #{$!.to_s} \n #{$!.backtrace.join("\n ")}" ) rescue nil
108
+ session.write "HTTP/1.0 501 NOK\r\nContent-type: text/html\r\n\r\n<html><head><title>WS</title></head><body>Error : #{$!}" rescue nil
109
+ ensure
110
+ session.close rescue nil
111
+ end
112
+ def read_header(session,params)
113
+ head=session.gets("\r\n\r\n")
114
+ head.split(/\r\n/m).each { |line| name,data=line.split(": ",2) ; params["HEAD-"+name.upcase]=data }
115
+ if params["HEAD-CONTENT-LENGTH"]
116
+ len= params["HEAD-CONTENT-LENGTH"].split(/\s+/).last.to_i
117
+ params["HEAD-CONTENT-LENGTH"]=len
118
+ data=""
119
+ while len>0
120
+ d=session.read(len>64*1024 ? 64*1024 : len)
121
+ raise("closed") if !d
122
+ len -= d.length
123
+ data+=d
124
+ end
125
+ params["HEAD-DATA"]=data
126
+ end
127
+ end
128
+
129
+ def redirect(o,d)
130
+ @redirect[o]=d
131
+ end
132
+ def do_service(session,request,service,user_passwd,params)
133
+ logg(session.peeraddr.last,request.chomp)
134
+ redir=@redirect["/"+service]
135
+ service=redir.gsub(/^\//,"") if @redirect[redir]
136
+ aservice=to_absolute(service)
137
+ if redir && ! @redirect[redir]
138
+ do_service(session,request,redir.gsub(/^\//,""),user_passwd,params)
139
+ elsif @cb["/"+service]
140
+ begin
141
+ code,type,data= @cb["/"+service].call(params)
142
+ if code==0 && data != '/'+service
143
+ do_service(session,request,data[1..-1],user_passwd,params)
144
+ else
145
+ code==200 ? sendData(session,type,data) : sendError(session,code,data)
146
+ end
147
+ rescue
148
+ logg session.peeraddr.last,"Error in get /#{service} : #{$!}"
149
+ sendError(session,501,$!.to_s)
150
+ end
151
+ elsif service =~ /^stop/
152
+ sendData(session,".html","Stopping...");
153
+ Thread.new() { sleep(0.1); stop_browser() }
154
+ elsif File.directory?(aservice)
155
+ sendData(session,".html",makeIndex(aservice))
156
+ elsif File.exists?(aservice)
157
+ sendFile(session,aservice)
158
+ else
159
+ info("unknown request serv=#{service} params=#{params.inspect} #{File.exists?(service)}")
160
+ sendError(session,500,"unknown request serv=#{aservice} params=#{params.inspect} #{File.exists?(service)}");
161
+ end
162
+ end
163
+ def stop_browser
164
+ info "exit on web demand !"
165
+ [@tho,@thm].each { |th| th.kill }
166
+ @server.close rescue nil
167
+ end
168
+ def makeIndex(adir)
169
+ dir=to_relative(adir)
170
+ dirs,files=Dir.glob(adir==@rootd ? "#{@rootd}*" : "#{adir}/*").sort.partition { |f| File.directory?(f)}
171
+
172
+ updir = hescape( dir.split(/\//)[0..-2].join("/"))
173
+ updir="/" if updir.length==0
174
+ up=(dir!="/") ? "<input type='button' onclick='location.href=\"#{updir}\"' value='Parent'>" : ""
175
+ "<html><head><title>#{dir}</title></head>\n<body><h3><center>#{@name} : #{dir[0..-1]}</center></h3>\n<hr>#{up}<br>#{to_table(dirs.map {|s| " <a href='#{hescape(to_relative(s))}'>"+File.basename(s)+"/"+"</a>\n"})}<hr>#{to_tableb(files) {|f| [" <a href='#{hescape(to_relative(f))}'>"+File.basename(f)+"</a>",n3(File.size(f)),File.mtime(f).strftime("%d/%m/%Y %H:%M:%S")]}}</body></html>"
176
+ end
177
+ def to_relative(f) f.gsub(/^#{@rootd}/,"/") end
178
+ def to_absolute(f) "#{@rootd}#{f.gsub(/^\//,'')}" end
179
+ def n3(n)
180
+ u=" B"
181
+ if n> 10000000
182
+ n=n/(1024*1024)
183
+ u=" MB"
184
+ elsif n> 100000
185
+ n=n/1024
186
+ u=" KB"
187
+ end
188
+ "<div style='width:100px;text-align:right;'>#{(n.round.to_i.to_s.reverse.gsub(/(\d\d\d)(?=\d)/,'\1 ' ).reverse) +u} | </div>"
189
+ end
190
+ def to_table(l)
191
+ "<table><tr>#{l.map {|s| "<td>#{s}</td>"}.join("</tr><tr>")}</tr></table>"
192
+ end
193
+ def to_tableb(l,&bl)
194
+ "<table><tr>#{l.map {|s| "<td>#{bl.call(s).join("</td><td>")}</td>"}.join("</tr><tr>")}</tr></table>"
195
+ end
196
+ def sendError(sock,no,txt=nil)
197
+ if txt
198
+ txt="<html><body><code><pre></pre>#{txt}</code></body></html>"
199
+ end
200
+ sock.write "HTTP/1.0 #{no} NOK\r\nContent-type: #{mime(".html")}\r\n\r\n <html><p>Error #{no} : #{txt}</p></html>"
201
+ end
202
+ def sendData(sock,type,content)
203
+ sock.write "HTTP/1.0 200 OK\r\nContent-Type: #{mime(type)}\r\nContent-Length: #{content.size}\r\n\r\n"
204
+ sock.write(content)
205
+ end
206
+ def sendFile(sock,filename)
207
+ s=File.size(filename)
208
+ if s < 0 || s>60_000_000 || File.extname(filename).downcase==".lnk"
209
+ logg @name,"Error reading file/File not downloadable #{File.basename(filename)} : (size=#{s})"
210
+ sendError(sock,500,"Error reading file/File not downloadable #{filename} : (size=#{s})" )
211
+ return
212
+ end
213
+ logg @name,filename," #{s/(1024*1024)} Mo" if s>10*1000_000
214
+ timeout([s/(512*1024),30.0].max.to_i) {
215
+ sock.write "HTTP/1.0 200 OK\r\nContent-Type: #{mime(filename)}\r\nContent-Length: #{File.size(filename)}\r\nLast-Modified: #{httpdate(File.mtime(filename))}\r\nDate: #{httpdate(Time.now)}\r\n\r\n"
216
+ File.open(filename,"rb") do |f|
217
+ f.binmode; sock.binmode;
218
+ ( sock.write(f.read(32*1024)) while (! f.eof? && ! sock.closed?) ) rescue nil
219
+ end
220
+ }
221
+ end
222
+ def httpdate( aTime ); (aTime||Time.now).gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" ); end
223
+ def mime(string)
224
+ MIME[string.split(/\./).last] || "application/octet-stream"
225
+ end
226
+ LICON="&#9728;&#9731;&#9742;&#9745;&#9745;&#9760;&#9763;&#9774;&#9786;&#9730;".split(/;/).map {|c| c+";"}
227
+ MIME={"png" => "image/png", "gif" => "image/gif", "html" => "text/html","htm" => "text/html",
228
+ "js" => "text/javascript" ,"css" => "text/css","jpeg" => "image/jpeg" ,"jpg" => "image/jpeg",
229
+ ".json" => "applicatipon/json",
230
+ "pdf"=> "application/pdf" , "svg" => "image/svg+xml","svgz" => "image/svg+xml",
231
+ "xml" => "text/xml" ,"xsl" => "text/xml" ,"bmp" => "image/bmp" ,"txt" => "text/plain" ,
232
+ "rb" => "text/plain" ,"pas" => "text/plain" ,"tcl" => "text/plain" ,"java" => "text/plain" ,
233
+ "c" => "text/plain" ,"h" => "text/plain" ,"cpp" => "text/plain", "xul" => "application/vnd.mozilla.xul+xml",
234
+ "doc" => "application/msword", "docx" => "application/msword","dot"=> "application/msword",
235
+ "xls" => "application/vnd.ms-excel","xla" => "application/vnd.ms-excel","xlt" => "application/vnd.ms-excel","xlsx" => "application/vnd.ms-excel",
236
+ "ppt" => "application/vnd.ms-powerpoint", "pptx" => "application/vnd.ms-powerpoint"
237
+ }
238
+ end # 220 loc webserver :)
239
+
240
+ class Webserver < WebserverAbstract
241
+ def initialize(port=7080,cadence=10,timeout=120)
242
+ super(port,Dir.getwd(),"",cadence,timeout)
243
+ end
244
+ end
245
+ class WebserverRoot < WebserverAbstract
246
+ def initialize(port=7080,root=".",name="wwww",cadence=10,timeout=120,options={})
247
+ super(port,root,name,cadence,timeout,options)
248
+ end
249
+ end
250
+ def cliweb(root=Dir.getwd,port=59999)
251
+ Thread.abort_on_exception = false
252
+ BasicSocket.do_not_reverse_lookup = true
253
+ $ws=WebserverRoot.new(port,root,'femto ws',10,300, {});
254
+ puts "Server root path #{root} with port #{port}"
255
+ sleep
256
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: femtows
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Regis d'Aubarede
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-14 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: ! "a tiny web server, for local file transfert, \nembededded, http experimentations\n"
15
+ email: regis.aubarede@gmail.com
16
+ executables:
17
+ - femtows.bat
18
+ - femtows.sh
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/femtows.bat
23
+ - bin/femtows.sh
24
+ - CHANGELOG.txt
25
+ - demo.rb
26
+ - femtows.gemspec
27
+ - femtows.log
28
+ - gitc.bat
29
+ - guidemo.rb
30
+ - lib/femtows.rb
31
+ - README.md
32
+ - VERSION
33
+ homepage: http://github.com/raubarede/femtows
34
+ licenses: []
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.15
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: a tiny webserver
57
+ test_files: []
58
+ has_rdoc: