En demon er en bakgrunnsprosess som kjører kontinuerlig uten brukerinteraksjon. Webtjenere, databaser og systemtjenester er typiske demoner. Målet er å skape en robust, sikker tjeneste som kan starte automatisk og håndtere feil elegant.
pid_t pid = fork();
if (pid > 0) {
exit(0); // Forelder avslutter
}
// Barn fortsetter som orphan → adopteres av init
if (setsid() == -1) {
perror("setsid failed");
exit(1);
}
// Frigjør controlling terminal og bli process group leader
pid = fork();
if (pid > 0) {
exit(0); // Session leader avslutter
}
// Hindrer gjenakkvisisjon av controlling terminal
if (chdir("/") == -1) {
perror("chdir failed");
exit(1);
}
// Unngå å låse filesystemer
for (int fd = 0; fd < getdtablesize(); fd++) {
close(fd);
}
// Eller: closefrom(0) på systemer som støtter det
int devnull = open("/dev/null", O_RDWR);
dup2(devnull, STDIN_FILENO);
dup2(devnull, STDOUT_FILENO);
dup2(devnull, STDERR_FILENO);
if (devnull > 2) close(devnull);
Container-teknologi deler kjerne med vertssystemet, men isolerer prosesser, filer og nettverk. Dette gir høy ytelse og ressurseffektivitet sammenlignet med full virtualisering.
# PID Namespace - isolerte prosess-IDs
unshare --pid --fork --mount-proc chroot /container/root /bin/sh
# Network Namespace - eget nettverk-stack
ip netns add container1
ip netns exec container1 ip link set dev lo up
# Mount Namespace - eget filesystem view
unshare --mount chroot /container/root /bin/sh
# User Namespace - UID/GID mapping
unshare --user --map-root-user /bin/sh
# CPU-begrensning
echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
# Minne-begrensning
echo 1G > /sys/fs/cgroup/memory/mycontainer/memory.limit_in_bytes
# I/O begrensning
echo "8:0 1048576" > /sys/fs/cgroup/blkio/mycontainer/blkio.throttle.read_bps_device
Docker/Podman
Orkestrerer containere
runc/crun
Faktisk containerstart
Docker Hub/Quay
Lagring av images
Kubernetes/Swarm
Cluster management
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
WORKDIR /app
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs . .
USER nodejs
EXPOSE 3000
CMD ["node", "server.js"]
HTML5 introduserte semantiske elementer, native multimedia-støtte, offline-kapabiliteter og kraftige JavaScript-APIer. DOCTYPE ble forenklet til <!DOCTYPE html>, og strict XHTML-syntaks ble valgfri.
<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Moderne webside</title>
</head>
<body>
<header>
<nav><!-- Navigasjon --></nav>
</header>
<main>
<article>
<section><!-- Innholdseksjon --></section>
</article>
<aside><!-- Sidebar --></aside>
</main>
<footer><!-- Bunntekst --></footer>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE users [
<!ELEMENT users (user+)>
<!ELEMENT user (name, email, role)>
<!ATTLIST user id ID #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT email (#PCDATA)>
<!ELEMENT role (admin|user|guest)>
]>
<users>
<user id="u1">
<name>Ola Nordmann</name>
<email>ola@example.com</email>
<role>admin</role>
</user>
</users>
DTD kan referere eksterne entiteter, som kan føre til informasjonslekkasje eller denial-of-service. Deaktiver eksterne entiteter i produksjon:
# Java
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
/* Grid layout */
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
/* Flexbox for komponentlayout */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Media queries for responsivitet */
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
}
SQLite er en embedded, server-less database som lagrer data i en enkelt fil. Perfect for utviklingsmiljøer og applikasjoner med moderate krav til samtidighet. Støtter full ACID-transaksjonalitet og de fleste SQL-standarder.
// Database connection med connection pooling
public class DatabaseManager {
private static final String DB_URL = "jdbc:sqlite:app.db";
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(DB_URL);
config.setMaximumPoolSize(10);
config.setConnectionTimeout(30000);
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
public class UserDAO {
public User findUserByEmail(String email) throws SQLException {
String sql = "SELECT id, name, email, password_hash FROM users WHERE email = ?";
try (Connection conn = DatabaseManager.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, email);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email"),
rs.getString("password_hash")
);
}
return null;
}
}
public boolean createUser(String name, String email, String passwordHash) throws SQLException {
String sql = "INSERT INTO users (name, email, password_hash) VALUES (?, ?, ?)";
try (Connection conn = DatabaseManager.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, name);
stmt.setString(2, email);
stmt.setString(3, passwordHash);
return stmt.executeUpdate() > 0;
}
}
}
public void transferMoney(int fromUserId, int toUserId, double amount) throws SQLException {
Connection conn = DatabaseManager.getConnection();
try {
conn.setAutoCommit(false); // Start transaksjon
// Trekk fra sender
String debitSql = "UPDATE accounts SET balance = balance - ? WHERE user_id = ? AND balance >= ?";
try (PreparedStatement debitStmt = conn.prepareStatement(debitSql)) {
debitStmt.setDouble(1, amount);
debitStmt.setInt(2, fromUserId);
debitStmt.setDouble(3, amount);
if (debitStmt.executeUpdate() == 0) {
throw new SQLException("Insufficient funds");
}
}
// Legg til mottaker
String creditSql = "UPDATE accounts SET balance = balance + ? WHERE user_id = ?";
try (PreparedStatement creditStmt = conn.prepareStatement(creditSql)) {
creditStmt.setDouble(1, amount);
creditStmt.setInt(2, toUserId);
creditStmt.executeUpdate();
}
conn.commit(); // Bekreft transaksjon
} catch (SQLException e) {
conn.rollback(); // Tilbakerull ved feil
throw e;
} finally {
conn.setAutoCommit(true);
conn.close();
}
}
CGI er en standard for hvordan webservere utfører eksterne programmer og returnerer resultatet til klienter. Hver forespørsel starter en ny prosess, som gir isolasjon men kan være ressurskrevende ved høy trafikk.
1. HTTP request → Webserver (Apache/Nginx)
2. Server parser URL og identifiserer CGI-script
3. Server setter miljøvariabler:
- REQUEST_METHOD=GET/POST/PUT/DELETE
- QUERY_STRING=param1=value1¶m2=value2
- CONTENT_TYPE=application/json
- CONTENT_LENGTH=1234
- REMOTE_ADDR=192.168.1.100
- HTTP_USER_AGENT=Mozilla/5.0...
4. Server starter CGI-prosess med exec()
5. CGI-script leser miljø og stdin
6. Script skriver HTTP-headers og body til stdout
7. Server sender output tilbake til klient
8. CGI-prosess termineres
import java.util.Map;
public class HelloCGI {
public static void main(String[] args) {
try {
// Les miljøvariabler
Map<String, String> env = System.getenv();
String method = env.get("REQUEST_METHOD");
String query = env.get("QUERY_STRING");
// Skriv HTTP-headers
System.out.println("Content-Type: application/json");
System.out.println("Cache-Control: no-cache");
System.out.println(); // Blank linje mellom headers og body
// Skriv JSON response
System.out.println("{");
System.out.println(" \"method\": \"" + method + "\",");
System.out.println(" \"query\": \"" + (query != null ? query : "") + "\",");
System.out.println(" \"timestamp\": " + System.currentTimeMillis());
System.out.println("}");
} catch (Exception e) {
// Error handling
System.out.println("Content-Type: application/json");
System.out.println("Status: 500 Internal Server Error");
System.out.println();
System.out.println("{\"error\": \"" + e.getMessage() + "\"}");
}
}
}
# Ressursbaserte URLs
GET /api/v1/users # Hent alle brukere
GET /api/v1/users/123 # Hent bruker med ID 123
POST /api/v1/users # Opprett ny bruker
PUT /api/v1/users/123 # Oppdater bruker 123 (full replace)
PATCH /api/v1/users/123 # Delvis oppdatering av bruker 123
DELETE /api/v1/users/123 # Slett bruker 123
# Filtering og sortering
GET /api/v1/users?role=admin&sort=name&limit=10&offset=20
# Statuskoder
200 OK # Vellykket GET, PUT, PATCH
201 Created # Vellykket POST
204 No Content # Vellykket DELETE
400 Bad Request # Ugyldig input
401 Unauthorized # Ikke autentisert
403 Forbidden # Ikke autorisert
404 Not Found # Ressurs ikke funnet
409 Conflict # Konflikt (duplicate key, etc.)
500 Internal Server Error # Server-feil
# Request headers
Accept: application/json
Accept-Language: no-NO,en;q=0.8
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
# Response med HATEOAS links
{
"id": 123,
"name": "Ola Nordmann",
"email": "ola@example.com",
"_links": {
"self": {"href": "/api/v1/users/123"},
"edit": {"href": "/api/v1/users/123", "method": "PUT"},
"delete": {"href": "/api/v1/users/123", "method": "DELETE"},
"orders": {"href": "/api/v1/users/123/orders"}
}
}
// Arrow functions og destructuring
const users = await fetch('/api/users').then(r => r.json());
const {name, email, role = 'user'} = currentUser;
// Template literals og expressions
const greeting = `Hei ${name}, du har ${unreadCount} uleste meldinger`;
// Async/await for cleaner asynchronous code
async function loadUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Failed to load user:', error);
throw error;
}
}
// Modules og imports
import {validateEmail} from './utils.js';
export class UserService {
static async createUser(userData) {
if (!validateEmail(userData.email)) {
throw new Error('Invalid email address');
}
// ... implementation
}
}
// Query selectors og element manipulation
const userList = document.querySelector('#user-list');
const searchInput = document.querySelector('#search');
// Event delegation for dynamisk innhold
userList.addEventListener('click', (event) => {
if (event.target.classList.contains('delete-btn')) {
const userId = event.target.dataset.userId;
deleteUser(userId);
}
});
// Debounced search input
let searchTimeout;
searchInput.addEventListener('input', (event) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(event.target.value);
}, 300);
});
// Dynamic content creation
function renderUser(user) {
const userElement = document.createElement('div');
userElement.className = 'user-card';
userElement.innerHTML = `
<h3>${escapeHtml(user.name)}</h3>
<p>${escapeHtml(user.email)}</p>
<button class="delete-btn" data-user-id="${user.id}">Slett</button>
`;
return userElement;
}
// XSS protection helper
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
class ApiClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.token = localStorage.getItem('authToken');
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...(this.token && {'Authorization': `Bearer ${this.token}`}),
...options.headers
},
...options
};
const response = await fetch(url, config);
if (response.status === 401) {
this.handleUnauthorized();
throw new Error('Unauthorized');
}
if (!response.ok) {
const error = await response.json().catch(() => ({message: 'Unknown error'}));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
handleUnauthorized() {
localStorage.removeItem('authToken');
window.location.href = '/login';
}
}
// service-worker.js
const CACHE_NAME = 'myapp-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/app.js',
'/offline.html'
];
// Install event - cache resources
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
// Fetch event - serve from cache or network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response; // Return cached version
}
// Fetch from network
return fetch(event.request).catch(() => {
// If network fails, return offline page for navigate requests
if (event.request.destination === 'document') {
return caches.match('/offline.html');
}
});
})
);
});
// Push event - handle notifications
self.addEventListener('push', (event) => {
const options = {
body: event.data.text(),
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
actions: [
{action: 'open', title: 'Åpne app'},
{action: 'close', title: 'Lukk'}
]
};
event.waitUntil(
self.registration.showNotification('Ny melding', options)
);
});
{
"name": "Min Sikre Webapplikasjon",
"short_name": "SikkerApp",
"description": "En sikker og moderne webapplikasjon",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
HTTP er stateless, men webapplikasjoner trenger tilstand. Cookies og sessions løser dette ved å knytte forespørsler til brukeridentitet og session-data.
# Komplett sikker cookie
Set-Cookie: sessionid=abc123;
Secure;
HttpOnly;
SameSite=Strict;
Path=/;
Max-Age=3600;
Domain=.example.com
# Forklaring av attributter:
Secure - Kun sendt over HTTPS
HttpOnly - Ikke tilgjengelig via JavaScript (XSS-beskyttelse)
SameSite - Kontrollerer cross-site requests (CSRF-beskyttelse)
- Strict: Aldri sendt med cross-site requests
- Lax: Sendt med top-level navigation (GET)
- None: Alltid sendt (krever Secure)
Path - Begrenser hvilke URLer som får cookien
Max-Age - Levetid i sekunder (alternativ til Expires)
Domain - Hvilke domener som får cookien
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
// Validate credentials (with proper password hashing)
if (authenticateUser(username, password)) {
HttpSession session = request.getSession(true);
// Regenerate session ID to prevent session fixation
session.invalidate();
session = request.getSession(true);
// Store user info in session
session.setAttribute("userId", getUserId(username));
session.setAttribute("username", username);
session.setAttribute("loginTime", System.currentTimeMillis());
// Set session timeout (30 minutes)
session.setMaxInactiveInterval(30 * 60);
// Secure cookie configuration
Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(request.isSecure());
sessionCookie.setPath("/");
response.addCookie(sessionCookie);
response.sendRedirect("/dashboard");
} else {
// Rate limiting should be implemented here
request.setAttribute("error", "Invalid credentials");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
// JWT-based authentication
public class JWTUtil {
private static final String SECRET = "your-secret-key";
private static final int EXPIRATION = 3600; // 1 hour
public static String generateToken(String userId, List<String> roles) {
return Jwts.builder()
.setSubject(userId)
.claim("roles", roles)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public static Claims validateToken(String token) {
try {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (JwtException e) {
throw new SecurityException("Invalid token: " + e.getMessage());
}
}
}
// Password hashing with bcrypt
public class PasswordUtil {
private static final int COST = 12;
public static String hashPassword(String plainPassword) {
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(COST));
}
public static boolean verifyPassword(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
}
@Component
public class SecurityService {
public boolean hasPermission(String userId, String resource, String action) {
User user = userService.findById(userId);
Set<Role> roles = user.getRoles();
for (Role role : roles) {
for (Permission permission : role.getPermissions()) {
if (permission.getResource().equals(resource) &&
permission.getAction().equals(action)) {
return true;
}
}
}
return false;
}
public void requirePermission(String userId, String resource, String action) {
if (!hasPermission(userId, resource, action)) {
throw new AccessDeniedException(
String.format("User %s lacks permission %s:%s", userId, resource, action)
);
}
}
}
// Usage in controllers
@RestController
public class UserController {
@Autowired
private SecurityService securityService;
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable String id, Authentication auth) {
securityService.requirePermission(auth.getName(), "users", "delete");
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
# Eksempler:
# Webtjener som www-data bruker
# Container med USER 1000:1000
# --cap-drop ALL --cap-add NET_ADMIN
Lag på lag: Nettverk → OS → Container → App → Data
Eksempel web-app:
- Firewall (nettverk)
- SELinux/AppArmor (OS)
- Container isolation (runtime)
- Input validation (app)
- Encrypted storage (data)
# Namespace isolation
unshare --pid --net --mount --fork chroot /new/root /bin/sh
# Container management
podman run -d --name web -p 8080:80 nginx
podman exec -it web /bin/sh
podman build -t myapp .
# Capabilities
docker run --cap-drop ALL --cap-add NET_ADMIN alpine
# User namespaces
podman run --user 1000:1000 alpine id
-- Prepared statement (conceptual)
PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?';
EXECUTE stmt USING @user_id;
-- SQLite foreign keys
PRAGMA foreign_keys=ON;
-- User creation with minimal privileges
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE ON webapp.* TO 'webapp'@'localhost';
# Secure headers
HTTP/1.1 200 OK
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Access-Control-Allow-Origin: https://trusted-domain.com