User Tools

Site Tools


Tiny multi-thread web server with TLS

It is blocked mode sample but work.

thrsrv.c
/*
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 */
 
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
 
#include <openssl/ssl.h>
#include <openssl/err.h>
 
typedef struct queue {
    int *items;
    int front;
    int rear;
    int size;
    int maxsize;
    pthread_mutex_t mutex;
} queue_t;
 
queue_t *queue_create(int size) {
    queue_t *q = NULL;
    q = (queue_t *)malloc(sizeof(queue_t));
    q->items = (int *)malloc(size * sizeof(int));
    q->maxsize = size;
    q->front = 0;
    q->rear = -1;
    q->size = 0;
    return q;
}
 
int queue_size(queue_t *q) {
    pthread_mutex_lock(&q->mutex);
    int res = q->size;
    pthread_mutex_unlock(&q->mutex);
    return res;
}
 
int queue_front(queue_t *q) {
    int res = -1;
    pthread_mutex_lock(&q->mutex);
    if (q->size > 0) {
        res = q->items[q->front];
        q->front = (q->front + 1) % q->maxsize;
        q->size--;
    }
    pthread_mutex_unlock(&q->mutex);
    return res;
}
 
bool queue_push(queue_t* q, int x) {
    bool res = false;
    pthread_mutex_lock(&q->mutex);
    if (q->size < q->maxsize) {
        q->rear = (q->rear + 1) % q->maxsize;
        q->items[q->rear] = x;
        q->size++;
        res = true;
    }
    pthread_mutex_unlock(&q->mutex);
    return res;
}
 
typedef struct thrpool {
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    queue_t* queue;
    const int poolsize;
    pthread_t* threads;
    SSL_CTX *context;
} thrpool_t;
 
thrpool_t* thrpool_create(const int pool_size, const int queue_size, void* handler) {
    thrpool_t* tp = NULL;
    tp = (thrpool_t*)malloc(sizeof(thrpool_t));
    tp->queue = queue_create(queue_size);
 
    tp->threads = (pthread_t*)malloc(sizeof(pthread_t) * pool_size);
 
    for (int i = 0; i < pool_size; i++) {
        pthread_create(&(tp->threads[i]), NULL, handler, (void*)tp);
    }
    for (int i = 0; i < pool_size; i++) {
        pthread_detach(tp->threads[i]);
        fprintf(stderr, "create thread %d\n", (int)tp->threads[i]);
    }
    return tp;
}
 
inline bool thrpool_enqueue(thrpool_t* tp, int newsock) {
    if (queue_push(tp->queue, newsock)) {
        pthread_mutex_unlock(&(tp->mutex));
        pthread_cond_signal(&(tp->cond));
        return true;
    }
    return false;
}
 
inline void thrpool_lock(thrpool_t* tp) {
    pthread_mutex_lock(&(tp->mutex));
}
 
inline void thrpool_unlock(thrpool_t* tp) {
    pthread_mutex_unlock(&(tp->mutex));
}
 
inline void thrpool_wait(thrpool_t* tp) {
    pthread_cond_wait(&(tp->cond), &(tp->mutex));
}
 
void* handler(void* argp) {
    thrpool_t* tp = (thrpool_t*)argp;
    while(true) {
        thrpool_lock(tp);
        while(queue_size(tp->queue) == 0) {
            thrpool_wait(tp);
        }
        int sock = queue_front(tp->queue);
 
        SSL *ssl_sock = SSL_new(tp->context);
        SSL_set_fd(ssl_sock, sock);
        if (SSL_accept(ssl_sock) > 0) {
            char ibuffer[1024];
            SSL_read(ssl_sock, ibuffer, 1024);
            //printf("worker %d handle socket %d\n", (int)pthread_self(), sock);
            char res[] =
                "HTTP/1.1 200 OK\r\n"
                "Accept-Ranges: bytes\r\n"
                "Content-Type: text/plain\r\n"
                "Content-Length: 0\r\n"
                "Conection: close\r\n"
                "Server: Srv11/0.1\r\n"
                "Date: Wed, 15 May 2019 20:05:07 UTC\r\n" "\r\n";
            SSL_write(ssl_sock, res, strlen(res));
        }
        SSL_free(ssl_sock);
        shutdown(sock, SHUT_RDWR);
        close(sock);
        thrpool_unlock(tp);
    }
    return NULL;
}
 
int main(int argc, char **argv) {
 
    const int poolsize = 10;
    const int queuesize = 1024 * 10;
    thrpool_t* tp = thrpool_create(poolsize, queuesize, handler);
 
    int sock;
    if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        fprintf(stderr, "cannot create socket, exit\n");
        exit(1);
    }
    int optval = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
        fprintf(stderr, "cannot set socket option, exit\n");
        exit(1);
    }
    struct sockaddr addr;
    const int port = 1024;
    struct sockaddr_in* paddr = (struct sockaddr_in*)&addr;
    paddr->sin_family = AF_INET;
    paddr->sin_addr.s_addr = INADDR_ANY;
    paddr->sin_port = htons(port);
    paddr->sin_len = sizeof(struct sockaddr_in);
 
    if (bind(sock, (struct sockaddr*)paddr, paddr->sin_len) < 0) {
        fprintf(stderr, "cannot bind socket, exit\n");
        exit(1);
    }
    if (listen(sock, 2048) < 0) {
        fprintf(stderr, "cannot listen socket, exit\n");
        exit(1);
    }
 
    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();
 
    //const SSL_METHOD *method = SSLv23_server_method();
    const SSL_METHOD *method = TLSv1_2_server_method();
    tp->context = SSL_CTX_new(method);
 
    if (!tp->context) {
        fprintf(stderr, "unable to create SSL context\n");
        exit(1);
    }
    SSL_CTX_set_ecdh_auto(tp->context, 1);
    if (SSL_CTX_use_certificate_file(tp->context, "server.crt", SSL_FILETYPE_PEM) <= 0) {
        fprintf(stderr, "unable to read certificate file\n");
        exit(1);
    }
    if (SSL_CTX_use_PrivateKey_file(tp->context, "server.key", SSL_FILETYPE_PEM) <= 0 ) {
        fprintf(stderr, "unable to read key file\n");
        exit(1);
    }
 
    int newsock;
    thrpool_lock(tp);
    while ((newsock = accept(sock, NULL, 0)) > 0) {
        if (!thrpool_enqueue(tp, newsock)) {
            shutdown(sock, SHUT_RDWR);
            close(sock);
        }
    }
    return 0;
}
$ ab -c 1000 -n10000 'https://localhost:1024/'
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests


Server Software:        Srv11/0.1
Server Hostname:        localhost
Server Port:            1024
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256
TLS Server Name:        localhost

Document Path:          /
Document Length:        0 bytes

Concurrency Level:      1000
Time taken for tests:   19.100 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1600000 bytes
HTML transferred:       0 bytes
Requests per second:    523.56 [#/sec] (mean)
Time per request:       1909.999 [ms] (mean)
Time per request:       1.910 [ms] (mean, across all concurrent requests)
Transfer rate:          81.81 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        8 1824 568.1   1635    4184
Processing:     0    0   0.7      0      70
Waiting:        0    0   0.0      0       0
Total:         78 1824 568.1   1635    4184

Percentage of the requests served within a certain time (ms)
  50%   1635
  66%   1698
  75%   1702
  80%   1703
  90%   2487
  95%   3394
  98%   3868
  99%   4027
 100%   4184 (longest request)