plezi 0.7.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.
- 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?
|