plezi 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/CHANGELOG.md +450 -0
- data/Gemfile +4 -0
- data/KNOWN_ISSUES.md +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +341 -0
- data/Rakefile +2 -0
- data/TODO.md +19 -0
- data/bin/plezi +301 -0
- data/lib/plezi.rb +125 -0
- data/lib/plezi/base/cache.rb +77 -0
- data/lib/plezi/base/connections.rb +33 -0
- data/lib/plezi/base/dsl.rb +177 -0
- data/lib/plezi/base/engine.rb +85 -0
- data/lib/plezi/base/events.rb +84 -0
- data/lib/plezi/base/io_reactor.rb +41 -0
- data/lib/plezi/base/logging.rb +62 -0
- data/lib/plezi/base/rack_app.rb +89 -0
- data/lib/plezi/base/services.rb +57 -0
- data/lib/plezi/base/timers.rb +71 -0
- data/lib/plezi/handlers/controller_magic.rb +383 -0
- data/lib/plezi/handlers/http_echo.rb +27 -0
- data/lib/plezi/handlers/http_host.rb +215 -0
- data/lib/plezi/handlers/http_router.rb +69 -0
- data/lib/plezi/handlers/magic_helpers.rb +43 -0
- data/lib/plezi/handlers/route.rb +272 -0
- data/lib/plezi/handlers/stubs.rb +143 -0
- data/lib/plezi/server/README.md +33 -0
- data/lib/plezi/server/helpers/http.rb +169 -0
- data/lib/plezi/server/helpers/mime_types.rb +999 -0
- data/lib/plezi/server/protocols/http_protocol.rb +318 -0
- data/lib/plezi/server/protocols/http_request.rb +133 -0
- data/lib/plezi/server/protocols/http_response.rb +294 -0
- data/lib/plezi/server/protocols/websocket.rb +208 -0
- data/lib/plezi/server/protocols/ws_response.rb +92 -0
- data/lib/plezi/server/services/basic_service.rb +224 -0
- data/lib/plezi/server/services/no_service.rb +196 -0
- data/lib/plezi/server/services/ssl_service.rb +193 -0
- data/lib/plezi/version.rb +3 -0
- data/plezi.gemspec +26 -0
- data/resources/404.erb +68 -0
- data/resources/404.haml +64 -0
- data/resources/404.html +67 -0
- data/resources/404.slim +63 -0
- data/resources/500.erb +68 -0
- data/resources/500.haml +63 -0
- data/resources/500.html +67 -0
- data/resources/500.slim +63 -0
- data/resources/Gemfile +85 -0
- data/resources/anorexic_gray.png +0 -0
- data/resources/anorexic_websockets.html +47 -0
- data/resources/code.rb +8 -0
- data/resources/config.ru +39 -0
- data/resources/controller.rb +139 -0
- data/resources/db_ac_config.rb +58 -0
- data/resources/db_dm_config.rb +51 -0
- data/resources/db_sequel_config.rb +42 -0
- data/resources/en.yml +204 -0
- data/resources/environment.rb +41 -0
- data/resources/haml_config.rb +6 -0
- data/resources/i18n_config.rb +14 -0
- data/resources/rakefile.rb +22 -0
- data/resources/redis_config.rb +35 -0
- data/resources/routes.rb +26 -0
- data/resources/welcome_page.html +72 -0
- data/websocket chatroom.md +639 -0
- metadata +141 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
module Plezi
|
2
|
+
|
3
|
+
# test connections with: openssl s_client -connect localhost:3000
|
4
|
+
|
5
|
+
# this class is a basic TCP socket service with SSL.
|
6
|
+
#
|
7
|
+
# a protocol should be assigned, or the service will fall back to an echo service.
|
8
|
+
#
|
9
|
+
# to-do: fix self certificate issue (fails).
|
10
|
+
class SSLService < BasicService
|
11
|
+
|
12
|
+
# instance methods
|
13
|
+
|
14
|
+
attr_reader :ssl_socket
|
15
|
+
|
16
|
+
# creates a new connection wrapper object for the new socket that was recieved from the `accept_nonblock` method call.
|
17
|
+
def initialize soc, parameters = {}
|
18
|
+
context = OpenSSL::SSL::SSLContext.new
|
19
|
+
context.set_params verify_mode: OpenSSL::SSL::VERIFY_NONE# OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
20
|
+
# context.options DoNotReverseLookup: true
|
21
|
+
if parameters[:ssl_cert] && parameters[:ssl_key]
|
22
|
+
context.cert = parameters[:ssl_cert]
|
23
|
+
context.key = parameters[:ssl_key]
|
24
|
+
else
|
25
|
+
context.cert, context.key = self.class.self_cert
|
26
|
+
end
|
27
|
+
context.cert_store = OpenSSL::X509::Store.new
|
28
|
+
context.cert_store.set_default_paths
|
29
|
+
@ssl_socket = OpenSSL::SSL::SSLSocket.new(soc, context)
|
30
|
+
@ssl_socket.sync_close = true
|
31
|
+
@socket = soc
|
32
|
+
@ssl_socket.accept
|
33
|
+
super
|
34
|
+
end
|
35
|
+
# identification markers
|
36
|
+
|
37
|
+
#returns the service type - set to normal
|
38
|
+
def service_type
|
39
|
+
'encrypted'
|
40
|
+
end
|
41
|
+
#returns true if the service is encrypted using the OpenSSL library.
|
42
|
+
def ssl?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns an IO-like object used for reading/writing (unlike the original IO object, this can be an SSL layer or any other wrapper object).
|
47
|
+
def io
|
48
|
+
touch
|
49
|
+
@ssl_socket
|
50
|
+
end
|
51
|
+
|
52
|
+
# reads from the connection
|
53
|
+
def read size = 1048576
|
54
|
+
data = ''
|
55
|
+
begin
|
56
|
+
loop { data << ssl_socket.read_nonblock( size) }
|
57
|
+
rescue Exception => e
|
58
|
+
|
59
|
+
end
|
60
|
+
touch unless data.empty?
|
61
|
+
data
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
#sends data over the connection
|
67
|
+
def _send data
|
68
|
+
ssl_socket.write data
|
69
|
+
end
|
70
|
+
|
71
|
+
#closes the connection
|
72
|
+
def _close
|
73
|
+
ssl_socket.flush rescue true
|
74
|
+
ssl_socket.close
|
75
|
+
end
|
76
|
+
|
77
|
+
# checks if the connection is closed
|
78
|
+
def _disconnected?
|
79
|
+
ssl_socket.closed? || ssl_socket.io.closed? rescue true # || ssl_socket.io.stat.mode == 0140222 <= if mode is read only, it's the same as closed.
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# SSL certificate
|
84
|
+
|
85
|
+
# returns the current self-signed certificate - or creates a new one, if there is no current certificate.
|
86
|
+
def self.self_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
87
|
+
@@self_cert ||= create_cert
|
88
|
+
return *@@self_cert
|
89
|
+
end
|
90
|
+
#creates a self-signed certificate
|
91
|
+
def self.create_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
92
|
+
unless cn
|
93
|
+
host_name = Socket::gethostbyname(Socket::gethostname)[0].split('.')
|
94
|
+
cn = ''
|
95
|
+
host_name.each {|n| cn << "/DC=#{n}"}
|
96
|
+
cn << "/CN=#{host_name.join('.')}"
|
97
|
+
end
|
98
|
+
# cn ||= "CN=#{Socket::gethostbyname(Socket::gethostname)[0] rescue Socket::gethostname}"
|
99
|
+
|
100
|
+
rsa = OpenSSL::PKey::RSA.new(bits)
|
101
|
+
cert = OpenSSL::X509::Certificate.new
|
102
|
+
cert.version = 2
|
103
|
+
cert.serial = 1
|
104
|
+
name = OpenSSL::X509::Name.parse(cn)
|
105
|
+
cert.subject = name
|
106
|
+
cert.issuer = name
|
107
|
+
cert.not_before = Time.now
|
108
|
+
cert.not_after = Time.now + (365*24*60*60)
|
109
|
+
cert.public_key = rsa.public_key
|
110
|
+
|
111
|
+
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
112
|
+
ef.issuer_certificate = cert
|
113
|
+
cert.extensions = [
|
114
|
+
ef.create_extension("basicConstraints","CA:FALSE"),
|
115
|
+
ef.create_extension("keyUsage", "keyEncipherment"),
|
116
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
117
|
+
ef.create_extension("extendedKeyUsage", "serverAuth"),
|
118
|
+
ef.create_extension("nsComment", comment),
|
119
|
+
]
|
120
|
+
aki = ef.create_extension("authorityKeyIdentifier",
|
121
|
+
"keyid:always,issuer:always")
|
122
|
+
cert.add_extension(aki)
|
123
|
+
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
|
124
|
+
|
125
|
+
return cert, rsa
|
126
|
+
end
|
127
|
+
|
128
|
+
# def self.cert_test
|
129
|
+
# #Creating a CA
|
130
|
+
# root_key = OpenSSL::PKey::RSA.new 2048 # the CA's public/private key
|
131
|
+
# root_ca = OpenSSL::X509::Certificate.new
|
132
|
+
# root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
|
133
|
+
# root_ca.serial = 1
|
134
|
+
# root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA"
|
135
|
+
# root_ca.issuer = root_ca.subject # root CA's are "self-signed"
|
136
|
+
# root_ca.public_key = root_key.public_key
|
137
|
+
# root_ca.not_before = Time.now
|
138
|
+
# root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
|
139
|
+
# ef = OpenSSL::X509::ExtensionFactory.new
|
140
|
+
# ef.subject_certificate = root_ca
|
141
|
+
# ef.issuer_certificate = root_ca
|
142
|
+
# root_ca.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
|
143
|
+
# root_ca.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
|
144
|
+
# root_ca.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
|
145
|
+
# root_ca.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
|
146
|
+
# root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
|
147
|
+
# #Creating an End-Point Certificate
|
148
|
+
# key = OpenSSL::PKey::RSA.new 2048
|
149
|
+
# cert = OpenSSL::X509::Certificate.new
|
150
|
+
# cert.version = 2
|
151
|
+
# cert.serial = 2
|
152
|
+
# cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate"
|
153
|
+
# cert.issuer = root_ca.subject # root CA is the issuer
|
154
|
+
# cert.public_key = key.public_key
|
155
|
+
# cert.not_before = Time.now
|
156
|
+
# cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity
|
157
|
+
# ef = OpenSSL::X509::ExtensionFactory.new
|
158
|
+
# ef.subject_certificate = cert
|
159
|
+
# ef.issuer_certificate = root_ca
|
160
|
+
# cert.add_extension extension_factory.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
|
161
|
+
# cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
|
162
|
+
# cert.sign(root_key, OpenSSL::Digest::SHA256.new)
|
163
|
+
|
164
|
+
# # #Creating a Certificate
|
165
|
+
# # name = OpenSSL::X509::Name.parse 'CN=localhost/DC=localhost'
|
166
|
+
# # cert = OpenSSL::X509::Certificate.new
|
167
|
+
# # cert.version = 2
|
168
|
+
# # cert.serial = 0
|
169
|
+
# # cert.not_before = Time.now
|
170
|
+
# # cert.not_after = Time.now + 3600
|
171
|
+
# # key = OpenSSL::PKey::RSA.new 2048
|
172
|
+
# # cert.public_key = key.public_key
|
173
|
+
# # cert.subject = name
|
174
|
+
|
175
|
+
# # # Certificate Extensions
|
176
|
+
# # cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE', true)
|
177
|
+
# # cert.add_extension extension_factory.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
|
178
|
+
# # cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash')
|
179
|
+
|
180
|
+
# # # Signing a Certificate
|
181
|
+
# # cert.issuer = name
|
182
|
+
# # cert.sign key, OpenSSL::Digest::SHA1.new
|
183
|
+
|
184
|
+
# #server
|
185
|
+
# context = OpenSSL::SSL::SSLContext.new
|
186
|
+
# context.cert = cert
|
187
|
+
# context.key = key
|
188
|
+
# tcp_server = TCPServer.new 8080
|
189
|
+
# ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context
|
190
|
+
# ssl_socket = ssl_server.accept
|
191
|
+
# end
|
192
|
+
end
|
193
|
+
end
|
data/plezi.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'plezi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "plezi"
|
8
|
+
spec.version = Plezi::VERSION
|
9
|
+
spec.authors = ["Boaz Segev"]
|
10
|
+
spec.email = ['boaz@2be.co.il']
|
11
|
+
spec.summary = %q{The Ruby Websocket Framework with RESTful and HTTP streaming support.}
|
12
|
+
spec.description = %q{Plezi is The Ruby Websocket and HTTP streaming Framework. Advance to next step in Ruby evolution - a framework with an integrated server, ready for seamless WebSockets and RESTful applications.}
|
13
|
+
spec.homepage = "http://boazsegev.github.io/plezi/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
|
24
|
+
spec.post_install_message = "Plezi is hungry - feed it your code!"
|
25
|
+
|
26
|
+
end
|
data/resources/404.erb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<head>
|
3
|
+
<style>
|
4
|
+
body, html
|
5
|
+
{
|
6
|
+
background-color: #eee;
|
7
|
+
padding: 0; margin: 0;
|
8
|
+
width: 100%;
|
9
|
+
}
|
10
|
+
h1
|
11
|
+
{
|
12
|
+
background-color: #ddd;
|
13
|
+
color: #008;
|
14
|
+
text-align: center;
|
15
|
+
border-bottom: 1px solid #000;
|
16
|
+
margin: 0;
|
17
|
+
padding: 0.5em;
|
18
|
+
width: auto;
|
19
|
+
}
|
20
|
+
p
|
21
|
+
{
|
22
|
+
color:#004;
|
23
|
+
font-size: 1.2em;
|
24
|
+
padding: 0 1em;
|
25
|
+
}
|
26
|
+
#wrapper
|
27
|
+
{
|
28
|
+
background-color: #fff;
|
29
|
+
margin: 1em 5%;
|
30
|
+
padding: 0 0 2% 0;
|
31
|
+
border-radius: 20px;
|
32
|
+
min-height: 50%;
|
33
|
+
color: #007;
|
34
|
+
}
|
35
|
+
#wrapper h2
|
36
|
+
{
|
37
|
+
background-color: #ddd;
|
38
|
+
color: #008;
|
39
|
+
text-align: center;
|
40
|
+
margin: 0 0 1em 0;
|
41
|
+
padding: 0.5em 0;
|
42
|
+
width: 100%;
|
43
|
+
border-radius: 20px;
|
44
|
+
}
|
45
|
+
#wrapper p{ padding: 0 2%;}
|
46
|
+
#wrapper #missing
|
47
|
+
{
|
48
|
+
color:#904;
|
49
|
+
font-size: 1.4em;
|
50
|
+
padding: 0.5em 0;
|
51
|
+
text-align: center;
|
52
|
+
background-color: #fee;
|
53
|
+
border-bottom: 1px solid #800;
|
54
|
+
margin: 0;
|
55
|
+
}
|
56
|
+
</style>
|
57
|
+
</head>
|
58
|
+
<body>
|
59
|
+
<h1>Plezi 404 error code (missing, not broken)...</h1>
|
60
|
+
<div id='wrapper'>
|
61
|
+
<h2 id='missing'>
|
62
|
+
couldn't find
|
63
|
+
<%= defined?(request) ? request.path : "what you're looking for..." %>
|
64
|
+
</h2>
|
65
|
+
<p>Sorry, we couldn't find what you're looking for...</p>
|
66
|
+
<p>... but we can always bring you something nice from another location, right?</p>
|
67
|
+
</div>
|
68
|
+
</body>
|
data/resources/404.haml
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
!!!5
|
2
|
+
%head
|
3
|
+
:css
|
4
|
+
body, html
|
5
|
+
{
|
6
|
+
background-color: #eee;
|
7
|
+
padding: 0; margin: 0;
|
8
|
+
width: 100%;
|
9
|
+
}
|
10
|
+
h1
|
11
|
+
{
|
12
|
+
background-color: #ddd;
|
13
|
+
color: #008;
|
14
|
+
text-align: center;
|
15
|
+
border-bottom: 1px solid #000;
|
16
|
+
margin: 0;
|
17
|
+
padding: 0.5em;
|
18
|
+
width: auto;
|
19
|
+
}
|
20
|
+
p
|
21
|
+
{
|
22
|
+
color:#004;
|
23
|
+
font-size: 1.2em;
|
24
|
+
padding: 0 1em;
|
25
|
+
}
|
26
|
+
#wrapper
|
27
|
+
{
|
28
|
+
background-color: #fff;
|
29
|
+
margin: 1em 5%;
|
30
|
+
padding: 0 0 2% 0;
|
31
|
+
border-radius: 20px;
|
32
|
+
min-height: 50%;
|
33
|
+
color: #007;
|
34
|
+
}
|
35
|
+
#wrapper h2
|
36
|
+
{
|
37
|
+
background-color: #ddd;
|
38
|
+
color: #008;
|
39
|
+
text-align: center;
|
40
|
+
margin: 0 0 1em 0;
|
41
|
+
padding: 0.5em 0;
|
42
|
+
width: 100%;
|
43
|
+
border-radius: 20px;
|
44
|
+
}
|
45
|
+
#wrapper p{ padding: 0 2%;}
|
46
|
+
#wrapper #missing
|
47
|
+
{
|
48
|
+
color:#904;
|
49
|
+
font-size: 1.4em;
|
50
|
+
padding: 0.5em 0;
|
51
|
+
text-align: center;
|
52
|
+
background-color: #fee;
|
53
|
+
border-bottom: 1px solid #800;
|
54
|
+
margin: 0;
|
55
|
+
}
|
56
|
+
%body
|
57
|
+
%h1< Plezi 404 error code (missing, not broken)...
|
58
|
+
#wrapper
|
59
|
+
%h2#missing
|
60
|
+
couldn't find
|
61
|
+
= defined?(request) ? request.path : "what you're looking for..."
|
62
|
+
%p Sorry, we couldn't find what you're looking for...
|
63
|
+
%p ... but we can always bring you something nice from another location, right?
|
64
|
+
/ want to use your layout? use the :haml_concat method to wrap the 404 error code message. see http://haml.info/docs/yardoc/Haml/Helpers.html#haml_concat-instance_method .
|
data/resources/404.html
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<head>
|
3
|
+
<style>
|
4
|
+
body, html
|
5
|
+
{
|
6
|
+
background-color: #eee;
|
7
|
+
padding: 0; margin: 0;
|
8
|
+
width: 100%;
|
9
|
+
}
|
10
|
+
h1
|
11
|
+
{
|
12
|
+
background-color: #ddd;
|
13
|
+
color: #008;
|
14
|
+
text-align: center;
|
15
|
+
border-bottom: 1px solid #000;
|
16
|
+
margin: 0;
|
17
|
+
padding: 0.5em;
|
18
|
+
width: 100%;
|
19
|
+
}
|
20
|
+
p
|
21
|
+
{
|
22
|
+
color:#004;
|
23
|
+
font-size: 1.2em;
|
24
|
+
padding: 0 1em;
|
25
|
+
}
|
26
|
+
#wrapper
|
27
|
+
{
|
28
|
+
background-color: #fff;
|
29
|
+
margin: 1em 5%;
|
30
|
+
padding: 0 0 2%;
|
31
|
+
border-radius: 20px;
|
32
|
+
height: auto;
|
33
|
+
color: #007;
|
34
|
+
}
|
35
|
+
#wrapper h2
|
36
|
+
{
|
37
|
+
background-color: #ddd;
|
38
|
+
color: #008;
|
39
|
+
text-align: center;
|
40
|
+
margin: 0 0 1em 0;
|
41
|
+
padding: 0.5em 0;
|
42
|
+
width: 100%;
|
43
|
+
border-radius: 20px;
|
44
|
+
}
|
45
|
+
#wrapper p{ padding: 0 2%;}
|
46
|
+
#wrapper #missing
|
47
|
+
{
|
48
|
+
color:#904;
|
49
|
+
font-size: 1.4em;
|
50
|
+
padding: 0.5em 0;
|
51
|
+
text-align: center;
|
52
|
+
background-color: #fee;
|
53
|
+
border-bottom: 1px solid #800;
|
54
|
+
margin: 0;
|
55
|
+
}
|
56
|
+
</style>
|
57
|
+
</head>
|
58
|
+
<body>
|
59
|
+
<h1>Plezi 404 error code (missing, not broken)...</h1>
|
60
|
+
<div id='wrapper'>
|
61
|
+
<h2 id='missing'>
|
62
|
+
couldn't find your request...
|
63
|
+
</h2>
|
64
|
+
<p>Sorry, we couldn't find what you're looking for...</p>
|
65
|
+
<p>... but we can always bring you something nice from another location, right?</p>
|
66
|
+
</div>
|
67
|
+
</body>
|
data/resources/404.slim
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
doctype html
|
2
|
+
head
|
3
|
+
css:
|
4
|
+
body, html
|
5
|
+
{
|
6
|
+
background-color: #eee;
|
7
|
+
padding: 0; margin: 0;
|
8
|
+
width: 100%;
|
9
|
+
}
|
10
|
+
h1
|
11
|
+
{
|
12
|
+
background-color: #ddd;
|
13
|
+
color: #008;
|
14
|
+
text-align: center;
|
15
|
+
border-bottom: 1px solid #000;
|
16
|
+
margin: 0;
|
17
|
+
padding: 0.5em;
|
18
|
+
width: auto;
|
19
|
+
}
|
20
|
+
p
|
21
|
+
{
|
22
|
+
color:#004;
|
23
|
+
font-size: 1.2em;
|
24
|
+
padding: 0 1em;
|
25
|
+
}
|
26
|
+
#wrapper
|
27
|
+
{
|
28
|
+
background-color: #fff;
|
29
|
+
margin: 1em 5%;
|
30
|
+
padding: 0 0 2% 0;
|
31
|
+
border-radius: 20px;
|
32
|
+
min-height: 50%;
|
33
|
+
color: #007;
|
34
|
+
}
|
35
|
+
#wrapper h2
|
36
|
+
{
|
37
|
+
background-color: #ddd;
|
38
|
+
color: #008;
|
39
|
+
text-align: center;
|
40
|
+
margin: 0 0 1em 0;
|
41
|
+
padding: 0.5em 0;
|
42
|
+
width: 100%;
|
43
|
+
border-radius: 20px;
|
44
|
+
}
|
45
|
+
#wrapper p { padding: 0 2%; }
|
46
|
+
#wrapper #missing
|
47
|
+
{
|
48
|
+
color:#904;
|
49
|
+
font-size: 1.4em;
|
50
|
+
padding: 0.5em 0;
|
51
|
+
text-align: center;
|
52
|
+
background-color: #fee;
|
53
|
+
border-bottom: 1px solid #800;
|
54
|
+
margin: 0;
|
55
|
+
}
|
56
|
+
body
|
57
|
+
h1 Plezi 404 error code (missing, not broken)...
|
58
|
+
#wrapper
|
59
|
+
h2#missing
|
60
|
+
| couldn't find
|
61
|
+
=< defined?(request) ? request.path : "what you're looking for..."
|
62
|
+
p Sorry, we couldn't find what you're looking for...
|
63
|
+
p ... but we can always bring you something nice from another location, right?
|