Henning Koch
Head of Development at
makandra
@triskweline
Entire app runs on a single computer
Entire app is (often) written in a single language
Popular languages: Java, C#, C++, Objective-C
Application runs on multiple computers
Typical: One server, many clients (= web browsers)
Client and server run different parts of the application
and they communicate over HTTP
Application is written in a "stack" of many technologies
Typical stack: HTML, CSS, JavaScript, Ruby/Node/Java/C#, SQL
Language | Purpose | Runs where? |
---|---|---|
HTML | Textual content | Client |
CSS | Layout & Design | Client |
JavaScript | Client-side interaction | Client |
HTTP | Client/server communication | Client/server |
Ruby/Node/Java/C# | Controller & Model | Server |
SQL | Database access | Server |
http://webdev101.makandra.de
https://github.com/makandra/webdev101
<html>
<body>
<h1>Hi there!</h1>
<p>Look at this image:</p>
<img src="image.jpg">
</body>
</html>
View example
(use the inspector)
User enters into her browser's address bar:
http://makandra.com/page.html
What happens?
Browser asks server:
GET /page.html HTTP/1.1 # I want the file /page.html
Host: makandra.com # I want it from makandra.com
Accept: text/html, text/plain # I understand HTML and plain text
Server looks for a local file page.html
and replies:
HTTP/1.1 200 OK # I found what you wanted
Content-Length: 114 # What you want has 114 bytes
Content-Type: text/html # And it's in HTML format
# Here it comes:
<html>
<body>
<h1>Hi there!</h1>
<p>Look at this image:</p>
<img src="image.jpg">
</body>
</html>
The browser parses the HTML and encounters this tag:
<img src="image.jpg">
What happens?
Browser asks server:
GET /image.jpg HTTP/1.1 # I want the file /image.jpg
Host: makandra.com # I want it from makandra.com
Accept: image/jpeg, image/png # I understand JPEG and PNG
Server looks for a local file image.jpg
and replies:
HTTP/1.1 200 OK # I found what you wanted
Content-Length: 67840 # What you want has 66 KB
Content-Type: image/jpeg # And it's in JPEG format
# Here it comes:
FF D8 FF E0 00 10 4A 46
49 46 00 01 01 01 00 48
00 48 00 00 FF E1 1C 13
45 78 69 66 00 00 49 49
2A 00 08 00 00 00 0D 00
0F 01 02 00 06 00 00 00
...
<html>
<body>
<h1>About Capybaras</h1>
<p>
The capybara is the
largest rodent
in the world.
</p>
</body>
</html>
<html>
<body>
<h1>About Capybaras</h1>
<p>
The capybara is the
largest rodent
in the world.
</p>
</body>
</html>
body {
font-family: 'Arial';
}
h1 {
text-transform: uppercase;
font-style: italic;
}
b {
background-color: #ff9900;
}
#ff0000
#00ff00
#0000ff
#ffff00
#ffffff
#000000
#ff9900
#4488bb
#9900ff
#777777
#22aa88
#885555
red
yellow
fuchsia
rebeccapurple
rgba(255, 0, 0, 1.0)
rgba(255, 0, 0, 0.75)
rgba(255, 0, 0, 0.5)
rgba(255, 0, 0, 0.25)
hsl(0, 100%, 50%)
hsl(30, 100%, 50%)
hsl(30, 50%, 50%)
hsl(30, 50%, 70%)
<html>
<head>
<style>
h1 {
color: #ff0000;
}
</style>
</head>
<body>
...
</body>
</html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
...
</body>
</html>
The browser parses the HTML and encounters this tag:
<link rel="stylesheet" href="styles.css">
What happens?
Browser asks server:
GET /styles.css HTTP/1.1 # I want the file /styles.css
Host: makandra.com # I know you as makandra.com
Accept: text/css # I understand the CSS format
Server looks for a local file styles.css
and replies:
HTTP/1.1 200 OK # I found what you wanted
Content-Length: 182 # What you want has 182 bytes
Content-Type: text/css # And it's in CSS format
# Here it comes:
body {
font-family: 'Arial';
}
h1 {
text-transform: uppercase;
font-style: italic;
}
...
body {
font-family: 'Arial';
}
h1 {
text-transform: uppercase;
font-style: italic;
}
b {
background-color: #ff0000;
}
body {
font-family: 'Arial';
}
h1 {
text-transform: uppercase;
font-style: italic;
}
b {
background-color: #ff0000;
}
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
h1 { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
p { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
p.introduction { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
p:last-child { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
h1, p:last-child { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
h1+p { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
b { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
p.introduction b { ... }
<h1>About Capybaras</h1>
<p class="introduction">
The capybara is the largest <b>rodent</b>
in the world.
</p>
<p>
The capybara belongs to the subfamily
<b>Hydrochoerinae</b> along with ...
</p>
Instead of starting from scratch you can build upon someone else's CSS.
CSS frameworks like Bootstrap or Foundation give you:
Let's write a web app that returns the current time:
http://timeapp.com/now.html
Browser asks server:
GET /now.html HTTP/1.1 # I want the file /now.html
Host: timeapp.com # I want it from timeapp.com
Accept: text/html, text/plain # I understand HTML and plain text
Server looks for a local file now.html
and replies:
HTTP/1.1 200 OK # I found what you wanted
Content-Length: 22 # What you want has 22 bytes
Content-Type: text/plain # And it's in plain text
# Here it comes:
The time is 15:00:00.
Browser asks server:
GET /now.html HTTP/1.1 # I want the file /now.html
Host: timeapp.com # I want it from timeapp.com
Accept: text/html, text/plain # I understand HTML and plain text
Server runs a program time.exe
.
The program time.exe
writes the current time to STDOUT
:
print "The time is " + Time.now.to_s
Server takes the program output and sends it the browser:
HTTP/1.1 200 OK # I found what you wanted
Content-Length: 22 # What you want has 22 bytes
Content-Type: text/plain # And it's in plain text
# Here it comes:
The time is 14:37:05.
The browser simply makes a request and gets back a string of HTML.
The browser doesn't even know if the HTML was generated by an app, or was simply read from a static file.
Popular choices:
The following examples are in Ruby, but concepts apply to all languages.
class User
def initialize(name, age)
@name = name
@age = age
end
def adult?
@age >= 18
end
end
user = User.new('Max', 18)
puts user.name
class User {
private String name;
private int age;
public User(name, age) {
this.name = name;
this.age = age;
}
public boolean isAdult() {
return age >= 18;
}
}
var user = new User("Max", 18);
System.out.println(user.getName());
To run the chat app:
cd examples/2000_chat/src
ruby app.rb
You can now access the app at http://localhost:4567/
The user types into her address bar:
http://chatapp.com/
What happens?
Browser asks server:
GET / HTTP/1.1 # I want /
Host: chatapp.com # I want it from chatapp.com
Accept: text/html, text/plain # I understand HTML and plain text
Server asks the program app.rb
for /
.
The program prints a new HTML page with the latest chat messages.
The server takes the program output and sends it the browser:
HTTP/1.1 200 OK # I found what you wanted
Content-Length: 1589 # What you want has 1589 bytes
Content-Type: text/html # And it's in HTML format
# Here it comes:
<html>
...
</html>
The user sends a message to the chat channel:
Hello world
What happens?
Browser asks server:
POST /send HTTP/1.1 # I'm posting data to /send
Host: chatapp.com # I'm posting it to chatapp.com
Content-Type: application/x-www-form-urlencoded # I'm posting key/value pairs
# Here it comes:
message=Hello+world
Server calls the program app.rb
with the posted data.
The program stores the message and prints a HTTP response.
The server takes the program output and sends it the browser:
HTTP/1.1 303 See Other # This conversation continues elsewhere
Location: / # Request / to continue
The browser automatically makes another request to /
and gets a fresh copy of the message list.
These things require code that runs in the browser, not on the server.
// This is a comment
let x = 1
let y = x + 1
console.log(y) // 2
function square(n) {
return n * n
}
let x = square(16)
console.log(x) // 256
let z = square
console.log(z) // function { ... }
console.log(z(4)) // 16
one
two
three
let div = document.querySelector('div')
let children = div.children
console.log(children) // [one
, two
, three
]
let parent = children[0].parentNode
console.log(parent) // [...]
console.log(parent === div) // true
children[1].style.color = 'red'
<html>
<body>
Hi world
<script>
var x = 1;
var y = x + y;
</script>
</body>
</html>
<html>
<body>
Hi world
<script src="scripts.js"></script>
</body>
</html>
<html>
<body>
</body>
</html>
let button = document.querySelector('button')
button.addEventListener('click', function(event) {
alert('Button was clicked!')
})
window.addEventListener(function(event) {
alert('User scrolled!')
})
null
.this.foo()
to call your own methods.this
.function showLastMessage() {
let lastMessage = document.querySelector('.message:last-child')
if (lastMessage) {
lastMessage.scrollIntoView()
}
}
window.addEventListener('load', showLastMessage)
⇢ User requests a website
⇠ Server sends a fresh copy
⇢ User clicks a link
⇠ Server sends a fresh copy
⇢ User submits a form
⇠ Server sends a fresh copy
⇢ User reloads the page manually
⇠ Server sends a fresh copy
⇣ Another user sends a message
× Server has no way to push the update to the user
async function refresh() {
let response = await fetch('/')
let newHTML = await response.text()
let parser = new DOMParser()
let newDocument = parser.parseFromString(newHTML, 'text/html')
let newChannel = newDocument.querySelector('.channel')
let oldChannel = document.querySelector('.channel')
oldChannel.replaceWith(newChannel)
}
setInterval(refresh, 1000)
function search() {
let queryInput = document.querySelector('.search-form input')
let query = queryInput.value.toLowerCase()
let messageElements = document.querySelectorAll('.message')
for (let messageElement of messageElements) {
var text = messageElement.textContent.toLowerCase()
if (text.includes(query)) {
messageElement.style.display = 'block'
} else {
messageElement.style.display = 'none'
}
}
}
window.addEventListener('load', function() {
let queryInput = document.querySelector('.search-form input')
queryInput.addEventListener('input', search)
})
app.rb
Our current implementation has app.rb
is a single script
with a lot of responsibilities:
As software engineers we always try to separate responsibilities into individual modules.
get '/' do
@messages = File.read('messages.txt').lines
erb(:channel)
end
get '/send' do
message = params['message'] + "\n"
File.write('messages.txt', message, mode: 'a')
redirect('/')
end
db = Mydb::Client.new(host: 'localhost')
get '/' do
@messages = db.select_values('SELECT text FROM messages')
erb(:channel)
end
get '/send' do
message = params['message']
escaped = db.escape(message)
db.execute('INSERT INTO messages VALUES (' + escaped + ')')
redirect('/')
end
channel = Channel.new
get '/' do
@messages = channel.messages
erb(:channel)
end
get '/send' do
message = params['message']
channel.add_message(message)
redirect('/')
end
class Channel
def initialize
@db = Mydb::Client.new
end
def messages
@db.select_values('SELECT text FROM messages')
end
def add_message(message)
escaped = @db.escape(message)
@db.execute('INSERT INTO messages VALUES (' + escaped + ')')
end
end
Layer | Responsibility |
---|---|
Database | Data storage & queries |
Model | O/R mapping, business logic |
Controller | Layer orchestration |
View | Content |
Stylesheets | Optics (optional) |
JavaScript | Behavior enhancements (optional) |
Also known as Single-page apps or SPAs.
Server-side app | Client-side app | |
---|---|---|
Code complexity | low | high |
Language | your choice | JavaScript |
Data access | sync | async via API |
Initial load time | fast | slow |
Optimistic rendering | duplicates logic | view bound to client-side state |
Offline | very hard | hard |
@triskweline
henning.koch@makandra.de
http://webdev101.makandra.de
https://github.com/makandra/webdev101
telnet example.com 80