Skip to content

Instantly share code, notes, and snippets.

@hemikak
Last active February 24, 2020 03:19
Show Gist options
  • Save hemikak/8f9fcde07bcb23b2d15a3e1a240fd3f5 to your computer and use it in GitHub Desktop.
Save hemikak/8f9fcde07bcb23b2d15a3e1a240fd3f5 to your computer and use it in GitHub Desktop.
A simple web server written in Ballerina
// Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
//
// WSO2 Inc. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import ballerina/file;
import ballerina/filepath;
import ballerina/http;
import ballerina/log;
import ballerina/mime;
//// Configuration related properties.
boolean isSinglePageApplication = true;
// file extension mapping to content types
final map<string> MIME_MAP = {
"json": mime:APPLICATION_JSON,
"xml": mime:TEXT_XML,
balo: mime:APPLICATION_OCTET_STREAM,
css: "text/css",
gif: "image/gif",
html: mime:TEXT_HTML,
ico: "image/x-icon",
jpeg: "image/jpeg",
jpg: "image/jpeg",
js: "application/javascript",
png: "image/png",
svg: "image/svg+xml",
txt: mime:TEXT_PLAIN,
woff2: "font/woff2",
zip: "application/zip"
};
@http:ServiceConfig {
basePath: "/",
chunking: http:CHUNKING_NEVER
}
service webServerSvc on new http:Listener(9090) {
@http:ResourceConfig {
methods: ["GET"],
path: "/*"
}
resource function serveHtmlFiles(http:Caller caller, http:Request request) {
http:Response res = new;
string requestedFilePath = <@untainted> <string> request.extraPathInfo;
requestedFilePath = checkpanic filepath:build("app", requestedFilePath);
// If requested for a file(not directory), check if its available in the server and response.
if (file:exists(requestedFilePath) && isFile(requestedFilePath)) {
res = getFileAsResponse(requestedFilePath);
error? clientResponse = caller->respond(res);
if (clientResponse is error) {
log:printError("unable respond back", err = clientResponse);
}
return;
}
// If requested is a directory, check and append forward slash.
if (!requestedFilePath.endsWith("/")) {
if (requestedFilePath.length() > 0) {
requestedFilePath = requestedFilePath + "/";
}
}
// Resolve to index.html of the requested directory
requestedFilePath = requestedFilePath + "index.html"; // resolve to index.html if not file it found.
if (isSinglePageApplication && !file:exists(requestedFilePath)) {
res = getFileAsResponse(checkpanic filepath:build("app", "index.html"));
} else {
res = getFileAsResponse(requestedFilePath);
}
error? clientResponse = caller->respond(res);
if (clientResponse is error) {
log:printError("unable respond back", err = clientResponse);
}
}
@http:ResourceConfig {
methods: ["GET"],
path: "/static/*"
}
resource function serveStaticFiles(http:Caller caller, http:Request request) {
// TODO: properly untaint the request.rawPath
string|error requestedFilePath = filepath:build("app", <@untainted> <string> request.rawPath);
http:Response res = new;
if (requestedFilePath is string) {
res = getFileAsResponse(requestedFilePath);
} else {
res.setTextPayload("server error occurred.");
res.statusCode = 500;
log:printError("unable to resolve file path", err = requestedFilePath);
}
error? clientResponse = caller->respond(res);
if (clientResponse is error) {
log:printError("unable respond back", err = clientResponse);
}
}
}
# Serve a file as a http response.
#
# + requestedFile - The path of the file to server.
# + return - The http response.
function getFileAsResponse(string requestedFile) returns http:Response {
http:Response res = new;
// Figure out content-type
string contentType = mime:APPLICATION_OCTET_STREAM;
string fileExtension = checkpanic filepath:extension(requestedFile);
if (fileExtension != "") {
contentType = getMimeTypeByExtension(fileExtension);
}
// Check if file exists.
if (file:exists(requestedFile)) {
res.setFileAsPayload(requestedFile, contentType = contentType);
} else {
log:printError("unable to find file: " + requestedFile);
res.setTextPayload("the server was not able to find what you were looking for.");
res.statusCode = http:STATUS_NOT_FOUND;
}
return res;
}
# Get the content type using a file extension.
#
# + extension - The file extension.
# + return - The content type if a match is found, else application/octet-stream.
function getMimeTypeByExtension(string extension) returns string {
var contentType = MIME_MAP[extension.toLowerAscii()];
if (contentType is string) {
return contentType;
} else {
return mime:APPLICATION_OCTET_STREAM;
}
}
# Check if a file path is a file and not a directory.
#
# + filePath - Path to the file
# + return - True if file, else false
function isFile(string filePath) returns boolean {
return !(file:readDir(filePath) is file:FileInfo[]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment