Overview
Comment: | Implement serving static files |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
59bddfdbef59e39db8672183a042d8d9 |
User & Date: | js 2019-02-24 23:26:57 |
Context
2019-03-03
| ||
12:50 | Change license from AGPL3 to GPL3 check-in: b0f2d0f8b8 user: js tags: trunk | |
2019-02-24
| ||
23:26 | Implement serving static files check-in: 59bddfdbef user: js tags: trunk | |
2019-01-27
| ||
21:05 | Initial commit check-in: 68fae0e18a user: js tags: trunk | |
Changes
Changes to Module.h.
︙ | ︙ | |||
16 17 18 19 20 21 22 | * along with this program. If not, see <https://www.gnu.org/licenses/>. */ #import <ObjFW/ObjFW.h> @protocol Module - (void)parseConfig: (OFXMLElement *)config; | < | | | | 16 17 18 19 20 21 22 23 24 25 26 | * along with this program. If not, see <https://www.gnu.org/licenses/>. */ #import <ObjFW/ObjFW.h> @protocol Module - (void)parseConfig: (OFXMLElement *)config; - (bool)handleRequest: (OFHTTPRequest *)request requestBody: (OFStream *)requestBody response: (OFHTTPResponse *)response; @end |
Changes to ObjWebServer.m.
︙ | ︙ | |||
105 106 107 108 109 110 111 | { OFString *path = [[request URL] path]; of_log(@"Request: %@", request); for (OFPair OF_GENERIC(OFString *, id <Module>) *module in _modules) if ([path hasPrefix: [module firstObject]]) | | < | | > | 105 106 107 108 109 110 111 112 113 114 115 116 117 | { OFString *path = [[request URL] path]; of_log(@"Request: %@", request); for (OFPair OF_GENERIC(OFString *, id <Module>) *module in _modules) if ([path hasPrefix: [module firstObject]]) if ([[module secondObject] handleRequest: request requestBody: requestBody response: response]) return; } @end |
Changes to ObjWebServer.xml.
1 | <ObjWebServer> | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <ObjWebServer> <listen host='0.0.0.0' port='1234'> <tls cert='test.crt' key='test.key'/> </listen> <listen host='::' port='1234'> <tls cert='test.crt' key='test.key'/> </listen> <module prefix='/' path='modules/static'> <root>htdocs</root> <mime-type extension='' type='application/octet-stream'/> <mime-type extension='c' type='text/plain; encoding-UTF-8'/> <mime-type extension='cc' type='text/plain; encoding=UTF-8'/> <mime-type extension='css' type='text/css; encoding=UTF-8'/> <mime-type extension='gz' type='application/gzip'/> <mime-type extension='h' type='text/plain; encoding=UTF-8'/> <mime-type extension='htm' type='text/html; encoding=UTF-8'/> <mime-type extension='html' type='text/html; encoding=UTF-8'/> <mime-type extension='jpeg' type='image/jpeg'/> <mime-type extension='jpg' type='image/jpeg'/> <mime-type extension='js' type='text/javascript; encoding=UTF-8'/> <mime-type extension='json' type='application/json'/> <mime-type extension='m' type='text/plain; encoding=UTF-8'/> <mime-type extension='mm' type='text/plain; encoding=UTF-8'/> <mime-type extension='mp3' type='audio/mpeg'/> <mime-type extension='mp4' type='video/mp4'/> <mime-type extension='ogg' type='application/ogg'/> <mime-type extension='opus' type='audio/opus'/> <mime-type extension='png' type='image/png'/> <mime-type extension='svg' type='image/svg+xml'/> <mime-type extension='txt' type='text/plain; encoding=UTF-8'/> <mime-type extension='webm' type='video/webm'/> <mime-type extension='zip' type='application/zip'/> </module> </ObjWebServer> |
Changes to StaticModule.m.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ #import <ObjFW/ObjFW.h> #import "Module.h" @interface StaticModule: OFPlugin <Module> { OFString *_root; } @end @implementation StaticModule - (void)dealloc { [_root release]; [super dealloc]; } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | > > > > | > > > > > > | > > > > > > > > > > > > > > > > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ #import <ObjFW/ObjFW.h> #import "Module.h" #define BUFFER_SIZE 4096 @interface StaticModule: OFPlugin <Module> { OFString *_root; OFDictionary OF_GENERIC(OFString *, OFString *) *_MIMETypes; } @end @interface StaticModule_FileSender: OFObject <OFStreamDelegate> { @public OFFile *_file; OFHTTPResponse *_response; } @end static OFData * readData(OFStream *stream) { void *buffer; OFData *ret; if ((buffer = malloc(BUFFER_SIZE)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: BUFFER_SIZE]; @try { size_t length = [stream readIntoBuffer: buffer length: BUFFER_SIZE]; ret = [OFData dataWithItemsNoCopy: buffer count: length freeWhenDone: true]; } @catch (id e) { free(buffer); @throw e; } return ret; } @implementation StaticModule_FileSender - (void)dealloc { [_file release]; [_response release]; [super dealloc]; } - (OFData *)stream: (OF_KINDOF(OFStream *))stream didWriteData: (OFData *)data bytesWritten: (size_t)bytesWritten exception: (id)exception { if (exception != nil || [_file isAtEndOfStream]) return nil; return readData(_file); } @end @implementation StaticModule - (void)dealloc { [_root release]; [super dealloc]; } - (void)parseConfig: (OFXMLElement *)config { OFMutableDictionary OF_GENERIC(OFString *, OFString *) *MIMETypes; _root = [[[config elementForName: @"root"] stringValue] copy]; if (_root == nil) { [of_stderr writeString: @"Error parsing config: No <root/> element!"]; [OFApplication terminateWithStatus: 1]; } MIMETypes = [OFMutableDictionary dictionary]; for (OFXMLElement *MIMEType in [config elementsForName: @"mime-type"]) { OFString *extension = [[MIMEType attributeForName: @"extension"] stringValue]; OFString *type = [[MIMEType attributeForName: @"type"] stringValue]; if (extension == nil) { [of_stderr writeString: @"Error parsing config: " @"<mime-type/> has no extension attribute!"]; [OFApplication terminateWithStatus: 1]; } if (type == nil) { [of_stderr writeString: @"Error parsing config: " @"<mime-type/> has no type attribute!"]; [OFApplication terminateWithStatus: 1]; } [MIMETypes setObject: type forKey: extension]; } [MIMETypes makeImmutable]; _MIMETypes = [MIMETypes mutableCopy]; } - (bool)handleRequest: (OFHTTPRequest *)request requestBody: (OFStream *)requestBody response: (OFHTTPResponse *)response { OFURL *URL = [[request URL] URLByStandardizingPath]; OFString *path = [URL path]; OFMutableDictionary *headers = [OFMutableDictionary dictionary]; OFFileManager *fileManager; bool firstComponent = true; OFString *MIMEType; StaticModule_FileSender *fileSender; for (OFString *component in [URL pathComponents]) { if (firstComponent && [component length] != 0) return false; if ([component isEqual: @"."] || [component isEqual: @".."]) return false; firstComponent = false; } /* TODO: Properly handle for OSes that do not use UNIX paths */ if (![path hasPrefix: @"/"]) return false; path = [_root stringByAppendingString: path]; fileManager = [OFFileManager defaultManager]; if ([fileManager directoryExistsAtPath: path]) path = [path stringByAppendingPathComponent: @"index.html"]; if (![fileManager fileExistsAtPath: path]) { [response setStatusCode: 404]; return false; } MIMEType = [_MIMETypes objectForKey: [path pathExtension]]; if (MIMEType == nil) MIMEType = [_MIMETypes objectForKey: @""]; if (MIMEType != nil) [headers setObject: MIMEType forKey: @"Content-Type"]; fileSender = [[[StaticModule_FileSender alloc] init] autorelease]; fileSender->_file = [[OFFile alloc] initWithPath: path mode: @"r"]; fileSender->_response = [response retain]; [response setStatusCode: 200]; [response setHeaders: headers]; [response setDelegate: fileSender]; [response asyncWriteData: readData(fileSender->_file)]; return true; } @end StaticModule * init_plugin(void) { return [[[StaticModule alloc] init] autorelease]; |
︙ | ︙ |