Un serveur web simple et 100% UTF-8 écrit en Purebasic, tout comme l'Atomic Web Server, mais en plus illisible
⦿Capacités :
- Serveur web : Evidemment... Veillez à déposer vos pages, dossiers, ect, dans un répertoire nommé "www" situé au même endroit que votre exécutable. Toutes les pages sont accessibles à tous les utilisateurs (pas de .htacess pour l'instant).
- Précargement des pages, qui, si l'option est activée, fera que tous les documents situés sous "www" seront chargés au démarrage du serveur. Attention aux gros fichiers qui pourraient remplir votre RAM. Cette option est utile si votre disque est lent ou que certains fichiers doivent être requêtés trés souvent, cela leur évitera d'être rechargés à chaque requête. Désactivé par défaut.
- Protection contre le flood : Chaque client (IP) qui se connecte au serveur est suivi par le serveur qui enregistre le nombre de requêtes et la quantité de données requêtées par ce client. Au dela d'un certain nombre de requêtes ou de données, le client sera considéré comme malveillant et bloqué. Désactivé par défaut et pas trés au point.
- Génération de code, via le Purescript, un début (de début (de début(de début))) de générateur de code HTML via des instructions lues dans le document d'origine. Le principe est le même que les pseudo-variables des serveurs de Falsam. En plaçant les balises <purescript> et </purescript> dans un fichier HTML, et dans ces balises une commande Purescript, le serveur remplacera la commande par son renvoi. Les commandes disponibles sont :
- getClientIP : Renvoie l'IP du client
- getDate : Renvoie la date actuelle
Statut : Hors ligne
⦿Code :
Code : Tout sélectionner
#Server = 42
#Server$ = "PureServer v1.4.2"
#Root$ = "www"
#MaxReq = 420
#MaxData = 50000000
#OK$ = "200 OK"
#OK_EMPLY = "201 NO CONTENT"
#NOT_FOUND$ = "404 NOT FOUND"
#DENIED$ = "403 FORBIDDEN"
#ILLU$ = "420 ILLUMINATI COMPLOT"
#INTERNAL_ERR$ = "500 INTERNAL ERROR"
#NOT_IMPL$ = "501 NOT IMPLEMENTED"
;- Fonctions (procédures) Prototype | Description
Declare HTMLRequestProvider(*inputBuffer) ;Décode les requêtes HTML en vue de les résoudre
Declare GenerateResponseDocument(RootPath$,URL$) ;Génére le document et son header, prêt à envoyer (à partir de RootPath$)
Declare.s GetMIMEType(Path$) ;Renvoie le type MIME du fichier Path$
Declare.s GetDate() ;Renvoie la date du jour (chaine, format GB)
Declare BuildHeader(*out,Code$,Length,ContentType$) ;Crée le header HTTP du document à envoyer
Declare.s ParsePurescript(Document$) ;Parcourt le document à la recherche de Purescript
Declare.s ProcessPurescript(Purescript$) ;Exécute le Purescript et génére le code HTML requis
Declare.s ParseDirectory(folder.s, id.l = 0)
Declare InitServer()
Declare LoadPrefs()
Declare Info(Sender$,Text$)
Declare Error(Sender$,Text$)
Declare Warning(Sender$,Text$)
;-Constantes
Global ClientIP, htmlLen, gFileLen, FileLength
Global gIndexPage.s = "/index.html"
Global gError404$ = "/error/404.html"
Global gRoot$ = "www"
Global gUsePreload, gUseAntiDDOS, gPort=420 ;<<<Changez le port ici ou dans le fichier de configuration !
Structure HTML_REQUEST
Method.s ;Méthode (GET/POST/PUT...)
File.s ;Fichier demandé
ProtocolVersion.s ;Version d'HTTP
EndStructure
Structure REQUEST
Req.HTML_REQUEST ;Requête HTML
Sender.s ;Envoyeur (IP)
BrowserInfos.s ;Infos aditionnelles
EndStructure
Structure DOC ;Document préchargé
*ptr
len.i
EndStructure
Structure CLIENT ;Données d'un client
IP.i
ID.i
NbRequest.i
TotalData.l
EndStructure
Global NewMap Documents.DOC()
NewMap AntiTroll.CLIENT()
OpenConsole("Serveur Web "+#Server$)
ConsoleColor(0,15)
PrintN(" "+#Server$+" ")
ConsoleColor(7,0)
If Not InitNetwork()
Error("Serveur","Erreur fatale : impossible d'initialiser le réseau.")
Input() : End
EndIf
;-Boucle principale
LoadPrefs()
If gUsePreload
InitServer()
EndIf
If CreateNetworkServer(#Server,gPort)
PrintN("Server running on port : "+gPort)
ConsoleTitle("Serveur Web "+#Server$+" , port : "+Str(gPort))
Repeat
Sevent = NetworkServerEvent(#Server)
Select Sevent
Case #PB_NetworkEvent_Connect;Un client est connecté
ClientID = EventClient()
ClientIP = GetClientIP(ClientID)
Case #PB_NetworkEvent_Data ;Un client envoie des données/requêtes
ClientID = EventClient()
ClientIP = GetClientIP(ClientID)
AntiTroll(Str(ClientIP))\NbRequest +1
If (AntiTroll(Str(ClientIP))\NbRequest > #MaxReq Or AntiTroll(Str(ClientIP))\TotalData > #MaxData) And gUseAntiDDOS
Warning("Serveur","Attaque bloquée sur ["+IPString(ClientIP)+"] : Requêtes : "+Str(AntiTroll(Str(ClientIP))\NbRequest)+", données : "+Str(AntiTroll(Str(ClientIP))\TotalData)+".")
Else
;Réception d'une reqête
Info("Serveur",FormatDate("[%hh:%ii:%ss - %dd/%mm/%yyyy]",Date())+" IP : "+IPString(ClientIP)+" - Client has sent a packet.")
*Buffer = AllocateMemory(1024);Buffer pour stocker la requête
If Not *Buffer : Error("Serveur","Impossible d'allouer de la mémoire. Fermez certains processus.") : EndIf;Dsl, pas assez de mana ^^
ReceiveNetworkData(ClientID,*Buffer,1024) ;On récupére la requête reçue
*ptr_Result = HTMLRequestProvider(*Buffer) ;On récupére la page associée à l'URL aprés résolution de la requête
If *ptr_Result ;*ptr_result pointe sur la page en mémoire / les données (images, audio, ect...)
effective = SendNetworkData(ClientID,*ptr_Result,gFileLen) ;On envoie la page
Debug "Envoyé : "+gFileLen+"/"+effective
If effective = gFileLen
Info("Serveur","Document envoyé correctement !")
Else
Warning("Serveur","Document envoyé mais possible corruption des données")
EndIf
AntiTroll(Str(ClientIP))\TotalData +effective ;On met à jour les statistiques du client
Else
Error("Serveur","404 file Not found.") ;Cool un 404
*ptr_Result = GenerateResponseDocument(gRoot$,gError404$)
SendNetworkData(ClientID,*ptr_Result,gFileLen)
EndIf
EndIf
CloseNetworkConnection(ClientID)
EndSelect
Delay(10)
ForEver
Else
Error("Serveur local","Erreur ! Impossible de créer le serveur Web sur le port "+gPort+".")
Input()
EndIf
;-Procédures
Procedure HTMLRequestProvider(*inputBuffer)
Protected Request.REQUEST, *ptrPage
;Dissection des informations de la requête, possibilité de s'en servir pour personnaliser/pour logger
Request$ = PeekS(*inputBuffer,1024,#PB_UTF8)
HTMLRequest$ = StringField(Request$,1,#LF$)
Request\Sender = StringField(Request$,2,#LF$)
Request\BrowserInfos = StringField(Request$,3,#LF$)
Request\Req\Method = StringField(HTMLRequest$,1," ")
Request\Req\File = StringField(HTMLRequest$,2," ")
Request\Req\ProtocolVersion = StringField(HTMLRequest$,3," ")
Info("Serveur","Détails de requête :")
ConsoleColor(14,0)
Print(" Requête : ")
ConsoleColor(10,0)
PrintN(HTMLRequest$)
ConsoleColor(14,0)
Print(" Depuis : ")
ConsoleColor(7,0)
PrintN(Request\Sender)
ConsoleColor(14,0)
Print(" Informations : ")
ConsoleColor(7,0)
PrintN(Request\BrowserInfos)
Debug "["+request\Req\File+"]"
If Request\Req\File = "/" Or GetFilePart(Request\Req\File) = "" ;Pas de page spécifiée? On affiche "index"
*ptrPage = GenerateResponseDocument(gRoot$,RTrim(GetPathPart(Request\Req\File),"/")+gIndexPage)
Debug "index.html have been automatically sent."
Else
*ptrPage = GenerateResponseDocument(gRoot$,Request\Req\File) ;Sinon on charge la page demandée
EndIf
ProcedureReturn *ptrPage
EndProcedure
Procedure.s GetDate() ;Récupérer la date du jour (EN)
Hour$ = FormatDate("%hh:%ii:%ss",Date())
Year$ = Str(Year(Date()))
DayOfMonth$ = Str(Day(Date()))
DayOfWeek$ = StringField("Sun,Mon,Tue,Wed,Thu,Fri,Sat", DayOfWeek(Date()) + 1, ",")
Month$ = StringField("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", Month(Date()), ",")
ProcedureReturn DayOfWeek$ + ", " + DayOfMonth$ + " " + Month$ + " " + Year$ + " " + Hour$
EndProcedure
Procedure BuildHeader(*out, Code$, Length, ContentType$) ;Construit le header HTTP en mémoire
Protected ptr, *init = *out
ptr = PokeS(*out, "HTTP/1.1 " + Code$ + #CRLF$, -1, #PB_UTF8) : *out + ptr
ptr = PokeS(*out, "Date: " + GetDate() + #CRLF$, -1, #PB_UTF8) : *out + ptr
ptr = PokeS(*out, "Server: "+ #Server$ + #CRLF$, -1, #PB_UTF8) : *out + ptr
ptr = PokeS(*out, "Content-Length: " + Str(Length) + #CRLF$, -1, #PB_UTF8) : *out + ptr
ptr = PokeS(*out, "Content-Type: " + ContentType$ + #CRLF$, -1, #PB_UTF8) : *out + ptr
ptr = PokeS(*out, #CRLF$, -1, #PB_UTF8) : *out + ptr
ProcedureReturn *out - *init
EndProcedure
Procedure.s GetMIMEType(Path$)
Protected ContentType$
Protected ext$ = "."+GetExtensionPart(Path$)
Select ext$ ;Déterminer le type MIME à inclure dans le header
Case ".html"
ContentType$ = "text/html"
Case ".gif"
ContentType$ = "image/gif"
Case ".jpg"
ContentType$ = "image/jpeg"
Case ".png"
ContentType$ = "image/png"
Case ".txt"
ContentType$ = "text/plain"
Case ".zip"
ContentType$ = "application/zip"
Case ".css"
ContentType$ = "text/css"
Case ".js"
ContentType$ = "application/javascript"
Case ".xml"
ContentType$ = "application/xml"
Case ".pdf"
ContentType$ = "application/pdf"
Case ".exe"
ContentType$ = "application/octet-stream"
Case ".ogg"
ContentType$ = "application/ogg"
Case ".mp3"
ContentType$ = "audio/mpeg" ;TODO : Compatibilté Chrome : audio/mp3
Default
ContentType$ = "application/octet-stream"
EndSelect
ProcedureReturn ContentType$
EndProcedure
Procedure GenerateResponseDocument(RootPath$, URL$) ;Génére un buffer contenant les données à envoyer (pages, médias...)
Protected File, HeaderLength
Static *Document, *File
Protected ContentType$
Protected FileLength
If Not FindMapElement(Documents(),RootPath$+URL$) ;Le fichier n'est pas préchargé ou le préchargement est désactivé (par défaut)
If gUsePreload : Warning("HTML provider","Cant find "+RootPath$+URL$+" in the pre-parsed files. It will be processed now.") : EndIf
File = ReadFile(#PB_Any,RootPath$+URL$,#PB_File_SharedRead | #PB_UTF8);On ouvre le fichier demandé en lecture
ContentType$ = GetMIMEType(URL$) ;On détermine le type MIME associé
If File
FileLength = Lof(File)
*FileBuffer = AllocateMemory(FileLength+1) ;Allocation de la mémoire pour le fichier
mFileLength = ReadData(File, *FileBuffer, FileLength)
If ContentType$ = "text/html" ;On recherche du Purescript (code à générer), a remplacer par CGI
Document$ = PeekS(*FileBuffer, -1, #PB_UTF8)
Document$ = ParsePurescript(Document$)
mByteLength = StringByteLength(Document$,#PB_UTF8)
*FileBuffer = ReAllocateMemory(*FileBuffer,mByteLength+1) ;On prend en compte l'\0 et on réalloue pour le fichier modifié
mFileLength = PokeS(*FileBuffer,Document$,-1, #PB_UTF8) ;On met le fichier en mémoire
EndIf
*Document = AllocateMemory(200)
HeaderLength = BuildHeader(*Document,#OK$,mFileLength,ContentType$)
*Document = ReAllocateMemory(*Document,mFileLength + HeaderLength)
CopyMemory(*FileBuffer, *Document+HeaderLength,mFileLength)
FreeMemory(*FileBuffer)
CloseFile(File)
gFileLen = mFileLength+ HeaderLength ;gFileLen (globale) contient la longueur des données en mémoire
ProcedureReturn *Document
Else
*Document = AllocateMemory(200)
gFileLen = BuildHeader(*Document,#NOT_FOUND$,0,"error")
Error("HTML provider","Error 404 : Document not found at "+URL$+" !")
Debug "[404] : "+URL$
*Document = GenerateResponseDocument(gRoot$,gError404$)
; CloseFile(File)
ProcedureReturn *Document
EndIf
Else
gFileLen = Documents(RootPath$+URL$)\len
ProcedureReturn Documents(RootPath$+URL$)\ptr ;Il suffit de renvoyer l'adresse du fichier préchargé en mémoire
EndIf
EndProcedure
Procedure.s ParsePurescript(Document$) ;Vérifie la présence de Purescript dans la page. Pas opti du tout !
While FindString(Document$,"<purescript>",index)
pos = FindString(Document$, "<purescript>",index) +12
endpos = FindString(Document$,"</purescript>",pos)
ps$ = Mid(Document$,pos,endpos-pos)
Info("PureScript","Resolved ["+ps$+"="+ProcessPurescript(ps$)+"]")
Document$ = ReplaceString(Document$,"<purescript>","",#PB_String_CaseSensitive,pos-12,1) ;On vire les balises purescript
Document$ = ReplaceString(Document$,"</purescript>","",#PB_String_CaseSensitive,pos-1,1)
Document$ = ReplaceString(Document$,ps$,ProcessPurescript(ps$),#PB_String_CaseSensitive,pos-14,1) ;On remplace le purescript par le code HTML généré
index= endpos
Wend
ProcedureReturn Document$
EndProcedure
Procedure.s ProcessPurescript(Purescript$) ;Génére l'HTML demandé par le purescript
Select Purescript$
Case "getClientIP" ;Donne l'ip du client
ProcedureReturn IPString(ClientIP)
Case "date"
ProcedureReturn GetDate()
Case "getDate" ;Devrait remplacer "date"
ProcedureReturn GetDate()
EndSelect
EndProcedure
Procedure InitServer()
EnableGraphicalConsole(1)
ConsoleColor(7,0)
PrintN("Initializating server...")
ParseDirectory(gRoot$+"/")
ConsoleLocate(0,5)
EnableGraphicalConsole(0)
EndProcedure
Procedure.s ParseDirectory(folder.s, id.l = 0)
Static countFiles
Protected Current$ = folder
If ExamineDirectory(id, folder, "*.*")
While NextDirectoryEntry(id)
If DirectoryEntryName(id) <> "." And DirectoryEntryName(id) <> ".."
If DirectoryEntryType(id) = #PB_DirectoryEntry_Directory
Current$ = Current$ + DirectoryEntryName(id) + "/"
ParseDirectory(Current$, id + 1)
Else
ConsoleLocate(0,4)
PrintN("File : "+DirectoryEntryName(id)+Space(200))
Documents(folder+DirectoryEntryName(id))\ptr = GenerateResponseDocument("",folder+DirectoryEntryName(id))
Documents(folder+DirectoryEntryName(id))\len = gFileLen
ConsoleColor(7,0)
countFiles+1
ConsoleLocate(0,3)
Print("Finding files : "+Str(countFiles))
EndIf
EndIf
Wend
FinishDirectory(id)
EndIf
EndProcedure
Procedure LoadPrefs()
If Not OpenPreferences("settings.ini")
CreatePreferences("settings.ini",#PB_Preference_GroupSeparator)
PreferenceComment("########################### Settings for the PureWeb Server ###################################")
PreferenceComment(" ")
PreferenceComment("This file is used to store the PureWeb's options")
PreferenceGroup("Web Settings")
WritePreferenceString("root","www")
WritePreferenceString("index","/index.html")
WritePreferenceString("404","/error/404.html")
WritePreferenceString("403","/error/403.html")
WritePreferenceString("500","/error/500.html")
WritePreferenceString("501","/error/501.html")
PreferenceGroup("HTML Provider")
WritePreferenceInteger("Use Preloading",0)
WritePreferenceInteger("Use AntiDDOS",1)
WritePreferenceInteger("Max Requests",420)
WritePreferenceLong("Max Data",42000000)
PreferenceGroup("IP Settings")
WritePreferenceString("Blocked IPs list","0.0.0.0")
WritePreferenceString("Unblocked IPs list","127.0.0.1|192.168.1.10|localhost")
PreferenceGroup("Server Settings")
PreferenceComment("General settings about this server")
WritePreferenceInteger("Use port",1946)
WritePreferenceString("Server name","PureWeb")
WritePreferenceInteger("Show requests",1)
WritePreferenceInteger("Show warnings",1)
WritePreferenceInteger("Show connections",1)
Warning("Serveur","Fichier de préférences automatiquement créé")
Else
;OpenPreferences("settings.ini")
gRoot$ = ReadPreferenceString("root","www")
gError404$ = ReadPreferenceString("404","/error/404.html")
gPort = ReadPreferenceInteger("Use port",80)
gUsePreload = ReadPreferenceInteger("Use Preloading",0)
gUseAntiDDOS = ReadPreferenceInteger("Use AntiDDOS",0)
gShowReq = ReadPreferenceInteger("Show requests",1)
gShowWarn = ReadPreferenceInteger("Show warnings",1)
gShowConnections = ReadPreferenceInteger("Show connections",1)
EndIf
If CountProgramParameters() ;Si on passe un numéro en paramétre, le serveur démarre sur ce port.
gPort = Val(ProgramParameter())
If Not gPort
gPort = 8080
EndIf
EndIf
ClosePreferences()
EndProcedure
Procedure Info(Sender$,Text$)
ConsoleColor(9,0)
Print("["+Sender$+"] ")
ConsoleColor(7,0)
PrintN(Text$)
EndProcedure
Procedure Error(Sender$,Text$)
ConsoleColor(9,0)
Print("["+Sender$+"] ")
ConsoleColor(12,0)
PrintN(Text$)
ConsoleColor(7,0)
EndProcedure
Procedure Warning(Sender$,Text$)
ConsoleColor(9,0)
Print("["+Sender$+"] ")
ConsoleColor(14,0)
PrintN(Text$)
EndProcedure