├── .gitignore ├── .travis.yml ├── LICENCE ├── Makefile ├── README.md ├── WINDOWS-INSTALL.md ├── binding.gyp ├── doc ├── Doxyfile └── README.md ├── docker └── latest │ └── Dockerfile ├── examples ├── hello-world.js ├── wms-server.js └── wms-server.map ├── lib └── mapserv.js ├── package.json ├── src ├── error.cpp ├── error.hpp ├── map.cpp ├── map.hpp ├── node-mapserv.cpp ├── node-mapservutil.c └── node-mapservutil.h ├── test ├── include-error.map ├── invalid.map ├── mapserv-test.js └── valid.map └── tools ├── config.py ├── git-bisect-run.sh └── install-deps.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | coverage/ 3 | node_modules/ 4 | doc/.made 5 | doc/html 6 | doc/latex 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for Travis-CI testing 2 | # Test using 3 | 4 | language: node_js 5 | node_js: 6 | - "0.10" 7 | - "0.12" 8 | - "0.13" 9 | env: 10 | - MAPSERVER_COMMIT=rel-6-2-0 # v6.2.0 11 | - MAPSERVER_COMMIT=rel-6-2-1 # v6.2.1 12 | - MAPSERVER_COMMIT=rel-6-4-0 # v6.4.0 13 | - MAPSERVER_COMMIT=rel-6-4-1 # v6.4.1 14 | - MAPSERVER_COMMIT= # repository HEAD 15 | matrix: 16 | fast_finish: true 17 | # allow unstable versions to fail 18 | allow_failures: 19 | - node_js: "0.12" 20 | - node_js: "0.13" 21 | - env: MAPSERVER_COMMIT= 22 | before_install: 23 | - sudo apt-get install libgif-dev # mapserver dependencies 24 | - sh ./tools/install-deps.sh /tmp $MAPSERVER_COMMIT # install the dependencies 25 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | # Node-mapserv development targets 3 | # 4 | # This Makefile contains the following primary targets intended to facilitate 5 | # Linux based development: 6 | # 7 | # - `make build`: create a Debug build of the module 8 | # - `make test`: run the tests 9 | # - `make cover`: perform the code coverage analysis 10 | # - `make valgrind`: run the test suite under valgrind 11 | # - `make doc`: create the doxygen documentation 12 | # - `make clean`: remove generated files 13 | # 14 | 15 | # The location of the mapserver build directory 16 | ifeq ($(strip $(npm_config_mapserv_build_dir)),) 17 | npm_config_mapserv_build_dir = $(shell npm config get mapserv:build_dir) 18 | endif 19 | 20 | # The location of the `vows` test command 21 | VOWS = ./node_modules/.bin/vows 22 | 23 | # The location of the `node-gyp` module builder. Try and get a globally 24 | # installed version, falling back to a local install. 25 | NODE_GYP = $(shell which node-gyp) 26 | ifeq ($(NODE_GYP),) 27 | NODE_GYP = ./node_modules/.bin/node-gyp 28 | endif 29 | 30 | # The location of the `istanbul` JS code coverage framework. Try and get a 31 | # globally installed version, falling back to a local install. 32 | ISTANBUL=$(shell which istanbul) 33 | ifeq ($(ISTANBUL),) 34 | ISTANBUL = ./node_modules/.bin/istanbul 35 | endif 36 | 37 | # Dependencies for the test target 38 | test_deps=build \ 39 | ./test/*.js \ 40 | ./test/*.map \ 41 | lib/*.js \ 42 | $(VOWS) 43 | 44 | 45 | # Build the module 46 | all: build 47 | build: build/Debug/bindings.node 48 | build/Debug/bindings.node: $(NODE_GYP) src/*.hpp src/*.cpp src/*.h src/*.c 49 | npm_config_mapserv_build_dir=$(npm_config_mapserv_build_dir) $(NODE_GYP) -v --debug configure build 50 | 51 | # Test the module 52 | test: $(test_deps) 53 | $(VOWS) --spec ./test/mapserv-test.js 54 | 55 | # Run the test suite under valgrind 56 | valgrind: $(test_deps) $(ISTANBUL) 57 | valgrind --leak-check=full --show-reachable=yes --track-origins=yes \ 58 | node --nouse_idle_notification --expose-gc \ 59 | $(VOWS) test/mapserv-test.js 60 | 61 | # Perform the code coverage 62 | cover: coverage/index.html 63 | coverage/index.html: coverage/node-mapserv.info 64 | genhtml --output-directory coverage coverage/node-mapserv.info 65 | @echo "\033[0;32mPoint your browser at \`coverage/index.html\`\033[m\017" 66 | coverage/node-mapserv.info: coverage/bindings.info 67 | lcov --test-name node-mapserv \ 68 | --add-tracefile coverage/lcov.info \ 69 | --add-tracefile coverage/bindings.info \ 70 | --output-file coverage/node-mapserv.info 71 | coverage/bindings.info: coverage/addon.info 72 | lcov --extract coverage/addon.info '*node-mapserv/src/*' --output-file coverage/bindings.info 73 | coverage/addon.info: coverage/lcov.info 74 | lcov --capture --base-directory src/ --directory . --output-file coverage/addon.info 75 | # This generates the JS lcov info as well as gcov `*.gcda` files: 76 | coverage/lcov.info: $(test_deps) $(ISTANBUL) 77 | node --nouse_idle_notification --expose-gc \ 78 | $(ISTANBUL) cover --report lcovonly \ 79 | $(VOWS) -- test/mapserv-test.js 80 | 81 | # Install required node modules 82 | $(NODE_GYP): 83 | npm install node-gyp 84 | 85 | $(VOWS): package.json 86 | npm install vows 87 | @touch $(VOWS) 88 | 89 | $(ISTANBUL): package.json 90 | npm install istanbul 91 | @touch $(ISTANBUL) 92 | 93 | # Generate the Doxygen documentation 94 | doc: doc/.made 95 | doc/.made: doc/Doxyfile 96 | chdir ./doc/ && doxygen 97 | @echo "\033[0;32mPoint your browser at \`doc/html/index.html\`\033[m\017" 98 | @touch doc/.made 99 | 100 | # Clean up any generated files 101 | clean: $(NODE_GYP) 102 | $(NODE_GYP) clean 103 | rm -rf coverage \ 104 | doc/html \ 105 | doc/latex 106 | 107 | .PHONY: test 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node Mapserv 2 | 3 | [![Build Status](https://secure.travis-ci.org/geo-data/node-mapserv.png)](http://travis-ci.org/geo-data/node-mapserv) 4 | 5 | `node-mapserv` is a Node.js module which makes the functionality provided by 6 | the traditional Mapserver `mapserv` CGI program available to Node code. 7 | 8 | `node-mapserv` is best thought of as a wrapper around CGI `mapserv`: internally 9 | it uses a C++ binding to shave away the thin layer representing the binary 10 | `mapserv` CGI interface, replacing it with a javascript interface. All the 11 | underlying `mapserv` logic and code remain the same. 12 | 13 | `node-mapserv` is *not* MapScript for Node. Instead it provides a simple 14 | declarative API for rendering mapserver mapfiles with the following benefits: 15 | 16 | * All the considerable functionality of CGI `mapserv` are at your disposal: 17 | there's no need to reinvent the wheel (maybe just add a few spokes here and 18 | there!). 19 | 20 | * Support for declarative mapfile programming: most of what can be accomplished 21 | imperatively using mapscript can be done declaratively by custom generating 22 | new mapfiles and tweaking existing mapfiles (see 23 | [this post](http://sgillies.net/blog/315/stop-using-mapscript/) for a more 24 | detailed discussion). 25 | 26 | * Adherence to the Node non-blocking philosophy: operations involving I/O (such 27 | as parsing mapfiles and rendering maps) are performed asynchronously in child 28 | threads which keeps the main event loop snappy. Rough benchmarks suggest 29 | performance is comparable to fastcgi mapserv (using `examples/wms-server.js` 30 | with a modified mapfile). 31 | 32 | * Robustly tested: The module has a suite of tests that exercises the whole 33 | API. The tests provide 96% line coverage and 95% function coverage; excluded 34 | code generally handles hard to replicate edge cases (e.g. memory 35 | exhaustion). This suite has been run through Valgrind to check for memory 36 | leaks. 37 | 38 | ## Usage 39 | 40 | It is assumed that you are familiar with 41 | [using `mapserv`](http://mapserver.org/cgi/index.html) and 42 | [creating mapfiles](http://mapserver.org/mapfile/index.html). 43 | 44 | The API is simple and defines the following general pattern: 45 | 46 | 1. Instantiate an instance of the `Map` class from a mapfile on the filesystem 47 | (using `Map.FromFile`) or a mapfile string (using `Map.FromString`). 48 | 49 | 2. Render the mapfile as many times as required using `Map.mapserv`. This 50 | function emulates Mapserver CGI `mapserv` functionality and as such requires 51 | the creation of CGI environment variables to define the request parameters. 52 | 53 | The following example illustrates these steps: 54 | 55 | ```javascript 56 | var mapserv = require('../lib/mapserv'), // the Mapserv module 57 | fs = require('fs'), // for filesystem operations 58 | 59 | // A minimalist mapfile string 60 | mapfile = "MAP \ 61 | NAME hello \ 62 | STATUS ON \ 63 | EXTENT 0 0 4000 3000 \ 64 | SIZE 400 300 \ 65 | IMAGECOLOR 200 255 255 \ 66 | LAYER \ 67 | NAME 'credits' \ 68 | STATUS DEFAULT \ 69 | TRANSFORM FALSE \ 70 | TYPE ANNOTATION \ 71 | FEATURE \ 72 | POINTS \ 73 | 200 150 \ 74 | END \ 75 | TEXT 'Hello world. Mapserver rocks.' \ 76 | END \ 77 | CLASS \ 78 | LABEL \ 79 | TYPE BITMAP \ 80 | COLOR 0 0 0 \ 81 | END \ 82 | END \ 83 | END \ 84 | END"; 85 | 86 | // Instantiate a Map object from the mapfile string. You could use 87 | // `mapserv.Map.FromFile` instead. 88 | mapserv.Map.FromString(mapfile, function handleMap(err, map) { 89 | if (err) throw err; // error loading the mapfile 90 | 91 | // a minimal CGI environment 92 | var env = { 93 | REQUEST_METHOD: 'GET', 94 | QUERY_STRING: 'mode=map&layer=credits' 95 | }; 96 | 97 | map.mapserv(env, function handleMapResponse(err, mapResponse) { 98 | if (err) { 99 | throw err; // error generating the response 100 | } 101 | 102 | // If the response is an image, save it to a file, otherwise write it 103 | // to standard output. 104 | var contentType = mapResponse.headers['Content-Type'][0]; // get the content type from the headers 105 | if (contentType.substr(0, 5) === 'image') { 106 | var filename = 'output.' + contentType.substr(6); // get the file extension from the content type 107 | fs.writeFile(filename, mapResponse.data, function onWrite(err) { 108 | if (err) { 109 | throw err; // error writing to file 110 | } 111 | console.log('Mapserver response written to `%s`', filename); 112 | }); 113 | } else { 114 | console.log(mapResponse.data.toString()); 115 | } 116 | }); 117 | }); 118 | ``` 119 | 120 | Editing the mapfile string and using it to instantiate new `Map` objects will 121 | of course allow you to produce different maps. 122 | 123 | The above example can be found in the package as `examples/hello-world.js`. 124 | `examples/wms-server.js` provides a more full featured example that marries 125 | `node-mapserv` with the stock Node `http` module to create a cascading WMS 126 | server. In addition it illustrates: 127 | 128 | * How to pass a 'body' string to `Map.mapserv` representing the HTTP body of a 129 | request (e.g. used in HTTP POST and PUT requests). 130 | 131 | * The use of the `mapserv.createCGIEnvironment` function used to generate a CGI 132 | environment from an `http.ServerRequest` object. 133 | 134 | Versioning information is also available. From the Node REPL: 135 | 136 | ``` 137 | > var mapserv = require('mapserv'); 138 | > mapserv.versions 139 | { node_mapserv: '0.1.3', 140 | mapserver: '6.3-dev', 141 | mapserver_numeric: 60300, 142 | mapserver_details: 'MapServer version 6.3-dev OUTPUT=PNG OUTPUT=JPEG SUPPORTS=PROJ SUPPORTS=AGG SUPPORTS=FREETYPE SUPPORTS=CAIRO SUPPORTS=ICONV SUPPORTS=FRIBIDI SUPPORTS=WMS_SERVER SUPPORTS=WFS_SERVER SUPPORTS=WCS_SERVER SUPPORTS=FASTCGI SUPPORTS=THREADS SUPPORTS=GEOS INPUT=JPEG INPUT=POSTGIS INPUT=OGR INPUT=GDAL INPUT=SHAPEFILE' } 143 | ``` 144 | 145 | ### Errors 146 | 147 | Errors generated by Mapserver include a number of useful details. This 148 | information is exposed by the following properties which are present on all 149 | errors returned by the module and named `MapserverError` (i.e. all errors 150 | passed to a callback): 151 | 152 | - **`code`**: an integer representing the error category 153 | - **`category`**: a description of the error category 154 | - **`routine`**: the name of the Mapserver routine generating the error 155 | - **`isReported`**: a boolean flagging whether the error has been rendered in 156 | any Mapserver output 157 | - **`errorStack`**: an array containing the stack of any errors leading up to 158 | the current error, most recent error first. This property is not available 159 | in errors present in the stack itself. 160 | 161 | ## Requirements 162 | 163 | * Linux OS (although it should work on other Unices with minimal effort - 164 | patches welcome!). Although not currently supported, the module has been 165 | built successfully on Windows - see `WINDOWS-INSTALL.md` for more details. 166 | 167 | * Node.js 0.10 (Not yet working with 0.12) 168 | 169 | * Mapserver >= 6.2. If you are using `Map.FromString` ensure that you are using 170 | Mapserver >= 6.2.1 or alternatively you have applied 171 | [this patch](https://github.com/mapserver/mapserver/commit/e9e48941e9b02378de57a8ad6c6aa0d070816b06). 172 | Mapserver *must* be compiled with support for threads. 173 | 174 | ## Installation 175 | 176 | ### Using NPM 177 | 178 | The latest stable release of Node Mapcache is available via the Node 179 | Package Manager: 180 | 181 | * Ensure [Node.js](http://nodejs.org) and [Mapserver](http://www.mapserver.org) 182 | are available on your system. Mapserver will need to have been built from 183 | source with the source directory still available. 184 | 185 | * Point `node-mapserv` to the Mapserver source directory. It uses the build 186 | files to configure itself during installation. E.g. 187 | 188 | `npm config set mapserv:build_dir /tmp/mapserver-6.2` 189 | 190 | * Get and install `node-mapserv`: 191 | 192 | `npm install mapserv` 193 | 194 | * Optionally test that everything is working as expected (recommended): 195 | 196 | `npm test mapserv` 197 | 198 | ### Using Docker 199 | 200 | Assuming you have [Docker](http://www.docker.io/) available on your 201 | system, the following command will obtain a docker image with the 202 | latest Node Mapserv code from git built against the latest Mapserver 203 | git checkout: 204 | 205 | docker pull homme/node-mapserv:latest 206 | 207 | Note that the latest Mapserver git checkout is the latest **at the 208 | time the image was built**. If you want the absolute latest code of 209 | both Node Mapserv *and* Mapserver, build the docker image yourself 210 | locally along these lines: 211 | 212 | docker build -t homme/node-mapserv:latest https://raw.github.com/geo-data/node-mapserv/master/docker/latest/Dockerfile 213 | 214 | By default the image runs the Node Mapserv tests: 215 | 216 | docker run homme/node-mapserv:latest 217 | 218 | Running Node directly from the image allows you to `require()` and 219 | play around with Node Mapserv interactively: 220 | 221 | docker run -t -i homme/node-mapserv:latest /usr/bin/node 222 | > var mapserv = require('mapserv'); 223 | undefined 224 | > mapserv.versions 225 | { node_mapserv: '0.1.4', 226 | mapserver: '6.5-dev', 227 | mapserver_numeric: 60500, 228 | mapserver_details: 'MapServer version 6.5-dev OUTPUT=PNG OUTPUT=JPEG OUTPUT=KML SUPPORTS=PROJ SUPPORTS=AGG SUPPORTS=FREETYPE SUPPORTS=CAIRO SUPPORTS=SVG_SYMBOLS SUPPORTS=SVGCAIRO SUPPORTS=ICONV SUPPORTS=XMP SUPPORTS=FRIBIDI SUPPORTS=WMS_SERVER SUPPORTS=WMS_CLIENT SUPPORTS=WFS_SERVER SUPPORTS=WFS_CLIENT SUPPORTS=WCS_SERVER SUPPORTS=SOS_SERVER SUPPORTS=THREADS SUPPORTS=GEOS INPUT=JPEG INPUT=POSTGIS INPUT=OGR INPUT=GDAL INPUT=SHAPEFILE' } 229 | 230 | See the [Docker Index](https://index.docker.io/u/homme/node-mapserv/) 231 | for further information. 232 | 233 | ## Recommendations 234 | 235 | * Avoid using Mapserver features that are not thread safe: `node-mapserv` makes 236 | heavy use of threads and although this is safe for core mapserver operations, 237 | some extended features should be avoided. See the 238 | [Mapserver FAQ](http://mapserver.org/faq.html?highlight=threads#is-mapserver-thread-safe) 239 | and GitHub issues #4041 and #4044 for further details. 240 | 241 | * Become familiar with Mapserver 242 | [runtime substitution](http://mapserver.org/cgi/runsub.html): this allows you 243 | to alter portions of a mapfile based on data passed via a CGI request. 244 | 245 | * Use the 246 | [`PROCESSING "CLOSE_CONNECTION=DEFER"`](http://mapserver.org/mapfile/layer.html#index-49) 247 | directive in you mapfiles in order to cache data connections where possible: 248 | `Map` instances wrap persistent mapfile data structures and can therefore 249 | benefit from pooling persistent data connections in the same way as fastcgi 250 | mapserv. 251 | 252 | * Check out [`node-mapcache`](https://npmjs.org/package/mapcache): this can 253 | work well in combination with `node-mapserv` for generating tiled maps. 254 | 255 | ## Developing 256 | 257 | Fork the code on GitHub or clone it: 258 | 259 | git clone https://github.com/geo-data/node-mapserv.git 260 | cd node-mapserv 261 | 262 | Build the module in Debug mode using: 263 | 264 | make build 265 | 266 | By default this uses the Mapserver build directory previously specified using 267 | `npm config set mapserv:build_dir`; to override this do something along the 268 | following lines: 269 | 270 | make build npm_config_mapserv_build_dir=/tmp/mapserver-6.2 271 | 272 | You may want to ensure you're building in a clean source tree in which case: 273 | 274 | make clean 275 | 276 | Add tests for your changes to `test/mapserv-test.js` and run them: 277 | 278 | make test 279 | 280 | Perform code coverage analysis to ensure all code paths in your changes are 281 | tested (this requires [`lcov`](http://ltp.sourceforge.net/coverage/lcov.php) be 282 | installed): 283 | 284 | make cover 285 | 286 | Finally run the test suite through `valgrind` to ensure you haven't introduced 287 | any memory issues: 288 | 289 | make valgrind 290 | 291 | And issue your pull request or patch... 292 | 293 | ### Documentation 294 | 295 | Doxygen based documentation is available for the C++ bindings: 296 | 297 | make doc 298 | 299 | ## Bugs 300 | 301 | Please report bugs or issues using the 302 | [GitHub issue tracker](https://github.com/geo-data/node-mapserv). 303 | 304 | ## Licence 305 | 306 | [BSD 2-Clause](http://opensource.org/licenses/BSD-2-Clause). 307 | 308 | ## Contact 309 | 310 | Homme Zwaagstra 311 | -------------------------------------------------------------------------------- /WINDOWS-INSTALL.md: -------------------------------------------------------------------------------- 1 | # Node Mapserv on Windows 2 | 3 | Although Windows is not currently an officially supported platform for Node 4 | Mapserv, the module has been successfully built on Windows. The related GitHub 5 | issues ([#7](https://github.com/geo-data/node-mapserv/issues/7) and 6 | [#14](https://github.com/geo-data/node-mapserv/issues/14)) provide further 7 | details on building with Windows. 8 | 9 | ## Installation 10 | 11 | It would be nice to simplify the Windows build process to enable a simple `npm 12 | install mapserv` (in a similar manner to the Linux build) but for now it is a 13 | manual process along the following lines: 14 | 15 | 1. Ensure you have an appropriate version of Node and `node-gyp` installed. 16 | 17 | 2. [Download](https://github.com/geo-data/node-mapserv/tags) and unpack Node 18 | Mapserv. Take note of where the `binding.gyp` file is. 19 | 20 | 3. Download the appropriate Mapserver SDK from the 21 | [GDAL and MapServer build SDK packages](http://www.gisinternals.com/sdk/) 22 | section (e.g. selecting `release-1600-dev`). Take note of where the 23 | `Makefile` is. 24 | 25 | 4. Unpack the SDK and edit `binding.gyp` to change the `ms_buildkit%` variable 26 | to point to SDK root path. 27 | 28 | 5. Download the MapServer source from the 29 | [GDAL and MapServer latest release versions](http://www.gisinternals.com/sdk/) 30 | section (e.g. selecting `release-1600-gdal-1-10-0-mapserver-6-2-1` and then 31 | `release-1600-gdal-1-10-0-mapserver-6-2-1-src.zip`). 32 | 33 | 6. Unpack the Mapserver source in the root of the SDK. Edit `binding.gyp` to 34 | change the `ms_root%` variable to point to the Mapserver source root path. 35 | 36 | 7. Adapt the SDK `Makefile` to also point to your Mapserver source root 37 | directory (Search for `MS_DIR`). 38 | 39 | 8. Build Mapserver: in a MSVS console go in the SDK directory and type `nmake 40 | ms`. Ensure the libraries listed in `binding.gyp` correspond to those just 41 | built and edit `binding.gyp` if this isn't the case. 42 | 43 | 9. Build Node Mapserv: from a console in the Node Mapserv root directory type 44 | `npm install .` 45 | 46 | Note again that Windows is not supported so you will have to work through 47 | issues yourself but feel free to discuss suggestions and improvements on 48 | GitHub. 49 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "bindings", 5 | "sources": [ 6 | "src/node-mapserv.cpp", 7 | "src/map.cpp", 8 | "src/error.cpp", 9 | "src/node-mapservutil.c" 10 | ], 11 | "include_dirs": [ 12 | " 18 | 19 | # Ensure the package repository is up to date 20 | RUN apt-get update -y 21 | 22 | # Install basic dependencies 23 | RUN apt-get install -y software-properties-common python-software-properties python g++ make cmake wget git 24 | 25 | # Install the ubuntu gis and Node repositories 26 | RUN add-apt-repository -y ppa:ubuntugis/ubuntugis-unstable 27 | RUN add-apt-repository -y ppa:chris-lea/node.js 28 | RUN apt-get update 29 | 30 | # Install Node.js 31 | RUN apt-get install -y nodejs 32 | 33 | # Install mapserver dependencies provided by Ubuntu repositories 34 | RUN apt-get install -y libxml2-dev \ 35 | libxslt1-dev \ 36 | libproj-dev \ 37 | libfribidi-dev \ 38 | libcairo2-dev \ 39 | librsvg2-dev \ 40 | libmysqlclient-dev \ 41 | libpq-dev \ 42 | libcurl4-gnutls-dev \ 43 | libexempi-dev \ 44 | libgdal-dev \ 45 | libgeos-dev 46 | 47 | # Install libharfbuzz from source as it is not in a repository 48 | RUN apt-get install -y bzip2 49 | RUN cd /tmp && wget http://www.freedesktop.org/software/harfbuzz/release/harfbuzz-0.9.19.tar.bz2 && \ 50 | tar xjf harfbuzz-0.9.19.tar.bz2 && \ 51 | cd harfbuzz-0.9.19 && \ 52 | ./configure && \ 53 | make && \ 54 | make install && \ 55 | ldconfig 56 | 57 | # Install Mapserver itself 58 | RUN git clone --depth=1 https://github.com/mapserver/mapserver/ /usr/local/src/mapserver 59 | RUN mkdir /usr/local/src/mapserver/build && \ 60 | cd /usr/local/src/mapserver/build && \ 61 | cmake ../ -DWITH_THREAD_SAFETY=1 \ 62 | -DWITH_PROJ=1 \ 63 | -DWITH_KML=1 \ 64 | -DWITH_SOS=1 \ 65 | -DWITH_WMS=1 \ 66 | -DWITH_FRIBIDI=1 \ 67 | -DWITH_HARFBUZZ=1 \ 68 | -DWITH_ICONV=1 \ 69 | -DWITH_CAIRO=1 \ 70 | -DWITH_RSVG=1 \ 71 | -DWITH_MYSQL=1 \ 72 | -DWITH_GEOS=1 \ 73 | -DWITH_POSTGIS=1 \ 74 | -DWITH_GDAL=1 \ 75 | -DWITH_OGR=1 \ 76 | -DWITH_CURL=1 \ 77 | -DWITH_CLIENT_WMS=1 \ 78 | -DWITH_CLIENT_WFS=1 \ 79 | -DWITH_WFS=1 \ 80 | -DWITH_WCS=1 \ 81 | -DWITH_LIBXML2=1 \ 82 | -DWITH_GIF=1 \ 83 | -DWITH_EXEMPI=1 \ 84 | -DWITH_XMLMAPFILE=1 \ 85 | -DWITH_FCGI=0 && \ 86 | make && \ 87 | make install && \ 88 | ldconfig 89 | 90 | # Install Node Mapserv. This installs to `/node_modules` so will always be found 91 | RUN git clone https://github.com/geo-data/node-mapserv/ /usr/local/src/node-mapserv 92 | RUN npm config set mapserv:build_dir /usr/local/src/mapserver/build && \ 93 | npm install /usr/local/src/node-mapserv 94 | RUN npm install vows # so that the default command works 95 | 96 | # Run the Node Mapserv tests by default 97 | CMD npm test mapserv 98 | -------------------------------------------------------------------------------- /examples/hello-world.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * A basic introduction to Node Mapserv 30 | * 31 | * This illustrates the core API and general usage pattern provided by the 32 | * Mapserv module by generating a map response and saving it to a file. It 33 | * shows how to: 34 | * 35 | * - instantiate a `Map` object from a mapfile string 36 | * - set up a minimal CGI environment for specifying map requests 37 | * - generate a map response using the `Map.mapserv` method 38 | * - access the `headers` and `data` properties in the map response 39 | */ 40 | 41 | var mapserv = require('../lib/mapserv'), // the Mapserv module 42 | fs = require('fs'), // for filesystem operations 43 | 44 | // A minimalist mapfile string 45 | mapfile = "MAP \ 46 | NAME hello \ 47 | STATUS ON \ 48 | EXTENT 0 0 4000 3000 \ 49 | SIZE 400 300 \ 50 | IMAGECOLOR 200 255 255 \ 51 | LAYER \ 52 | NAME 'credits' \ 53 | STATUS DEFAULT \ 54 | TRANSFORM FALSE \ 55 | TYPE ANNOTATION \ 56 | FEATURE \ 57 | POINTS \ 58 | 200 150 \ 59 | END \ 60 | TEXT 'Hello world. Mapserver rocks.' \ 61 | END \ 62 | CLASS \ 63 | LABEL \ 64 | TYPE BITMAP \ 65 | COLOR 0 0 0 \ 66 | END \ 67 | END \ 68 | END \ 69 | END"; 70 | 71 | // Instantiate a Map object from the mapfile string. You could use 72 | // `mapserv.Map.FromFile` instead. 73 | mapserv.Map.FromString(mapfile, function handleMap(err, map) { 74 | if (err) throw err; // error loading the mapfile 75 | 76 | // a minimal CGI environment 77 | var env = { 78 | REQUEST_METHOD: 'GET', 79 | QUERY_STRING: 'mode=map&layer=credits' 80 | }; 81 | 82 | map.mapserv(env, function handleMapResponse(err, mapResponse) { 83 | if (err) { 84 | throw err; // error generating the response 85 | } 86 | 87 | // If the response is an image, save it to a file, otherwise write it 88 | // to standard output. 89 | var contentType = mapResponse.headers['Content-Type'][0]; // get the content type from the headers 90 | if (contentType.substr(0, 5) === 'image') { 91 | var filename = 'output.' + contentType.substr(6); // get the file extension from the content type 92 | fs.writeFile(filename, mapResponse.data, function onWrite(err) { 93 | if (err) { 94 | throw err; // error writing to file 95 | } 96 | console.log('Mapserver response written to `%s`', filename); 97 | }); 98 | } else { 99 | console.log(mapResponse.data.toString()); 100 | } 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /examples/wms-server.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * Set up Mapserv as a WMS server 30 | * 31 | * This provides an example of how to use the Mapserv module in combination 32 | * with the Node HTTP module to create a Cascading WMS server. The resulting 33 | * WMS server acts as a proxy to the Marine Geo WMS service. It can be 34 | * accessed using a standard WMS client or by using the built in mapserver 35 | * OpenLayers client. 36 | */ 37 | 38 | var path = require('path'), // for file path manipulations 39 | http = require('http'), // for the http server 40 | mapserv = require('../lib/mapserv'), // the Mapserv module 41 | host = 'localhost', // the server name 42 | port = 3000, // which port will the server run on? 43 | baseUrl = "http://" + host + ":" + port, // what is the server url? 44 | mapfile = path.join(__dirname, 'wms-server.map'); // the location of the mapfile 45 | 46 | // Instantiate a Map object from the mapfile 47 | mapserv.Map.FromFile(mapfile, function handleMap(err, map) { 48 | if (err) throw err; // error loading the mapfile 49 | 50 | // fire up a http server, handling all requests 51 | http.createServer(function handleMapRequest(req, res) { 52 | var env = mapserv.createCGIEnvironment(req), 53 | buffer; // a `Buffer` object for the request body 54 | 55 | // buffer any request body 56 | req.once('data', function onFirstData(chunk) { 57 | buffer = chunk; // the initial chunk 58 | 59 | // deal with the rest of the message body 60 | req.on('data', function onData(chunk) { 61 | buffer.concat([chunk], 1); 62 | }); 63 | }); 64 | 65 | // no more request data: prepare our response 66 | req.on('end', function onEnd() { 67 | // delegate the request to the Map object, handling the response 68 | map.mapserv(env, buffer, function handleMapResponse(err, mapResponse) { 69 | console.log('Serving ' + req.url); 70 | 71 | if (err) { 72 | // the map returned an error: handle it 73 | if (mapResponse.data) { 74 | // return the error as rendered by mapserver 75 | res.writeHead(500, mapResponse.headers); 76 | res.end(mapResponse.data); 77 | } else { 78 | // A raw error we need to output ourselves 79 | res.writeHead(500, {'Content-Type': 'text/plain'}); 80 | res.end(err.stack); 81 | } 82 | console.error(err.stack); // log the error 83 | return; 84 | } 85 | 86 | // send the map response to the client 87 | res.writeHead(200, mapResponse.headers); 88 | if (req.method !== 'HEAD') { 89 | res.end(mapResponse.data); 90 | } else { 91 | res.end(); 92 | } 93 | }); 94 | }); 95 | }).listen(port, "localhost"); 96 | 97 | console.log( 98 | "Cascading WMS Server running at " + baseUrl + " - try the following URL:\n" + 99 | baseUrl + "/?mode=browse&template=openlayers&layer=GMRT\n" + 100 | "or point a WMS client at " + baseUrl + "/?" 101 | ); 102 | }); 103 | -------------------------------------------------------------------------------- /examples/wms-server.map: -------------------------------------------------------------------------------- 1 | # Used in the `node-mapserv` wms example 2 | MAP 3 | NAME "WMS-TEST" 4 | # Map image size 5 | SIZE 400 400 6 | UNITS dd 7 | EXTENT -180 -90 180 90 8 | 9 | PROJECTION 10 | 'init=epsg:4326' 11 | END 12 | 13 | IMAGECOLOR 255 255 255 14 | IMAGEQUALITY 95 15 | IMAGETYPE png 16 | 17 | OUTPUTFORMAT 18 | NAME png 19 | DRIVER 'GD/PNG' 20 | MIMETYPE 'image/png' 21 | IMAGEMODE RGBA 22 | EXTENSION 'png' 23 | END 24 | 25 | WEB 26 | # WMS server settings 27 | METADATA 28 | 'ows_title' 'WMS-TEST' 29 | 'ows_onlineresource' 'http://localhost:3000/' 30 | 'ows_srs' 'EPSG:4326' 31 | 'ows_enable_request' '*' 32 | END 33 | END 34 | 35 | LAYER 36 | NAME "GMRT" 37 | TYPE RASTER 38 | STATUS ON 39 | CONNECTION "http://gmrt.marine-geo.org/cgi-bin/mapserv?map=/public/mgg/web/gmrt.marine-geo.org/htdocs/services/map/wms_merc.map&" 40 | CONNECTIONTYPE WMS 41 | METADATA 42 | "ows_srs" "EPSG:4326" 43 | "ows_name" "GMRT" 44 | "ows_server_version" "1.0.0" 45 | "ows_format" "image/png" 46 | END 47 | END 48 | END 49 | -------------------------------------------------------------------------------- /lib/mapserv.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * Mapserv module 30 | * 31 | * This acts as a JavaScript shim exposing the API defined in the C++ addon. 32 | * 33 | * See the README for further details on the module. 34 | */ 35 | 36 | var bindings, 37 | path = require('path'), // for file path manipulations 38 | url = require('url'); // for url parsing 39 | 40 | // try and load the bindings 41 | try { 42 | bindings = require('../build/Release/bindings'); 43 | } catch (err) { 44 | // perhaps it's a debug build... 45 | try { 46 | bindings = require('../build/Debug/bindings'); 47 | } catch(err2) { 48 | // ... no it isn't; throw the original error 49 | throw err; 50 | } 51 | } 52 | 53 | /** 54 | * Create a CGI environment from a Node HTTP request object 55 | * 56 | * This is a utility function that facilitates the generation of CGI 57 | * environment variables from a Node HTTP request object. 58 | * 59 | * See 60 | * 61 | * for details of CGI environment variables. 62 | */ 63 | function createCGIEnvironment(req, vars) { 64 | var urlParts = url.parse(decodeURIComponent(req.url)), // parse the request url 65 | host = req.headers.host.split(':'), 66 | env = { 67 | // Server specific variables: 68 | SERVER_SOFTWARE: 'Node.js', 69 | SERVER_NAME: host[0], 70 | GATEWAY_INTERFACE: 'CGI/1.1', 71 | 72 | // Request specific variables: 73 | SERVER_PROTOCOL: 'HTTP/' + req.httpVersion, 74 | SERVER_PORT: host[1], 75 | REQUEST_METHOD: req.method, 76 | PATH_INFO: urlParts.pathname || '/', 77 | PATH_TRANSLATED: path.resolve(path.join('.', urlParts.pathname)), 78 | SCRIPT_NAME: '/', 79 | QUERY_STRING: urlParts.query || '', 80 | REMOTE_ADDR: req.connection.remoteAddress 81 | }, 82 | key; 83 | 84 | // add content and authorisation specific variables 85 | if ('content-length' in req.headers) { 86 | env.CONTENT_LENGTH = req.headers['content-length']; 87 | } 88 | if ('content-type' in req.headers) { 89 | env.CONTENT_TYPE = req.headers['content-type']; 90 | } 91 | if ('authorization' in req.headers) { 92 | var auth = req.headers.authorization.split(' '); 93 | env.AUTH_TYPE = auth[0]; 94 | } 95 | 96 | // add client specific variables 97 | for (key in req.headers) { 98 | switch (key) { 99 | case 'host': 100 | case 'content-length': 101 | case 'content-type': 102 | case 'authorization': 103 | // skip already handled headers 104 | break; 105 | default: 106 | // convert the header to a CGI variable 107 | var name = 'HTTP_' + key.toUpperCase().replace(/-/g, '_'); 108 | env[name] = req.headers[key]; 109 | } 110 | } 111 | 112 | // add user specified variables 113 | if (vars) { 114 | for (key in vars) { 115 | env[key] = vars[key]; 116 | } 117 | } 118 | 119 | return env; 120 | } 121 | 122 | module.exports.Map = bindings.Map; 123 | module.exports.versions = bindings.versions; 124 | module.exports.createCGIEnvironment = createCGIEnvironment; 125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "include_dir": "", 4 | "lib_dir": "" 5 | }, 6 | "author": "Homme Zwaagstra ", 7 | "name": "mapserv", 8 | "description": "All the functionality of Mapserver's mapserv CGI program made available to Node", 9 | "version": "0.1.5", 10 | "repository": { 11 | "type": "git", 12 | "url": "http://github.com/geo-data/node-mapserv.git" 13 | }, 14 | "main": "lib/mapserv.js", 15 | "engines": { 16 | "node": ">=0.10 <0.11" 17 | }, 18 | "scripts": { 19 | "test": "vows --spec ./test/mapserv-test.js" 20 | }, 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "vows": "*", 24 | "istanbul": "*" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/error.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2013, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * @file error.cpp 30 | * @brief This defines the `MapserverError` class. 31 | */ 32 | 33 | #include "error.hpp" 34 | 35 | Persistent MapserverError::MapserverError_symbol; 36 | 37 | /** 38 | * @defgroup error_properties Properties of the error object 39 | * 40 | * These represent property names that are added to javascript `Error` objects 41 | * generated by mapserver. 42 | * 43 | * @{ 44 | */ 45 | Persistent MapserverError::name_symbol; 46 | Persistent MapserverError::code_symbol; 47 | Persistent MapserverError::category_symbol; 48 | Persistent MapserverError::routine_symbol; 49 | Persistent MapserverError::isReported_symbol; 50 | Persistent MapserverError::errorStack_symbol; 51 | /**@}*/ 52 | 53 | 54 | /** 55 | * @details This is called from the module initialisation function 56 | * when the module is first loaded by Node. It should only be called 57 | * once per process. 58 | */ 59 | void MapserverError::Init() { 60 | // initialise the persistent strings 61 | MapserverError_symbol = NODE_PSYMBOL("MapserverError"); 62 | 63 | name_symbol = NODE_PSYMBOL("name"); 64 | code_symbol = NODE_PSYMBOL("code"); 65 | category_symbol = NODE_PSYMBOL("category"); 66 | routine_symbol = NODE_PSYMBOL("routine"); 67 | isReported_symbol = NODE_PSYMBOL("isReported"); 68 | errorStack_symbol = NODE_PSYMBOL("errorStack"); 69 | } 70 | 71 | /** 72 | * @details A constructor that builds from a standard mapserver `errorObj`. 73 | * This effectively copies the mapserver data structure. 74 | * 75 | * @param error A mapserver `errorObj`. 76 | */ 77 | MapserverError::MapserverError(const errorObj *error) { 78 | MapserverError *copy = this; 79 | length = 0; 80 | while (error) { 81 | copy->code = error->code; 82 | copy->routine = error->routine; 83 | copy->message = error->message; 84 | copy->isReported = error->isreported; 85 | if (error->next) { 86 | copy->next = new MapserverError(); 87 | copy = copy->next; 88 | } 89 | error = error->next; 90 | length++; 91 | } 92 | copy->next = NULL; 93 | } 94 | 95 | /** 96 | * @details This returns a representation of the `MapserverError` instance as a 97 | * V8 exception. The internal linked list implementing the error stack is 98 | * converted to a Javascript Array. 99 | */ 100 | Handle MapserverError::toV8Error() { 101 | HandleScope scope; 102 | 103 | // Represent the error stack linked list as an array 104 | MapserverError *error = next; 105 | unsigned int i = 0; 106 | Local errorStack = Array::New(length-1); 107 | while (error) { 108 | Handle exception = ToV8Error(error); 109 | errorStack->Set(i++, exception); // add the error to the stack 110 | error = error->next; 111 | } 112 | 113 | // Create an error representing the current error 114 | Handle result = ToV8Error(this); 115 | result->ToObject()->Set(errorStack_symbol, errorStack); // add the stack to the current error 116 | 117 | return scope.Close(result); 118 | } 119 | 120 | /** 121 | * @detail A class method that converts a `MapserverError` to a V8 exception. 122 | * This only operates on the error properties and does not process the internal 123 | * linked list. 124 | * 125 | * @param error The `MapserverError` pointer. 126 | */ 127 | Handle MapserverError::ToV8Error(MapserverError *error) { 128 | HandleScope scope; 129 | 130 | char *category = msGetErrorCodeString(error->code); 131 | Local result = Exception::Error(String::New(( error->message.length() ? error->message.c_str() : category ))); 132 | Local object = result->ToObject(); 133 | object->Set(name_symbol, MapserverError_symbol); 134 | object->Set(routine_symbol, String::New(error->routine.c_str())); 135 | object->Set(code_symbol, Integer::New(error->code)); 136 | object->Set(category_symbol, String::New(category)); 137 | object->Set(isReported_symbol, Boolean::New(error->isReported)); 138 | 139 | return scope.Close(result); 140 | } 141 | -------------------------------------------------------------------------------- /src/error.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2013, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | #ifndef __NODE_MAPSERV_ERROR_H__ 29 | #define __NODE_MAPSERV_ERROR_H__ 30 | 31 | /** 32 | * @file error.hpp 33 | * @brief This declares error handling facilities. 34 | */ 35 | 36 | // Standard headers 37 | #include 38 | 39 | // Node related headers 40 | #include 41 | #include 42 | 43 | // Mapserver headers 44 | #include "maperror.h" 45 | 46 | using namespace v8; 47 | 48 | /** 49 | * @brief A representation of a Mapserver error 50 | * 51 | * This class effectively duplicates the Mapserver `errorObj` data structure, 52 | * decorating it with methods that: 53 | * 54 | * - ease instantiation from an `errorObj` 55 | * - facilitate conversion to a Javascript representation of the error 56 | * 57 | * Standard `errorObj` structure cannot be used directly as they are destroyed 58 | * when their thread terminates. 59 | */ 60 | class MapserverError { 61 | public: 62 | 63 | /// Initialise the class 64 | static void Init(); 65 | 66 | /// Instantiate a MapserverError from an errorObj 67 | MapserverError(const errorObj *error); 68 | 69 | /// Create an error from a string, routine and optional error code 70 | MapserverError(const char *message, const char *routine, int code=MS_MISCERR) : 71 | code(code), 72 | routine(routine), 73 | message(message), 74 | isReported(false), 75 | next(NULL), 76 | length(1) 77 | { 78 | } 79 | 80 | /// Clear up, deleting all linked errors 81 | ~MapserverError() { 82 | while (next) { 83 | MapserverError* prev = next; 84 | next = prev->next; 85 | prev->next = NULL; // so the destructor isn't called recursively 86 | delete prev; 87 | } 88 | } 89 | 90 | /// Convert the error to a V8 exception 91 | Handle toV8Error(); 92 | 93 | private: 94 | 95 | /// The Mapserver error code 96 | int code; 97 | /// The routine from which the error originates 98 | std::string routine; 99 | /// The error message 100 | std::string message; 101 | /// Has the error been reported by Mapserver? 102 | bool isReported; 103 | /// The previous error in the error stack 104 | MapserverError *next; 105 | /// The number of errors in this error stack 106 | unsigned int length; 107 | 108 | /// Instantiate a bare bones error: populate it later 109 | MapserverError() : 110 | isReported(false), 111 | next(NULL), 112 | length(1) 113 | { 114 | } 115 | 116 | /// Convert an error to a V8 exception 117 | static Handle ToV8Error(MapserverError *error); 118 | 119 | /// The string "MapserverError" 120 | static Persistent MapserverError_symbol; 121 | /// The string "name" 122 | static Persistent name_symbol; 123 | /// The string "code" 124 | static Persistent code_symbol; 125 | /// The string "category" 126 | static Persistent category_symbol; 127 | /// The string "routine" 128 | static Persistent routine_symbol; 129 | /// The string "isReported" 130 | static Persistent isReported_symbol; 131 | /// The string "errorStack" 132 | static Persistent errorStack_symbol; 133 | }; 134 | 135 | #endif /* __NODE_MAPSERV_ERROR_H__ */ 136 | -------------------------------------------------------------------------------- /src/map.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * @file map.cpp 30 | * @brief This defines the primary `Map` class. 31 | */ 32 | 33 | #include "map.hpp" 34 | #include "node-mapservutil.h" 35 | 36 | Persistent Map::map_template; 37 | 38 | /** 39 | * @defgroup map_response Properties of the mapserv response object 40 | * 41 | * These represent property names of the items present in the response 42 | * object returned from a mapserv request. 43 | * 44 | * @{ 45 | */ 46 | Persistent Map::data_symbol; 47 | Persistent Map::headers_symbol; 48 | /**@}*/ 49 | 50 | /** 51 | * @details This is called from the module initialisation function 52 | * when the module is first loaded by Node. It should only be called 53 | * once per process. 54 | * 55 | * @param target The object representing the module. 56 | */ 57 | void Map::Init(Handle target) { 58 | HandleScope scope; 59 | 60 | Local template_ = FunctionTemplate::New(New); 61 | 62 | map_template = Persistent::New(template_); 63 | map_template->InstanceTemplate()->SetInternalFieldCount(1); 64 | map_template->SetClassName(String::NewSymbol("Map")); 65 | 66 | data_symbol = NODE_PSYMBOL("data"); 67 | headers_symbol = NODE_PSYMBOL("headers"); 68 | 69 | NODE_SET_PROTOTYPE_METHOD(map_template, "mapserv", MapservAsync); 70 | NODE_SET_METHOD(map_template, "FromFile", FromFileAsync); 71 | NODE_SET_METHOD(map_template, "FromString", FromStringAsync); 72 | 73 | target->Set(String::NewSymbol("Map"), map_template->GetFunction()); 74 | } 75 | 76 | /** 77 | * @details This is a constructor method used to return a new `Map` instance. 78 | * 79 | * `args` should contain the following parameters: 80 | * 81 | * @param config An `External` object wrapping a `mapObj` instance. 82 | */ 83 | Handle Map::New(const Arguments& args) { 84 | HandleScope scope; 85 | if (!args.IsConstructCall()) { 86 | THROW_CSTR_ERROR(Error, "Map() is expected to be called as a constructor with the `new` keyword"); 87 | } 88 | 89 | if (args.Length() != 1) { 90 | THROW_CSTR_ERROR(Error, "usage: new Map(mapObj)"); 91 | } 92 | REQ_EXT_ARG(0, map); 93 | 94 | Map* self = new Map((mapObj *)map->Value()); 95 | self->Wrap(args.This()); 96 | return scope.Close(args.This()); 97 | } 98 | 99 | /** 100 | * @details This is an asynchronous factory method creating a new `Map` 101 | * instance from a mapserver mapfile. 102 | * 103 | * `args` should contain the following parameters: 104 | * 105 | * @param mapfile A string representing the mapfile path. 106 | * 107 | * @param callback A function that is called on error or when the map has been 108 | * created. It should have the signature `callback(err, map)`. 109 | */ 110 | Handle Map::FromFileAsync(const Arguments& args) { 111 | HandleScope scope; 112 | 113 | if (args.Length() != 2) { 114 | THROW_CSTR_ERROR(Error, "usage: Map.FromFile(mapfile, callback)"); 115 | } 116 | REQ_STR_ARG(0, mapfile); 117 | REQ_FUN_ARG(1, callback); 118 | 119 | MapfileBaton *baton = new MapfileBaton(); 120 | 121 | baton->request.data = baton; 122 | baton->map = NULL; 123 | baton->callback = Persistent::New(callback); 124 | baton->error = NULL; 125 | baton->mapfile = *mapfile; 126 | 127 | uv_queue_work(uv_default_loop(), 128 | &baton->request, 129 | FromFileWork, 130 | (uv_after_work_cb) FromFileAfter); 131 | 132 | return Undefined(); 133 | } 134 | 135 | /** 136 | * @details This is called by `FromFileAsync` and runs in a different thread to 137 | * that function. 138 | * 139 | * @param req The asynchronous libuv request. 140 | */ 141 | void Map::FromFileWork(uv_work_t *req) { 142 | /* No HandleScope! This is run in a separate thread: *No* contact 143 | should be made with the Node/V8 world here. */ 144 | 145 | MapfileBaton *baton = static_cast(req->data); 146 | 147 | baton->map = msLoadMap(const_cast(baton->mapfile.c_str()), NULL); 148 | if (!baton->map) { 149 | errorObj *error = msGetErrorObj(); 150 | if (!error) { 151 | baton->error = new MapserverError("Could not load mapfile", "Map::FromFileWork()"); 152 | } else { 153 | baton->error = new MapserverError(error); 154 | } 155 | } 156 | 157 | msResetErrorList(); 158 | return; 159 | } 160 | 161 | /** 162 | * @details This is set by `FromFileAsync` to run after `FromFileWork` has 163 | * finished. It is passed the response generated by the latter and runs in the 164 | * same thread as the former. It creates a `Map` instance and returns it 165 | * to the caller via the callback they originally specified. 166 | * 167 | * @param req The asynchronous libuv request. 168 | */ 169 | void Map::FromFileAfter(uv_work_t *req) { 170 | HandleScope scope; 171 | 172 | MapfileBaton *baton = static_cast(req->data); 173 | Handle argv[2]; 174 | 175 | if (baton->error) { 176 | argv[0] = baton->error->toV8Error(); 177 | argv[1] = Undefined(); 178 | delete baton->error; // we've finished with it 179 | } else { 180 | Local mapObj = External::New(baton->map); 181 | Persistent map = Persistent(map_template->GetFunction()->NewInstance(1, &mapObj)); 182 | 183 | argv[0] = Undefined(); 184 | argv[1] = scope.Close(map); 185 | } 186 | 187 | // pass the results to the user specified callback function 188 | TryCatch try_catch; 189 | baton->callback->Call(Context::GetCurrent()->Global(), 2, argv); 190 | if (try_catch.HasCaught()) { 191 | FatalException(try_catch); 192 | } 193 | 194 | // clean up 195 | baton->callback.Dispose(); 196 | delete baton; 197 | return; 198 | } 199 | 200 | /** 201 | * @details This is an asynchronous factory method creating a new `Map` 202 | * instance from a string representation of a mapserver mapfile. 203 | * 204 | * `args` should contain the following parameters: 205 | * 206 | * @param mapfile A string representing the mapfile. 207 | * 208 | * @param callback A function that is called on error or when the map has been 209 | * created. It should have the signature `callback(err, map)`. 210 | */ 211 | Handle Map::FromStringAsync(const Arguments& args) { 212 | HandleScope scope; 213 | string mapfile; 214 | 215 | if (args.Length() != 2) { 216 | THROW_CSTR_ERROR(Error, "usage: Map.FromString(mapfile, callback)"); 217 | } 218 | 219 | // get the mapfile string from the arguments 220 | if (args[0]->IsString()) { 221 | mapfile = *String::Utf8Value(args[0]->ToString()); 222 | } else if (args[0]->IsObject()) { 223 | // see if it's a buffer 224 | if (!Buffer::HasInstance(args[0])) { 225 | THROW_CSTR_ERROR(TypeError, "Argument 0 must be a string or buffer"); 226 | } 227 | Local buffer = args[0]->ToObject(); 228 | mapfile = string(Buffer::Data(buffer), Buffer::Length(buffer)); 229 | } else { 230 | THROW_CSTR_ERROR(TypeError, "Argument 0 must be a string or buffer"); 231 | } 232 | 233 | REQ_FUN_ARG(1, callback); 234 | 235 | MapfileBaton *baton = new MapfileBaton(); 236 | baton->request.data = baton; 237 | baton->map = NULL; 238 | baton->callback = Persistent::New(callback); 239 | baton->error = NULL; 240 | baton->mapfile = mapfile; 241 | 242 | // Run in a different thread. Note there is *no* `FromStringAfter`: 243 | // `FromFileAfter` is used instead. 244 | uv_queue_work(uv_default_loop(), 245 | &baton->request, 246 | FromStringWork, 247 | (uv_after_work_cb) FromFileAfter); 248 | 249 | return Undefined(); 250 | } 251 | 252 | /** 253 | * @details This is called by `FromStringAsync` and runs in a different thread 254 | * to that function. 255 | * 256 | * @param req The asynchronous libuv request. 257 | */ 258 | void Map::FromStringWork(uv_work_t *req) { 259 | /* No HandleScope! This is run in a separate thread: *No* contact 260 | should be made with the Node/V8 world here. */ 261 | 262 | MapfileBaton *baton = static_cast(req->data); 263 | 264 | baton->map = msLoadMapFromString(const_cast(baton->mapfile.c_str()), NULL); 265 | if (!baton->map) { 266 | errorObj *error = msGetErrorObj(); 267 | if (!error) { 268 | baton->error = new MapserverError("Could not load mapfile", "Map::FromStringWork()"); 269 | } else { 270 | baton->error = new MapserverError(error); 271 | } 272 | } 273 | 274 | msResetErrorList(); 275 | return; 276 | } 277 | 278 | /** 279 | * @details This is the asynchronous method used to generate a mapserv 280 | * response. The response is a javascript object literal with the following 281 | * properties: 282 | * 283 | * - `data`: a `Buffer` object representing the response body 284 | * - `headers`: the HTTP headers as an object literal 285 | * 286 | * `args` should contain the following parameters: 287 | * 288 | * @param env A javascript object literal containing the CGI environment 289 | * variables which will direct the mapserv response. 290 | * 291 | * @param body The optional string or buffer object representing the body of an 292 | * HTTP request. 293 | * 294 | * @param callback A function that is called on error or when the 295 | * resource has been created. It should have the signature 296 | * `callback(err, resource)`. 297 | */ 298 | Handle Map::MapservAsync(const Arguments& args) { 299 | HandleScope scope; 300 | string body; 301 | Local env; 302 | Local callback; 303 | 304 | switch (args.Length()) { 305 | case 2: 306 | ASSIGN_OBJ_ARG(0, env); 307 | ASSIGN_FUN_ARG(1, callback); 308 | break; 309 | case 3: 310 | ASSIGN_OBJ_ARG(0, env); 311 | 312 | if (args[1]->IsString()) { 313 | body = *String::Utf8Value(args[1]->ToString()); 314 | } else if (Buffer::HasInstance(args[1])) { 315 | Local buffer = args[1]->ToObject(); 316 | body = string(Buffer::Data(buffer), Buffer::Length(buffer)); 317 | } else if (!args[1]->IsNull() && !args[1]->IsUndefined()) { 318 | THROW_CSTR_ERROR(TypeError, "Argument 1 must be one of a string; buffer; null; undefined"); 319 | } 320 | 321 | ASSIGN_FUN_ARG(2, callback); 322 | break; 323 | default: 324 | THROW_CSTR_ERROR(Error, "usage: Map.mapserv(env, [body], callback)"); 325 | } 326 | 327 | Map* self = ObjectWrap::Unwrap(args.This()); 328 | MapBaton *baton = new MapBaton(); 329 | 330 | baton->request.data = baton; 331 | baton->self = self; 332 | baton->callback = Persistent::New(callback); 333 | baton->map = self->map; 334 | baton->error = NULL; 335 | baton->body = body; 336 | 337 | // Convert the environment object to a `std::map` 338 | const Local properties = env->GetPropertyNames(); 339 | const uint32_t length = properties->Length(); 340 | for (uint32_t i = 0; i < length; ++i) { 341 | const Local key = properties->Get(i); 342 | const Local value = env->Get(key); 343 | baton->env.insert(pair(string(*String::Utf8Value(key->ToString())), 344 | string(*String::Utf8Value(value->ToString()))) 345 | ); 346 | } 347 | 348 | self->Ref(); // increment reference count so map is not garbage collected 349 | 350 | uv_queue_work(uv_default_loop(), 351 | &baton->request, 352 | MapservWork, 353 | (uv_after_work_cb) MapservAfter); 354 | 355 | return Undefined(); 356 | } 357 | 358 | /** 359 | * @details This is called by `MapservAsync` and runs in a different thread to 360 | * that function. It performs the actual work of interacting with 361 | * mapserver. The code is based on the logic found in the `mapserv` program but 362 | * the output is instead buffered using the mapserver output buffering 363 | * functionality: it can then be captured and passed back to the client. 364 | * 365 | * @param req The asynchronous libuv request. 366 | */ 367 | void Map::MapservWork(uv_work_t *req) { 368 | /* No HandleScope! This is run in a separate thread: *No* contact 369 | should be made with the Node/V8 world here. */ 370 | 371 | MapBaton *baton = static_cast(req->data); 372 | mapservObj* mapserv = NULL; 373 | bool reportError = false; // flag an error as worthy of reporting 374 | 375 | if (msDebugInitFromEnv() != MS_SUCCESS) { 376 | reportError = true; 377 | goto handle_error; 378 | } 379 | 380 | mapserv = msAllocMapServObj(); 381 | 382 | msIO_installStdinFromBuffer(); // required to catch POSTS without data 383 | msIO_installStdoutToBuffer(); // required to capture mapserver output 384 | 385 | // load the CGI parameters from the environment object 386 | mapserv->request->NumParams = wrap_loadParams(mapserv->request, 387 | GetEnv, 388 | const_cast(baton->body.c_str()), 389 | baton->body.length(), 390 | static_cast(&(baton->env))); 391 | if( mapserv->request->NumParams == -1 ) { 392 | // no errors are generated by default but messages are output instead 393 | msSetError( MS_MISCERR, "No request parameters loaded", 394 | "Map::MapservWork" ); 395 | reportError = true; 396 | goto get_output; 397 | } 398 | 399 | // Copy the map into the mapservObj for this request 400 | if(!LoadMap(mapserv, baton->map)) { 401 | reportError = true; 402 | goto get_output; 403 | } 404 | 405 | // Execute the request 406 | if(msCGIDispatchRequest(mapserv) != MS_SUCCESS) { 407 | reportError = true; 408 | goto get_output; 409 | } 410 | 411 | get_output: 412 | // Get the content type. If headers other than content-type need to be 413 | // retrieved it may be best to use something along the lines of 414 | // . 415 | baton->content_type = msIO_stripStdoutBufferContentType(); 416 | msIO_stripStdoutBufferContentHeaders(); 417 | 418 | // Get the buffered output 419 | baton->buffer = msIO_getStdoutBufferBytes(); 420 | 421 | handle_error: 422 | // handle any unhandled errors 423 | errorObj *error = msGetErrorObj(); 424 | if (error && error->code != MS_NOERR) { 425 | // report the error if requested 426 | if (reportError) { 427 | baton->error = new MapserverError(error); 428 | } 429 | msResetErrorList(); // clear all handled errors 430 | } 431 | 432 | // clean up 433 | msFreeMapServObj(mapserv); 434 | msIO_resetHandlers(); 435 | msDebugCleanup(); 436 | return; 437 | } 438 | 439 | /** 440 | * @details This is set by `MapservAsync` to run after `MapservWork` has 441 | * finished, being passed the response generated by the latter and running in 442 | * the same thread as the former. It formats the mapserv response into 443 | * javascript datatypes and returns them via the original callback. 444 | * 445 | * @param req The asynchronous libuv request. 446 | */ 447 | void Map::MapservAfter(uv_work_t *req) { 448 | HandleScope scope; 449 | 450 | MapBaton *baton = static_cast(req->data); 451 | Map *self = baton->self; 452 | gdBuffer *buffer = baton->buffer; 453 | 454 | Handle argv[2]; 455 | 456 | if (baton->error) { 457 | argv[0] = baton->error->toV8Error(); 458 | delete baton->error; // we've finished with it 459 | } else { 460 | argv[0] = Undefined(); 461 | } 462 | 463 | // convert the http_response to a javascript object 464 | Local result = Object::New(); 465 | 466 | // Add the content-type to the headers object. This object mirrors the 467 | // HTTP headers structure and creates an API that allows for the addition 468 | // of other headers in the future. 469 | Local headers = Object::New(); 470 | if (baton->content_type) { 471 | Local values = Array::New(1); 472 | 473 | values->Set(0, String::New(baton->content_type)); 474 | headers->Set(String::New("Content-Type"), values); 475 | } 476 | result->Set(headers_symbol, headers); 477 | 478 | // set the response data as a Node Buffer object. This is zero-copied from 479 | // mapserver and free'd when the buffer is garbage collected. 480 | if (buffer && buffer->data) { 481 | result->Set(data_symbol, 482 | Buffer::New((char *)buffer->data, buffer->size, FreeBuffer, NULL)->handle_); 483 | 484 | // add the content-length header 485 | Local values = Array::New(1); 486 | values->Set(0, Uint32::New(buffer->size)); 487 | headers->Set(String::New("Content-Length"), values); 488 | } 489 | 490 | argv[1] = result; 491 | 492 | // pass the results to the user specified callback function 493 | TryCatch try_catch; 494 | baton->callback->Call(Context::GetCurrent()->Global(), 2, argv); 495 | if (try_catch.HasCaught()) { 496 | FatalException(try_catch); 497 | } 498 | 499 | // clean up 500 | if (buffer) { 501 | delete baton->buffer; 502 | baton->buffer = NULL; 503 | } 504 | 505 | if (baton->content_type) { 506 | msFree(baton->content_type); 507 | } 508 | 509 | baton->env.clear(); 510 | baton->callback.Dispose(); 511 | self->Unref(); // decrement the map reference so it can be garbage collected 512 | delete baton; 513 | return; 514 | } 515 | 516 | /** 517 | * @details This is a callback passed to the mapserver `loadParams` function. 518 | * It is called whenever mapserver needs to retrieve a CGI environment 519 | * variable. The environment variables are retrieved from the `env` javascript 520 | * object literal that the client passed into the `mapserv` method. 521 | */ 522 | char* Map::GetEnv(const char *name, void* thread_context) { 523 | std::map *env = static_cast *>(thread_context); 524 | std::map::iterator it = env->find(std::string(name)); 525 | 526 | if (it != env->end()) { 527 | return const_cast(it->second.c_str()); 528 | } else { 529 | return NULL; 530 | } 531 | } 532 | 533 | /** 534 | * @details This code is largely copied from the PHP MapScript module. It is 535 | * used to retrieve the buffered mapserver STDOUT data. 536 | */ 537 | Map::gdBuffer* Map::msIO_getStdoutBufferBytes(void) { 538 | msIOContext *ctx = msIO_getHandler( (FILE *) "stdout" ); 539 | msIOBuffer *buf; 540 | gdBuffer *gdBuf; 541 | 542 | if( ctx == NULL || ctx->write_channel == MS_FALSE 543 | || strcmp(ctx->label,"buffer") != 0 ) 544 | { 545 | msSetError( MS_MISCERR, "Can't identify msIO buffer.", 546 | "Map::msIO_getStdoutBufferBytes" ); 547 | return NULL; 548 | } 549 | 550 | buf = (msIOBuffer *) ctx->cbData; 551 | 552 | gdBuf = new gdBuffer(); 553 | gdBuf->data = buf->data; 554 | gdBuf->size = buf->data_offset; 555 | gdBuf->owns_data = MS_TRUE; 556 | 557 | /* we are seizing ownership of the buffer contents */ 558 | buf->data_offset = 0; 559 | buf->data_len = 0; 560 | buf->data = NULL; 561 | 562 | return gdBuf; 563 | } 564 | 565 | /** 566 | * @details This creates a `mapObj` primed for use with a `mapservObj`. 567 | */ 568 | mapObj* Map::LoadMap(mapservObj *mapserv, mapObj *src) { 569 | mapObj* map = msNewMapObj(); 570 | 571 | if (!map) { 572 | return NULL; 573 | } 574 | 575 | // updating alters the state of the map, so work on a copy 576 | if (msCopyMap(map, src) != MS_SUCCESS) { 577 | msFreeMap(map); 578 | return NULL; 579 | } 580 | mapserv->map = map; 581 | 582 | // delegate to the helper function 583 | if (updateMap(mapserv, map) != MS_SUCCESS) { 584 | msFreeMap(map); 585 | mapserv->map = NULL; 586 | return NULL; 587 | } 588 | 589 | return map; 590 | } 591 | -------------------------------------------------------------------------------- /src/map.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | #ifndef __NODE_MAPSERV_MAP_H__ 29 | #define __NODE_MAPSERV_MAP_H__ 30 | 31 | /** 32 | * @file map.hpp 33 | * @brief This declares the primary `Map` class. 34 | */ 35 | 36 | /// The node-mapserv version string 37 | #define NODE_MAPSERV_VERSION "0.1.5" 38 | 39 | // Standard headers 40 | #include 41 | #include 42 | 43 | // Node headers 44 | #include 45 | #include 46 | #include 47 | 48 | // Mapserver headers 49 | #include "mapserver.h" 50 | extern "C" { 51 | #include "mapserv.h" 52 | } 53 | 54 | // Node-mapserv headers 55 | #include "error.hpp" 56 | 57 | /// Throw an exception generated from a `char` string 58 | #define THROW_CSTR_ERROR(TYPE, STR) \ 59 | return ThrowException(Exception::TYPE(String::New(STR))); 60 | 61 | /// Assign to a local V8 `Function` variable from the function arguments 62 | #define ASSIGN_FUN_ARG(I, VAR) \ 63 | if (args.Length() <= (I) || !args[I]->IsFunction()) \ 64 | THROW_CSTR_ERROR(TypeError, \ 65 | "Argument " #I " must be a function"); \ 66 | VAR = Local::Cast(args[I]); 67 | 68 | /// Create a local V8 `Function` variable from the function arguments 69 | #define REQ_FUN_ARG(I, VAR) \ 70 | Local VAR; \ 71 | ASSIGN_FUN_ARG(I, VAR); 72 | 73 | /// Create an `UTF8` V8 string variable from the function arguments 74 | #define REQ_STR_ARG(I, VAR) \ 75 | if (args.Length() <= (I) || !args[I]->IsString()) \ 76 | THROW_CSTR_ERROR(TypeError, \ 77 | "Argument " #I " must be a string"); \ 78 | String::Utf8Value VAR(args[I]->ToString()); 79 | 80 | /// Create a local V8 `External` variable from the function arguments 81 | #define REQ_EXT_ARG(I, VAR) \ 82 | if (args.Length() <= (I) || !args[I]->IsExternal()) \ 83 | THROW_CSTR_ERROR(TypeError, \ 84 | "Argument " #I " invalid"); \ 85 | Local VAR = Local::Cast(args[I]); 86 | 87 | /// Assign to a local V8 `Object` variable from the function arguments 88 | #define ASSIGN_OBJ_ARG(I, VAR) \ 89 | if (args.Length() <= (I) || !args[I]->IsObject()) \ 90 | THROW_CSTR_ERROR(TypeError, \ 91 | "Argument " #I " must be an object"); \ 92 | VAR = Local(args[I]->ToObject()); 93 | 94 | /// Create a local V8 `Object` variable from the function arguments 95 | #define REQ_OBJ_ARG(I, VAR) \ 96 | Local VAR; \ 97 | ASSIGN_OBJ_ARG(I, VAR); 98 | 99 | using namespace std; 100 | using namespace node; 101 | using namespace v8; 102 | 103 | /** 104 | * @brief The primary class in the module representing a mapserver Map 105 | * 106 | * This class wraps an instance of a mapserver `mapObj`. The `FromFile` and 107 | * `FromString` class constructor methods enable the map to be instantiated 108 | * from a mapfile using the non-blocking callback paradigm where the work is 109 | * carried out in a separate thread. 110 | * 111 | * Once instantiated the `mapserv` method is exposed to javascript clients 112 | * which allow calls to be made to the underlying mapserv functionality. Again 113 | * this is performed asynchronously to prevent blocking of the main Node.js 114 | * event loop. 115 | */ 116 | class Map: ObjectWrap { 117 | public: 118 | 119 | /// Initialise the class 120 | static void Init(Handle target); 121 | 122 | /// Instantiate a `Map` instance from a mapfile 123 | static Handle FromFileAsync(const Arguments& args); 124 | 125 | /// Instantiate a `Map` instance from a map string 126 | static Handle FromStringAsync(const Arguments& args); 127 | 128 | /// Wrap the `mapserv` CGI functionality 129 | static Handle MapservAsync(const Arguments& args); 130 | 131 | private: 132 | 133 | /// The function template for creating new `Map` instances. 134 | static Persistent map_template; 135 | 136 | /// The string "data" 137 | static Persistent data_symbol; 138 | /// The string "headers" 139 | static Persistent headers_symbol; 140 | 141 | /// The underlying mapserver data structure that the class wraps 142 | mapObj *map; 143 | 144 | /// The structure used when performing asynchronous operations 145 | struct Baton { 146 | /// The asynchronous request 147 | uv_work_t request; 148 | /// The function executed upon request completion 149 | Persistent callback; 150 | /// A message set when the request fails 151 | MapserverError *error; 152 | 153 | /// The mapObj generated by the request 154 | mapObj *map; 155 | }; 156 | 157 | /// The context used by asynchronous constructor methods 158 | struct MapfileBaton: Baton { 159 | /// The mapfile source from which to create a mapObj 160 | string mapfile; 161 | }; 162 | 163 | /// The structure containing mapserver output 164 | struct gdBuffer { 165 | unsigned char *data; 166 | int size; 167 | int owns_data; 168 | }; 169 | 170 | /// Asynchronous context used in method calls 171 | struct MapBaton: Baton { 172 | /// The `Map` object from which the call originated 173 | Map *self; 174 | /// The request body 175 | string body; 176 | /// The Content-Type header 177 | char *content_type; 178 | /// The buffer containing the mapserv response 179 | gdBuffer *buffer; 180 | /// The CGI environment variables 181 | std::map env; 182 | }; 183 | 184 | /// Instantiate a Map from a mapObj 185 | Map(mapObj *map) : 186 | map(map) 187 | { 188 | // should throw an error here if !map 189 | } 190 | 191 | /// Clear up the mapObj 192 | ~Map() { 193 | if (map) { 194 | msFreeMap(map); 195 | } 196 | } 197 | 198 | /// Instantiate an object 199 | static Handle New(const Arguments& args); 200 | 201 | /// Asynchronously create a `mapObj` from a file path 202 | static void FromFileWork(uv_work_t *req); 203 | 204 | /// Return the new `Map` instance to the caller 205 | static void FromFileAfter(uv_work_t *req); 206 | 207 | /// Asynchronouysly create a `mapObj` from a mapfile string 208 | static void FromStringWork(uv_work_t *req); 209 | 210 | /// Return the new `Map` instance to the caller 211 | static void FromStringAfter(uv_work_t *req); 212 | 213 | /// Asynchronously execute a mapserv request 214 | static void MapservWork(uv_work_t *req); 215 | 216 | /// Return the mapserv response to the caller 217 | static void MapservAfter(uv_work_t *req); 218 | 219 | /// Get a CGI environment variable 220 | static char* GetEnv(const char *name, void* thread_context); 221 | 222 | /// Get the mapserver output as a buffer 223 | static gdBuffer* msIO_getStdoutBufferBytes(void); 224 | 225 | /// Create a map object for use in a mapserv request 226 | static mapObj* LoadMap(mapservObj *mapserv, mapObj *src); 227 | 228 | /// Free data zero-copied to a `Buffer` 229 | static void FreeBuffer(char *data, void *hint) { 230 | msFree(data); 231 | data = NULL; 232 | } 233 | }; 234 | 235 | /** 236 | * @def REQ_STR_ARG(I, VAR) 237 | * 238 | * This throws a `TypeError` if the argument is of the wrong type. 239 | * 240 | * @param I A zero indexed integer representing the variable to 241 | * extract in the `args` array. 242 | * @param VAR The symbol name of the variable to be created. 243 | 244 | * @def ASSIGN_FUN_ARG(I, VAR) 245 | * 246 | * This throws a `TypeError` if the argument is of the wrong type. 247 | * 248 | * @param I A zero indexed integer representing the variable to 249 | * extract in the `args` array. 250 | * @param VAR The symbol name of the variable to be created. 251 | 252 | * @def REQ_FUN_ARG(I, VAR) 253 | * 254 | * This defines a `Local` variable and then delegates to the 255 | * `ASSIGN_FUN_ARG` macro. 256 | 257 | * @def REQ_EXT_ARG(I, VAR) 258 | * 259 | * This throws a `TypeError` if the argument is of the wrong type. 260 | * 261 | * @param I A zero indexed integer representing the variable to 262 | * extract in the `args` array. 263 | * @param VAR The symbol name of the variable to be created. 264 | 265 | * @def ASSIGN_OBJ_ARG(I, VAR) 266 | * 267 | * This throws a `TypeError` if the argument is of the wrong type. 268 | * 269 | * @param I A zero indexed integer representing the variable to 270 | * extract in the `args` array. 271 | * @param VAR The symbol name of the variable to be created. 272 | 273 | * @def REQ_OBJ_ARG(I, VAR) 274 | * 275 | * This defines a `Local` variable and then delegates to the 276 | * `ASSIGN_OBJ_ARG` macro. 277 | 278 | * @def THROW_CSTR_ERROR(TYPE, STR) 279 | * 280 | * This returns from the containing function throwing an error of a 281 | * specific type. 282 | * 283 | * @param TYPE The symbol name of the exception to be thrown. 284 | * @param STR The `char` string to set as the error message. 285 | 286 | * @struct Map::gdBuffer 287 | * 288 | * This structure is used to capture data output from mapserver. It is 289 | * inspired by code in the PHP Mapserver MapScript module. 290 | 291 | * @struct Map::Baton 292 | * 293 | * This represents a standard interface used to transfer data 294 | * structures between threads when using libuv asynchronously. See 295 | * for details. 296 | */ 297 | 298 | #endif /* __NODE_MAPSERV_MAP_H__ */ 299 | -------------------------------------------------------------------------------- /src/node-mapserv.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * @file node-mapserv.cpp 30 | * @brief This registers and initialises the module with Node. 31 | * 32 | * @mainpage Node %Mapserv 33 | * 34 | * This represents the C++ bindings that are part of a module 35 | * providing access from Node.js to 36 | * the functionality provided by the Mapserver mapserv CGI program. 38 | * 39 | * Map is the primary C++ class which wraps the underlying MapServer 40 | * `mapObj` data structure. A javascript shim is used in the package 41 | * to expose this class to javascript clients. 42 | * 43 | * See the `README.md` file distributed with the package for further 44 | * details. 45 | */ 46 | 47 | #include 48 | #include "map.hpp" 49 | #include "error.hpp" 50 | 51 | /** Clean up at module exit. 52 | * 53 | * This performs housekeeping duties when the module is 54 | * unloaded. 55 | * 56 | * The function signature is designed so that a pointer to the 57 | * function can be passed to the `atexit` function. 58 | */ 59 | static void cleanup(void) { 60 | msCleanup(0); 61 | } 62 | 63 | /** Initialise the module. 64 | * 65 | * This is the entry point to the module called by Node and as such it 66 | * performs various initialisation functions: 67 | * 68 | * - Sets up the `libmapserver` library 69 | * - Initialises the `Map` class 70 | * - Ensures `libmapserver` has been compiled with thread support 71 | * 72 | * @param target The object representing the module. 73 | */ 74 | extern "C" { 75 | static void init (Handle target) { 76 | 77 | // initialise mapserver 78 | if (msSetup() != MS_SUCCESS ) { 79 | errorObj *error = msGetErrorObj(); 80 | 81 | if(!error || error->code == MS_NOERR || error->isreported) { 82 | // either we have no error, or it was already reported by other means 83 | ThrowException(Exception::Error(String::New("Mapserver setup failed"))); 84 | } else { 85 | ThrowException(Exception::Error(String::New(error->message))); 86 | } 87 | msResetErrorList(); 88 | msCleanup(0); 89 | return; 90 | } 91 | 92 | // a runtime check to ensure we have a mapserver that supports threads 93 | if (!strstr(msGetVersion(), "SUPPORTS=THREADS")) { 94 | ThrowException(Exception::Error(String::New("Mapserver is not compiled with support for threads"))); 95 | msCleanup(0); 96 | return; 97 | } 98 | 99 | // initialise module components 100 | Map::Init(target); 101 | MapserverError::Init(); 102 | 103 | // versioning information 104 | Local versions = Object::New(); 105 | versions->Set(String::NewSymbol("node_mapserv"), String::New(NODE_MAPSERV_VERSION)); 106 | versions->Set(String::NewSymbol("mapserver"), String::New(MS_VERSION)); 107 | #ifdef MS_VERSION_NUM 108 | versions->Set(String::NewSymbol("mapserver_numeric"), Integer::New(msGetVersionInt())); 109 | #endif 110 | versions->Set(String::NewSymbol("mapserver_details"), String::New(msGetVersion())); 111 | target->Set(String::NewSymbol("versions"), versions); 112 | 113 | // Ensure Mapserver is cleaned up on receipt of various signals. 114 | // Importantly this ensures that `MS_ERRORFILE` is properly closed (if 115 | // set). 116 | signal(SIGHUP, msCleanup); 117 | signal(SIGINT, msCleanup); 118 | #ifdef SIGQUIT 119 | signal(SIGQUIT, msCleanup); 120 | #endif 121 | signal(SIGTERM, msCleanup); 122 | #ifdef SIGUSR1 123 | signal(SIGUSR1, msCleanup); 124 | #endif 125 | #ifdef SIGUSR2 126 | signal(SIGUSR2, msCleanup); 127 | #endif 128 | atexit(cleanup); // clean up on normal exit 129 | } 130 | } 131 | 132 | /// Register the module 133 | NODE_MODULE(bindings, init) 134 | -------------------------------------------------------------------------------- /src/node-mapservutil.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * @file node-mapservutil.c 30 | * @brief This defines C utility functions used by the `Map` class. 31 | */ 32 | 33 | #include "node-mapservutil.h" 34 | 35 | /** 36 | * A utility function copied verbatim from `mapservutil.c` 37 | */ 38 | static void setClassGroup(layerObj *layer, char *classgroup) 39 | { 40 | int i; 41 | 42 | if(!layer || !classgroup) return; 43 | 44 | for(i=0; inumclasses; i++) { 45 | if(layer->class[i]->group && strcmp(layer->class[i]->group, classgroup) == 0) { 46 | msFree(layer->classgroup); 47 | layer->classgroup = msStrdup(classgroup); 48 | return; /* bail */ 49 | } 50 | } 51 | } 52 | 53 | int wrap_loadParams(cgiRequestObj *request, char* (*getenv2)(const char*, void* thread_context), 54 | char *raw_post_data, ms_uint32 raw_post_data_length, void* thread_context) { 55 | return loadParams(request, getenv2, raw_post_data, raw_post_data_length, thread_context); 56 | } 57 | 58 | int updateMap(mapservObj *mapserv, mapObj *map) { 59 | int i, j; 60 | 61 | if(!msLookupHashTable(&(map->web.validation), "immutable")) { 62 | /* check for any %variable% substitutions here, also do any map_ changes, we do this here so WMS/WFS */ 63 | /* services can take advantage of these "vendor specific" extensions */ 64 | for(i=0; irequest->NumParams; i++) { 65 | /* 66 | ** a few CGI variables should be skipped altogether 67 | ** 68 | ** qstring: there is separate per layer validation for attribute queries and the substitution checks 69 | ** below conflict with that so we avoid it here 70 | */ 71 | if(strncasecmp(mapserv->request->ParamNames[i],"qstring",7) == 0) continue; 72 | 73 | /* check to see if there are any additions to the mapfile */ 74 | if(strncasecmp(mapserv->request->ParamNames[i],"map_",4) == 0 || strncasecmp(mapserv->request->ParamNames[i],"map.",4) == 0) { 75 | msAcquireLock( TLOCK_PARSER ); 76 | if(msUpdateMapFromURL(map, mapserv->request->ParamNames[i], mapserv->request->ParamValues[i]) != MS_SUCCESS) { 77 | msReleaseLock( TLOCK_PARSER ); 78 | return MS_FAILURE; 79 | } 80 | msReleaseLock( TLOCK_PARSER ); 81 | 82 | continue; 83 | } 84 | 85 | if(strncasecmp(mapserv->request->ParamNames[i],"classgroup",10) == 0) { /* #4207 */ 86 | for(j=0; jnumlayers; j++) { 87 | setClassGroup(GET_LAYER(map, j), mapserv->request->ParamValues[i]); 88 | } 89 | continue; 90 | } 91 | } 92 | 93 | msApplySubstitutions(map, mapserv->request->ParamNames, mapserv->request->ParamValues, mapserv->request->NumParams); 94 | msApplyDefaultSubstitutions(map); 95 | 96 | /* check to see if a ogc map context is passed as argument. if there */ 97 | /* is one load it */ 98 | 99 | for(i=0; irequest->NumParams; i++) { 100 | if(strcasecmp(mapserv->request->ParamNames[i],"context") == 0) { 101 | if(mapserv->request->ParamValues[i] && strlen(mapserv->request->ParamValues[i]) > 0) { 102 | if(strncasecmp(mapserv->request->ParamValues[i],"http",4) == 0) { 103 | if(msGetConfigOption(map, "CGI_CONTEXT_URL")) 104 | msLoadMapContextURL(map, mapserv->request->ParamValues[i], MS_FALSE); 105 | } else 106 | msLoadMapContext(map, mapserv->request->ParamValues[i], MS_FALSE); 107 | } 108 | } 109 | } 110 | } 111 | 112 | /* 113 | * RFC-42 HTTP Cookie Forwarding 114 | * Here we set the http_cookie_data metadata to handle the 115 | * HTTP Cookie Forwarding. The content of this metadata is the cookie 116 | * content. In the future, this metadata will probably be replaced 117 | * by an object that is part of the mapObject that would contain 118 | * information on the application status (such as cookie). 119 | */ 120 | if( mapserv->request->httpcookiedata != NULL ) { 121 | msInsertHashTable( &(map->web.metadata), "http_cookie_data", 122 | mapserv->request->httpcookiedata ); 123 | } 124 | 125 | return MS_SUCCESS; 126 | } 127 | -------------------------------------------------------------------------------- /src/node-mapservutil.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | #ifndef NODE_MAPSERVUTIL_H 29 | #define NODE_MAPSERVUTIL_H 30 | 31 | /** 32 | * @file node-mapservutil.h 33 | * @brief This declares C utility functions used by the `Map` class. 34 | * 35 | * This is C code which encapsulates mapserver code that is not exposed by 36 | * libmapserver and must therefore be duplicated. It also includes code which 37 | * is made available in libmapserver but which is not accessible to C++ 38 | * (i.e. the rest of the module) for linkage reasons; wrapping the code here 39 | * enables it to be used elsewhere by C++. 40 | */ 41 | 42 | #include "mapserver.h" 43 | #include "mapthread.h" 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif 48 | 49 | /* `mapserv.h` is not wrapped with `extern "C"` */ 50 | #include "mapserv.h" 51 | 52 | /** 53 | * Wrap the mapserver `loadParams` function 54 | * 55 | * This is necessary because `loadParams` is not wrapped in an `extern "C"` 56 | * which causes linker problems when compiling with C++ due to name mangling. 57 | */ 58 | int wrap_loadParams(cgiRequestObj *request, char* (*getenv2)(const char*, void* thread_context), 59 | char *raw_post_data, ms_uint32 raw_post_data_length, void* thread_context); 60 | 61 | /** 62 | * Perform mapserv request map initialisation (e.g. variable substitutions) 63 | * 64 | * This function is copied verbatim from the latter part of `msCGILoadMap()` 65 | * with the exception of adding the mutex around `msUpdateMapFromURL()` and 66 | * altering the return type. 67 | */ 68 | int updateMap(mapservObj *mapserv, mapObj *map); 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | 74 | #endif /* NODE_MAPSERVUTIL_H */ 75 | -------------------------------------------------------------------------------- /test/include-error.map: -------------------------------------------------------------------------------- 1 | # Trigger an error with a non-existent include 2 | MAP 3 | NAME "include-mapfile" 4 | EXTENT 0 0 500 500 5 | SIZE 250 250 6 | 7 | INCLUDE "non-existent-file.map" 8 | END 9 | -------------------------------------------------------------------------------- /test/invalid.map: -------------------------------------------------------------------------------- 1 | # A valid mapfile used for testing 2 | MAP 3 | NAME valid 4 | STATUS ON 5 | EXTENT 0 0 4000 3000 6 | SIZE 400 300 7 | IMAGECOLOR 200 255 255 8 | 9 | WEB 10 | IMAGEPATH "./" 11 | IMAGEURL "/tmp/" 12 | # Should have an `END` 13 | 14 | LAYER 15 | NAME "credits" 16 | STATUS DEFAULT 17 | TRANSFORM FALSE 18 | TYPE ANNOTATION 19 | FEATURE 20 | POINTS 21 | 200 150 22 | END 23 | TEXT 'Hello world. Mapserver rocks.' 24 | END 25 | CLASS 26 | LABEL 27 | TYPE BITMAP 28 | COLOR 0 0 0 29 | END 30 | END 31 | END 32 | 33 | END -------------------------------------------------------------------------------- /test/mapserv-test.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * - Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * - Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | *****************************************************************************/ 27 | 28 | /** 29 | * Mapserv test harness 30 | * 31 | * This test harness uses 'vows' (http://vowsjs.org) to exercise the 32 | * Mapserv API. It can be run directly using vows: 33 | * 34 | * vows --spec test/mapserv-test.js 35 | * 36 | * or indirectly using 'npm': 37 | * 38 | * npm test 39 | */ 40 | 41 | var vows = require('vows'), 42 | assert = require('assert'), 43 | fs = require('fs'), 44 | path = require('path'), 45 | buffer = require('buffer'), 46 | mapserv; 47 | 48 | // Load node-mapserv. We cause a failure the first time to ensure that certain 49 | // code branches in module initialisation are run. 50 | try { 51 | // an invalid file path (checked in `msDebugInitFromEnv()`)... 52 | process.env['MS_ERRORFILE'] = '/oops'; 53 | // ...causing module initialisation to fail 54 | mapserv = require('../lib/mapserv'); 55 | } catch (e) { 56 | delete process.env['MS_ERRORFILE']; // clear the problem 57 | mapserv = require('../lib/mapserv'); // should be ok 58 | } 59 | 60 | // Exercise cleanup code by calling the V8 garbage collector. This is only 61 | // available if node has been called with the `--expose-gc` option. 62 | if (typeof(global.gc) == 'function') { 63 | process.on('exit', function onExit() { 64 | global.gc(); 65 | }); 66 | } 67 | 68 | // Dummy request object used for testing `createCGIEnvironment` 69 | function dummyRequest() { 70 | var req = { 71 | url: 'http://example.com/foo/bar?key=value', 72 | httpVersion: '1.1', 73 | method: 'POST', 74 | connection: { 75 | remoteAddress: '127.0.0.0' 76 | }, 77 | headers: { 78 | 'content-length': '22', 79 | 'content-type': 'application/x-www-form-urlencoded', 80 | host: 'localhost:80', 81 | accept: '*/*', 82 | authorization: 'Basic WovZwuYYN2mWNO5=' 83 | } 84 | }; 85 | 86 | return req; 87 | } 88 | 89 | // Ensure a Mapserver error has the expected interface 90 | function assertMapserverError(expected, actual, stack) { 91 | assert.instanceOf(actual, Error); 92 | assert.equal(actual.name, 'MapserverError'); 93 | assert.include(actual, 'code'); 94 | assert.include(actual, 'category'); 95 | assert.include(actual, 'routine'); 96 | assert.include(actual, 'isReported'); 97 | if (stack || stack === undefined) { 98 | assert.include(actual, 'errorStack'); 99 | assert.isArray(actual.errorStack); 100 | if (typeof stack == 'number') { 101 | assert.lengthOf(actual.errorStack, stack); 102 | } 103 | } 104 | assert.isNumber(actual.code); 105 | assert.isString(actual.routine); 106 | assert.strictEqual(actual.routine.length > 1, true); 107 | assert.isString(actual.category); 108 | assert.strictEqual(actual.category.length > 1, true); 109 | assert.isBoolean(actual.isReported); 110 | assert.equal(expected, actual.message); 111 | } 112 | 113 | vows.describe('mapserv').addBatch({ 114 | // Ensure the module has the expected interface 115 | 116 | 'The mapserv module': { 117 | topic: mapserv, 118 | 119 | 'should have a `Map` object': { 120 | topic: function (mapserv) { 121 | return mapserv.Map; 122 | }, 123 | 'which is a function': function (Map) { 124 | assert.isFunction(Map); 125 | }, 126 | 'which has the property `FromFile`': { 127 | topic: function (Map) { 128 | return Map.FromFile; 129 | }, 130 | 'which is a factory method': function (FromFile) { 131 | assert.isFunction(FromFile); 132 | } 133 | }, 134 | 'which has the property `FromString`': { 135 | topic: function (Map) { 136 | return Map.FromString; 137 | }, 138 | 'which is a factory method': function (FromString) { 139 | assert.isFunction(FromString); 140 | } 141 | }, 142 | 'which has the prototype property `mapserv`': { 143 | topic: function (Mapserv) { 144 | return Mapserv.prototype.mapserv || false; 145 | }, 146 | 'which is a method': function (mapserv) { 147 | assert.isFunction(mapserv); 148 | } 149 | }, 150 | 'which acts as a constructor': { 151 | 'requiring at least one argument': function (Map) { 152 | var err; 153 | try { 154 | err = new Map(); 155 | } catch (e) { 156 | err = e; 157 | } 158 | assert.instanceOf(err, Error); 159 | assert.equal(err.message, 'usage: new Map(mapObj)'); 160 | }, 161 | 'which cannot be instantiated from javascript': function (Map) { 162 | var err; 163 | try { 164 | err = new Map('test'); 165 | } catch (e) { 166 | err = e; 167 | } 168 | assert.instanceOf(err, Error); 169 | assert.equal(err.message, 'Argument 0 invalid'); 170 | }, 171 | 'which throws an error when called directly': function (Map) { 172 | var err; 173 | try { 174 | err = Map(); 175 | } catch (e) { 176 | err = e; 177 | } 178 | assert.instanceOf(err, Error); 179 | assert.equal(err.message, 'Map() is expected to be called as a constructor with the `new` keyword'); 180 | } 181 | } 182 | }, 183 | 184 | 'should have a `versions` property': { 185 | topic: function (mapserv) { 186 | return mapserv.versions; 187 | }, 188 | 'which is an object': function (versions) { 189 | assert.isObject(versions); 190 | }, 191 | 'which contains the `node-mapserv` version': { 192 | topic: function (versions) { 193 | return versions.node_mapserv; 194 | }, 195 | 'is a string': function (version) { 196 | assert.isString(version); 197 | assert.isTrue(version.length > 0); 198 | }, 199 | 'which is the same as that in `package.json`': function (version) { 200 | var contents = fs.readFileSync(path.join(__dirname, '..', 'package.json')), 201 | json = JSON.parse(contents), 202 | pversion = json.version; 203 | assert.equal(version, pversion); 204 | } 205 | }, 206 | 'which contains the `mapserver` version': { 207 | topic: function (versions) { 208 | return versions.mapserver; 209 | }, 210 | 'as a string': function (version) { 211 | assert.isString(version); 212 | assert.isTrue(version.length > 0); 213 | } 214 | }, 215 | 'which contains the `mapserver_numeric` version': { 216 | topic: function (versions) { 217 | return versions.mapserver_numeric; 218 | }, 219 | 'as an integer': function (version) { 220 | if (version) { // a hack to skip pre mapserver 6.3 221 | assert.isNumber(version); 222 | assert.isTrue(version >= 60200); // the minimum supported version 223 | } 224 | } 225 | }, 226 | 'which contains the `mapserver_details`': { 227 | topic: function (versions) { 228 | return versions.mapserver_details; 229 | }, 230 | 'as a string': function (version) { 231 | assert.isString(version); 232 | assert.isTrue(version.length > 0); 233 | } 234 | } 235 | }, 236 | 237 | 'should have a `createCGIEnvironment` property': { 238 | topic: function (mapserv) { 239 | return mapserv.createCGIEnvironment; 240 | }, 241 | 'which is a function': function (func) { 242 | assert.isFunction(func); 243 | } 244 | } 245 | } 246 | }).addBatch({ 247 | // Ensure `FromFile` has the expected interface 248 | 249 | '`Map.FromFile`': { 250 | topic: function () { 251 | return mapserv.Map.FromFile; 252 | }, 253 | 254 | 'works with two valid arguments': { 255 | topic: function (FromFile) { 256 | return typeof(FromFile('non-existent-file', function(err, map) { 257 | // do nothing 258 | })); 259 | }, 260 | 'returning undefined': function (retval) { 261 | assert.equal(retval, 'undefined'); 262 | } 263 | }, 264 | 'fails with one argument': { 265 | topic: function (FromFile) { 266 | try { 267 | return FromFile('first-arg'); 268 | } catch (e) { 269 | return e; 270 | } 271 | }, 272 | 'by throwing an error': function (err) { 273 | assert.instanceOf(err, Error); 274 | assert.equal(err.message, 'usage: Map.FromFile(mapfile, callback)'); 275 | } 276 | }, 277 | 'fails with three arguments': { 278 | topic: function (FromFile) { 279 | try { 280 | return FromFile('first-arg', 'second-arg', 'third-arg'); 281 | } catch (e) { 282 | return e; 283 | } 284 | }, 285 | 'by throwing an error': function (err) { 286 | assert.instanceOf(err, Error); 287 | assert.equal(err.message, 'usage: Map.FromFile(mapfile, callback)'); 288 | } 289 | }, 290 | 'requires a string for the first argument': { 291 | topic: function (FromFile) { 292 | try { 293 | return FromFile(42, function(err, map) { 294 | // do nothing 295 | }); 296 | } catch (e) { 297 | return e; 298 | } 299 | }, 300 | 'throwing an error otherwise': function (err) { 301 | assert.instanceOf(err, TypeError); 302 | assert.equal(err.message, 'Argument 0 must be a string'); 303 | } 304 | }, 305 | 'requires a function for the second argument': { 306 | topic: function (FromFile) { 307 | try { 308 | return FromFile('first-arg', 'second-arg'); 309 | } catch (e) { 310 | return e; 311 | } 312 | }, 313 | 'throwing an error otherwise': function (err) { 314 | assert.instanceOf(err, TypeError); 315 | assert.equal(err.message, 'Argument 1 must be a function'); 316 | } 317 | } 318 | } 319 | }).addBatch({ 320 | // Ensure `FromString` has the expected interface 321 | 322 | '`Map.FromString`': { 323 | topic: function () { 324 | return mapserv.Map.FromString; 325 | }, 326 | 327 | 'works with two valid arguments': { 328 | topic: function (FromString) { 329 | return typeof(FromString('map string', function(err, map) { 330 | // do nothing 331 | })); 332 | }, 333 | 'returning undefined': function (retval) { 334 | assert.equal(retval, 'undefined'); 335 | } 336 | }, 337 | 'fails with one argument': { 338 | topic: function (FromString) { 339 | try { 340 | return FromString('first-arg'); 341 | } catch (e) { 342 | return e; 343 | } 344 | }, 345 | 'by throwing an error': function (err) { 346 | assert.instanceOf(err, Error); 347 | assert.equal(err.message, 'usage: Map.FromString(mapfile, callback)'); 348 | } 349 | }, 350 | 'fails with three arguments': { 351 | topic: function (FromString) { 352 | try { 353 | return FromString('first-arg', 'second-arg', 'third-arg'); 354 | } catch (e) { 355 | return e; 356 | } 357 | }, 358 | 'by throwing an error': function (err) { 359 | assert.instanceOf(err, Error); 360 | assert.equal(err.message, 'usage: Map.FromString(mapfile, callback)'); 361 | } 362 | }, 363 | 'fails without a string or buffer for the first argument': { 364 | topic: function (FromString) { 365 | try { 366 | return FromString(42, function(err, map) { 367 | // do nothing 368 | }); 369 | } catch (e) { 370 | return e; 371 | } 372 | }, 373 | 'throwing an error otherwise': function (err) { 374 | assert.instanceOf(err, TypeError); 375 | assert.equal(err.message, 'Argument 0 must be a string or buffer'); 376 | } 377 | }, 378 | 'fails with an object that is not a buffer as the first argument': { 379 | topic: function (FromString) { 380 | try { 381 | return FromString(['not a buffer'], function(err, map) { 382 | // do nothing 383 | }); 384 | } catch (e) { 385 | return e; 386 | } 387 | }, 388 | 'throwing an error otherwise': function (err) { 389 | assert.instanceOf(err, TypeError); 390 | assert.equal(err.message, 'Argument 0 must be a string or buffer'); 391 | } 392 | }, 393 | 'requires a function for the second argument': { 394 | topic: function (FromString) { 395 | try { 396 | return FromString('first-arg', 'second-arg'); 397 | } catch (e) { 398 | return e; 399 | } 400 | }, 401 | 'throwing an error otherwise': function (err) { 402 | assert.instanceOf(err, TypeError); 403 | assert.equal(err.message, 'Argument 1 must be a function'); 404 | } 405 | } 406 | } 407 | }).addBatch({ 408 | // Ensure errors provide the expected interface 409 | 410 | 'Loading a non-existent mapfile': { 411 | topic: function () { 412 | mapserv.Map.FromFile('non-existent-file', this.callback); // this should produce an error 413 | }, 414 | 415 | 'results in an error': function (err, result) { 416 | assert.equal(undefined, result); 417 | assertMapserverError('MS_DEFAULT_MAPFILE_PATTERN validation failed.', err); 418 | } 419 | } 420 | }).addBatch({ 421 | // Ensure `Map.FromFile` works as expected 422 | 423 | 'A valid mapfile file': { 424 | topic: path.join(__dirname, 'valid.map'), 425 | 426 | 'when loaded with a valid callback': { 427 | topic: function (mapfile) { 428 | mapserv.Map.FromFile(mapfile, this.callback); // load the map from file 429 | }, 430 | 'results in a `Map`': function (err, result) { 431 | assert.isNull(err); 432 | assert.instanceOf(result, mapserv.Map); 433 | } 434 | } 435 | }, 436 | 'An invalid mapfile': { 437 | topic: path.join(__dirname, 'invalid.map'), 438 | 439 | 'when loaded': { 440 | topic: function (mapfile) { 441 | mapserv.Map.FromFile(mapfile, this.callback); // load the map from file 442 | }, 443 | 'results in an error': function (err, result) { 444 | assert.equal(undefined, result); 445 | assertMapserverError('Parsing error near (LAYER):(line 14)', err); 446 | } 447 | } 448 | }, 449 | 'A mapfile with a non existent INCLUDE': { 450 | topic: path.join(__dirname, 'include-error.map'), 451 | 452 | 'when loaded': { 453 | topic: function (mapfile) { 454 | mapserv.Map.FromFile(mapfile, this.callback); // load the map from file 455 | }, 456 | 'results in an error': function (err, result) { 457 | assert.equal(undefined, result); 458 | assertMapserverError('Premature End-of-File.', err, 1); 459 | } 460 | } 461 | } 462 | }).addBatch({ 463 | // Ensure `Map.FromString` works as expected 464 | 465 | 'A valid mapfile string': { 466 | topic: function () { 467 | var mapfile = path.join(__dirname, 'valid.map'); 468 | fs.readFile(mapfile, "utf8", this.callback); 469 | }, 470 | 'when loaded with a valid callback': { 471 | topic: function (mapfile) { 472 | mapserv.Map.FromString(mapfile, this.callback); // load the map from a file 473 | }, 474 | 'results in a `Map`': function (err, result) { 475 | assert.isNull(err); 476 | assert.instanceOf(result, mapserv.Map); 477 | } 478 | } 479 | }, 480 | 'A valid mapfile buffer': { 481 | topic: function () { 482 | var mapfile = path.join(__dirname, 'valid.map'); 483 | fs.readFile(mapfile, this.callback); 484 | }, 485 | 'when loaded with a valid callback': { 486 | topic: function (mapfile) { 487 | mapserv.Map.FromString(mapfile, this.callback); // load the map from a file 488 | }, 489 | 'results in a `Map`': function (err, result) { 490 | assert.isNull(err); 491 | assert.instanceOf(result, mapserv.Map); 492 | } 493 | } 494 | }, 495 | 'An invalid mapfile buffer': { 496 | topic: function () { 497 | var mapfile = path.join(__dirname, 'invalid.map'); 498 | fs.readFile(mapfile, this.callback); 499 | }, 500 | 'when loaded': { 501 | topic: function (mapfile) { 502 | mapserv.Map.FromString(mapfile, this.callback); // load the map from a string 503 | }, 504 | 'results in an error': function (err, result) { 505 | assert.equal(undefined, result); 506 | assertMapserverError('Parsing error near (LAYER):(line 14)', err); 507 | } 508 | } 509 | }, 510 | 'A mapfile buffer that does not terminate properly': { 511 | topic: "MAP \ 512 | NAME DEMO \ 513 | STATUS ON \ 514 | SIZE 150 150 \ 515 | EXTENT 0 0 150 150 \ 516 | IMAGECOLOR 255 255 255 \ 517 | IMAGETYPE png \ 518 | LAYER \ 519 | NAME foo \ 520 | TYPE POINT \ 521 | STATUS DEFAULT \ 522 | TRANSFORM False \ 523 | FEATURE \ 524 | POINTS \ 525 | 50 50 \ 526 | END \ 527 | END \ 528 | CLASS \ 529 | LABEL \ 530 | SIZE 10 \ 531 | COLOR 0 0 0 \ 532 | TYPE BITMAP \ 533 | TEXT \"hello\" # doesn't work\ 534 | END \ 535 | END \ 536 | END \ 537 | END", 538 | 'when loaded': { 539 | topic: function (mapfile) { 540 | mapserv.Map.FromString(mapfile, this.callback); // load the map from a string 541 | }, 542 | 'results in an error': function (err, result) { 543 | assert.equal(undefined, result); 544 | assertMapserverError('Premature End-of-File.', err); 545 | } 546 | } 547 | } 548 | }).addBatch({ 549 | // Ensure `Map.mapserv` has the expected interface 550 | 551 | 'the `Map.mapserv` method': { 552 | topic: function () { 553 | mapserv.Map.FromFile(path.join(__dirname, 'valid.map'), this.callback); 554 | }, 555 | 556 | 'works with two valid arguments': { 557 | topic: function (map) { 558 | return typeof(map.mapserv({}, function(err, response) { 559 | // do nothing 560 | })); 561 | }, 562 | 'returning undefined when called': function (retval) { 563 | assert.equal(retval, 'undefined'); 564 | } 565 | }, 566 | 'works with body data as a string': { 567 | topic: function (map) { 568 | return typeof(map.mapserv({}, "mode=map&layer=credits", function(err, response) { 569 | // do nothing 570 | })); 571 | }, 572 | 'returning undefined when called': function (retval) { 573 | assert.equal(retval, 'undefined'); 574 | } 575 | }, 576 | 'works with body data as a buffer': { 577 | topic: function (map) { 578 | return typeof(map.mapserv({}, new Buffer("mode=map&layer=credits"), function(err, response) { 579 | // do nothing 580 | })); 581 | }, 582 | 'returning undefined when called': function (retval) { 583 | assert.equal(retval, 'undefined'); 584 | } 585 | }, 586 | 'works with body data as `null`': { 587 | topic: function (map) { 588 | return typeof(map.mapserv({}, null, function(err, response) { 589 | // do nothing 590 | })); 591 | }, 592 | 'returning undefined when called': function (retval) { 593 | assert.equal(retval, 'undefined'); 594 | } 595 | }, 596 | 'works with body data as `undefined`': { 597 | topic: function (map) { 598 | return typeof(map.mapserv({}, undefined, function(err, response) { 599 | // do nothing 600 | })); 601 | }, 602 | 'returning undefined when called': function (retval) { 603 | assert.equal(retval, 'undefined'); 604 | } 605 | }, 606 | 'fails with body data as an object': { 607 | topic: function (map) { 608 | try { 609 | return map.mapserv({}, {}, function(err, response) { 610 | // do nothing 611 | }); 612 | } catch (e) { 613 | return e; 614 | } 615 | }, 616 | 'throwing an error': function (err) { 617 | assert.instanceOf(err, Error); 618 | assert.equal(err.message, "Argument 1 must be one of a string; buffer; null; undefined"); 619 | } 620 | }, 621 | 'fails with one argument': { 622 | topic: function (map) { 623 | try { 624 | return map.mapserv('first-arg'); 625 | } catch (e) { 626 | return e; 627 | } 628 | }, 629 | 'throwing an error': function (err) { 630 | assert.instanceOf(err, Error); 631 | assert.equal(err.message, 'usage: Map.mapserv(env, [body], callback)'); 632 | } 633 | }, 634 | 'fails with four arguments': { 635 | topic: function (map) { 636 | try { 637 | return map.mapserv('1st', '2nd', '3rd', '4th'); 638 | } catch (e) { 639 | return e; 640 | } 641 | }, 642 | 'throwing an error': function (err) { 643 | assert.instanceOf(err, Error); 644 | assert.equal(err.message, 'usage: Map.mapserv(env, [body], callback)'); 645 | } 646 | }, 647 | 'requires an object for the first argument': { 648 | topic: function (map) { 649 | try { 650 | return map.mapserv(null, function(err, response) { 651 | // do nothing 652 | }); 653 | } catch (e) { 654 | return e; 655 | } 656 | }, 657 | 'throwing an error otherwise': function (err) { 658 | assert.instanceOf(err, TypeError); 659 | assert.equal(err.message, 'Argument 0 must be an object'); 660 | } 661 | }, 662 | 'requires a function for the second argument': { 663 | topic: function (map) { 664 | try { 665 | return map.mapserv({}, null); 666 | } catch (e) { 667 | return e; 668 | } 669 | }, 670 | 'throwing an error otherwise': function (err) { 671 | assert.instanceOf(err, TypeError); 672 | assert.equal(err.message, 'Argument 1 must be a function'); 673 | } 674 | } 675 | } 676 | }).addBatch({ 677 | // Ensure mapserv functions as expected with a valid map 678 | 'requesting a valid map': { 679 | topic: function () { 680 | mapserv.Map.FromFile(path.join(__dirname, 'valid.map'), this.callback); 681 | }, 682 | 'via `GET`': { 683 | topic: function (map) { 684 | return map.mapserv( 685 | { 686 | 'REQUEST_METHOD': 'GET', 687 | // all GET variables after `layer` are there to ensure 688 | // code in `node-mapservutil.c` is covered 689 | 'QUERY_STRING': 'mode=map&layer=credits&classgroup=group2&map.name=new_name&context=/foo/bar.xml&context=http://foo/bar.xml', 690 | 'HTTP_COOKIE': 'Cookie=tasty' // ensure code in `node-mapservutil.c` is covered 691 | }, 692 | this.callback); 693 | }, 694 | 'returns a response': { 695 | 'which is an object': function (response) { 696 | assert.instanceOf(response, Object); 697 | }, 698 | 'which has the correct headers': function (response) { 699 | assert.lengthOf(response.headers, 2); 700 | 701 | // check the content-type 702 | assert.isArray(response.headers['Content-Type']); 703 | assert.deepEqual(response.headers['Content-Type'], [ 'image/png' ]); 704 | 705 | // check the content-length 706 | assert.isArray(response.headers['Content-Length']); 707 | assert.lengthOf(response.headers['Content-Length'], 1); 708 | assert.isNumber(response.headers['Content-Length'][0]); 709 | assert.isTrue(response.headers['Content-Length'][0] > 0); 710 | }, 711 | 'which returns image data as a `Buffer`': function (response) { 712 | assert.isObject(response.data); 713 | assert.instanceOf(response.data, buffer.Buffer); 714 | assert.isTrue(response.data.length > 0); 715 | } 716 | }, 717 | 'does not return an error': function (err, response) { 718 | assert.isNull(err); 719 | } 720 | }, 721 | 'via `GET` with invalid CGI parameters': { 722 | topic: function (map) { 723 | return map.mapserv( 724 | { 725 | 'REQUEST_METHOD': 'GET', 726 | 'QUERY_STRING': 'mode=map&layer=credits&zoomdir=2' // bad zoomdir 727 | }, this.callback); 728 | }, 729 | 'returns an error': function (err, response) { 730 | assertMapserverError('Zoom direction must be 1, 0 or -1.', err); 731 | } 732 | }, 733 | 'via `GET` with invalid mapfile parameters': { 734 | topic: function (map) { 735 | return map.mapserv( 736 | { 737 | 'REQUEST_METHOD': 'GET', 738 | 'QUERY_STRING': 'mode=map&layer=credits&map.layer[2].name=oops' // `layer[2]` doesn't exist 739 | }, this.callback); 740 | }, 741 | 'returns an error': function (err, response) { 742 | assertMapserverError('Layer to be modified not valid.', err); 743 | } 744 | }, 745 | 'via `POST`': { 746 | 'using a string': { 747 | topic: function (map) { 748 | var body = 'mode=map&layer=credits'; 749 | return map.mapserv( 750 | { 751 | 'REQUEST_METHOD': 'POST', 752 | 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 753 | 'CONTENT_LENGTH': body.length 754 | }, 755 | body, 756 | this.callback); 757 | }, 758 | 'returns a response': { 759 | 'which is an object': function (response) { 760 | assert.instanceOf(response, Object); 761 | }, 762 | 'which has the correct headers': function (response) { 763 | assert.lengthOf(response.headers, 2); 764 | 765 | // check the content-type 766 | assert.isArray(response.headers['Content-Type']); 767 | assert.deepEqual(response.headers['Content-Type'], [ 'image/png' ]); 768 | 769 | // check the content-length 770 | assert.isArray(response.headers['Content-Length']); 771 | assert.lengthOf(response.headers['Content-Length'], 1); 772 | assert.isNumber(response.headers['Content-Length'][0]); 773 | assert.isTrue(response.headers['Content-Length'][0] > 0); 774 | }, 775 | 'which returns image data as a `Buffer`': function (response) { 776 | assert.isObject(response.data); 777 | assert.instanceOf(response.data, buffer.Buffer); 778 | assert.isTrue(response.data.length > 0); 779 | } 780 | }, 781 | 'does not return an error': function (err, response) { 782 | assert.isNull(err); 783 | } 784 | }, 785 | 'using a buffer': { 786 | topic: function (map) { 787 | var body = new Buffer('mode=map&layer=credits'); 788 | return map.mapserv( 789 | { 790 | 'REQUEST_METHOD': 'POST', 791 | 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 792 | 'CONTENT_LENGTH': body.length 793 | }, 794 | body, 795 | this.callback); 796 | }, 797 | 'returns a response': { 798 | 'which is an object': function (response) { 799 | assert.instanceOf(response, Object); 800 | }, 801 | 'which has the correct headers': function (response) { 802 | assert.lengthOf(response.headers, 2); 803 | 804 | // check the content-type 805 | assert.isArray(response.headers['Content-Type']); 806 | assert.deepEqual(response.headers['Content-Type'], [ 'image/png' ]); 807 | 808 | // check the content-length 809 | assert.isArray(response.headers['Content-Length']); 810 | assert.lengthOf(response.headers['Content-Length'], 1); 811 | assert.isNumber(response.headers['Content-Length'][0]); 812 | assert.isTrue(response.headers['Content-Length'][0] > 0); 813 | }, 814 | 'which returns image data as a `Buffer`': function (response) { 815 | assert.isObject(response.data); 816 | assert.instanceOf(response.data, buffer.Buffer); 817 | assert.isTrue(response.data.length > 0); 818 | } 819 | }, 820 | 'does not return an error': function (err, response) { 821 | assert.isNull(err); 822 | } 823 | } 824 | }, 825 | 'with no `REQUEST_METHOD` returns a response': { 826 | topic: function (map) { 827 | map.mapserv( 828 | { 829 | // empty 830 | }, 831 | this.callback); 832 | }, 833 | 'which is an object': function (err, response) { 834 | assert.instanceOf(response, Object); 835 | }, 836 | 'which only has a `Content-Length` header': function (err, response) { 837 | assert.lengthOf(response.headers, 1); 838 | assert.isArray(response.headers['Content-Length']); 839 | assert.lengthOf(response.headers['Content-Length'], 1); 840 | assert.isNumber(response.headers['Content-Length'][0]); 841 | assert.isTrue(response.headers['Content-Length'][0] > 0); 842 | }, 843 | 'which has text data as a `Buffer`': function (err, response) { 844 | assert.isObject(response.data); 845 | assert.instanceOf(response.data, buffer.Buffer); 846 | assert.isTrue(response.data.length > 0); 847 | }, 848 | 'which has an error': function (err, response) { 849 | assertMapserverError('No request parameters loaded', err); 850 | } 851 | }, 852 | 'with a `REQUEST_METHOD` but no `QUERY_STRING` returns a response': { 853 | topic: function (map) { 854 | return map.mapserv( 855 | { 856 | 'REQUEST_METHOD': 'GET' 857 | // no QUERY_STRING 858 | }, 859 | this.callback); 860 | }, 861 | 'which is an object': function (err, response) { 862 | assert.instanceOf(response, Object); 863 | }, 864 | 'which has the correct headers': function (err, response) { 865 | assert.lengthOf(response.headers, 2); 866 | 867 | // check the content-type 868 | assert.isArray(response.headers['Content-Type']); 869 | assert.deepEqual(response.headers['Content-Type'], [ 'text/html' ]); 870 | 871 | // check the content-length 872 | assert.isArray(response.headers['Content-Length']); 873 | assert.lengthOf(response.headers['Content-Length'], 1); 874 | assert.isNumber(response.headers['Content-Length'][0]); 875 | assert.isTrue(response.headers['Content-Length'][0] > 0); 876 | }, 877 | 'which has text data as a `Buffer`': function (err, response) { 878 | assert.isObject(response.data); 879 | assert.instanceOf(response.data, buffer.Buffer); 880 | assert.isTrue(response.data.length > 0); 881 | assert.equal(response.data.toString(), "No query information to decode. QUERY_STRING not set.\n"); 882 | }, 883 | 'returns an error': function (err, response) { 884 | assertMapserverError('No request parameters loaded', err); 885 | } 886 | } 887 | } 888 | }).addBatch({ 889 | // Ensure `createCGIEnvironment` works as expected 890 | 'calling `createCGIEnvironment`': { 891 | topic: mapserv.createCGIEnvironment(dummyRequest(), { 892 | SCRIPT_NAME: '/testing' // overwrite a variable 893 | }), 894 | 895 | 'should produce the expected CGI environment': function (env) { 896 | assert.isObject(env); 897 | assert.deepEqual(env, { 898 | SERVER_SOFTWARE: 'Node.js', 899 | SERVER_NAME: 'localhost', 900 | GATEWAY_INTERFACE: 'CGI/1.1', 901 | SERVER_PROTOCOL: 'HTTP/1.1', 902 | SERVER_PORT: '80', 903 | REQUEST_METHOD: 'POST', 904 | PATH_INFO: '/foo/bar', 905 | PATH_TRANSLATED: path.resolve(path.join('.', 'foo/bar')), 906 | SCRIPT_NAME: '/testing', 907 | QUERY_STRING: 'key=value', 908 | REMOTE_ADDR: '127.0.0.0', 909 | CONTENT_LENGTH: '22', 910 | CONTENT_TYPE: 'application/x-www-form-urlencoded', 911 | AUTH_TYPE: 'Basic', 912 | HTTP_ACCEPT: '*/*' }); 913 | } 914 | } 915 | }).export(module); // Export the Suite 916 | -------------------------------------------------------------------------------- /test/valid.map: -------------------------------------------------------------------------------- 1 | # A valid mapfile used for testing 2 | MAP 3 | NAME valid 4 | STATUS ON 5 | EXTENT 0 0 4000 3000 6 | SIZE 400 300 7 | IMAGECOLOR 200 255 255 8 | CONFIG "CGI_CONTEXT_URL" "1" 9 | 10 | WEB 11 | IMAGEPATH "./" 12 | IMAGEURL "/tmp/" 13 | END 14 | 15 | LAYER 16 | NAME "credits" 17 | STATUS DEFAULT 18 | TRANSFORM FALSE 19 | TYPE POINT 20 | CLASSGROUP "group1" 21 | FEATURE 22 | POINTS 23 | 200 150 24 | END 25 | TEXT 'Hello world. Mapserver rocks.' 26 | END 27 | CLASS 28 | GROUP "group1" 29 | LABEL 30 | TYPE BITMAP 31 | STYLE 32 | GEOMTRANSFORM 'labelpnt' 33 | COLOR 0 0 0 34 | END 35 | END 36 | END 37 | CLASS 38 | GROUP "group2" 39 | LABEL 40 | TYPE BITMAP 41 | STYLE 42 | GEOMTRANSFORM 'labelpnt' 43 | COLOR 0 0 255 44 | END 45 | END 46 | END 47 | END 48 | 49 | END 50 | -------------------------------------------------------------------------------- /tools/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | ############################################################################## 4 | # Copyright (c) 2012, GeoData Institute (www.geodata.soton.ac.uk) 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # 10 | # - Redistributions of source code must retain the above copyright notice, 11 | # this list of conditions and the following disclaimer. 12 | # 13 | # - Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | # POSSIBILITY OF SUCH DAMAGE. 28 | ############################################################################## 29 | 30 | """ 31 | Output mapserver configuration information to `node-gyp` 32 | 33 | Configuration options are retrieved from environment variables set using `npm 34 | config set`. This allows for a simple `npm install mapserv` to work. 35 | """ 36 | 37 | from optparse import OptionParser 38 | import os, sys 39 | import re 40 | 41 | def warn(msg): 42 | print >> sys.stderr, msg 43 | 44 | def die(msg): 45 | warn('Configuration failed: %s' % msg) 46 | sys.exit(1) 47 | 48 | class ConfigError(Exception): 49 | pass 50 | 51 | class Config(object): 52 | """Base class for obtaining Mapserver configuration information""" 53 | 54 | def __init__(self, build_dir): 55 | self.build_dir = build_dir 56 | 57 | def getLibDir(self): 58 | return '' 59 | 60 | def getIncludeDir(self): 61 | return os.path.join(self.build_dir) 62 | 63 | def getLdflags(self): 64 | lib_dir = self.getLibDir() 65 | ldflags = '' 66 | if lib_dir: 67 | # write the library path into the resulting binary 68 | ldflags += "-Wl,-rpath=%s -L%s\n" % (lib_dir, lib_dir) 69 | return ldflags 70 | 71 | def getCflags(self): 72 | return '' 73 | 74 | class AutoconfConfig(Config): 75 | """Class for obtaining mapserver configuration pre mapserver 6.4 76 | 77 | Mapserver uses autotools for building and configuration in this version. 78 | """ 79 | 80 | def __init__(self, *args, **kwargs): 81 | super(AutoconfConfig, self).__init__(*args, **kwargs) 82 | makefile = os.path.join(self.build_dir, 'Makefile') 83 | if not os.path.exists(makefile): 84 | raise ConfigError('Expected `Makefile` in %s' % self.build_dir) 85 | 86 | self.makefile = makefile 87 | 88 | def iterMakefile(self): 89 | """ 90 | Iterate over the contents of the `Makefile` 91 | 92 | This additionally checks to ensure that Mapserver has been compiled 93 | with threads, raising an error if this is not the case. 94 | """ 95 | with_threads = False 96 | p = re.compile('^THREAD *= *(.*)$') 97 | with open(self.makefile, 'r') as f: 98 | for line in f: 99 | match = p.match(line) 100 | if match: 101 | with_threads = bool(match.groups()[0].strip()) 102 | yield line.rstrip() 103 | if not with_threads: 104 | raise ConfigError('Mapserver has not been compiled with support for threads') 105 | 106 | def getLibDir(self): 107 | p = re.compile('^prefix\s*=\s*(.+)$') # match the prefix 108 | libdir = '' 109 | for line in self.iterMakefile(): 110 | match = p.match(line) 111 | if match: 112 | arg = match.groups()[0].strip() 113 | if arg: 114 | # we don't return here to let iterMakefile iterate over the 115 | # whole file performing its thread check 116 | libdir = os.path.join(arg, 'lib') 117 | return libdir 118 | 119 | def getCflags(self): 120 | # add includes from the Makefile 121 | p = re.compile('^[A-Z]+_INC *= *(.+)$') # match an include header 122 | matches = [] 123 | for line in self.iterMakefile(): 124 | match = p.match(line) 125 | if match: 126 | arg = match.groups()[0].strip() 127 | if arg: 128 | matches.append(arg) 129 | 130 | return ' '.join(matches) 131 | 132 | class CmakeConfig(Config): 133 | """Class for obtaining Mapserver configuration for versions >= 6.4 134 | 135 | Mapserver uses Cmake for building and configuration in this version. 136 | """ 137 | 138 | def __init__(self, *args, **kwargs): 139 | super(CmakeConfig, self).__init__(*args, **kwargs) 140 | cmake_cache = os.path.join(self.build_dir, 'CMakeCache.txt') 141 | if not os.path.exists(cmake_cache): 142 | raise ConfigError('Expected `CMakeCache.txt` in %s' % self.build_dir) 143 | 144 | self.cmake_cache = cmake_cache 145 | 146 | def iterCmakeCache(self): 147 | """ 148 | Iterate over the contents of the `CmakeCache.txt` file 149 | 150 | This additionally checks to ensure that Mapserver has been compiled 151 | with threads, raising an error if this is not the case. 152 | """ 153 | with_threads = False 154 | patterns = [ 155 | re.compile('^WITH_THREAD_SAFETY:BOOL *= *(.+)$'), # >= Mapserver 6.4 156 | re.compile('^WITH_THREADS:BOOL *= *(.+)$') # < Mapserver 6.4 157 | ] 158 | 159 | with open(self.cmake_cache, 'r') as f: 160 | for line in f: 161 | for p in patterns: 162 | match = p.match(line) 163 | if match: 164 | with_threads = match.groups()[0].strip() in ('ON', '1') 165 | yield line 166 | if not with_threads: 167 | raise ConfigError('Mapserver has not been compiled with support for threads') 168 | 169 | def getLibDir(self): 170 | p = re.compile('^CMAKE_INSTALL_PREFIX:PATH *= *(.+)$') # match the prefix 171 | lib_dir = '' 172 | for line in self.iterCmakeCache(): 173 | match = p.match(line) 174 | if match: 175 | arg = match.groups()[0].strip() 176 | if arg: 177 | # we don't return here to let iterCmakeCache iterate over 178 | # the whole file performing its thread check 179 | lib_dir = os.path.join(arg, 'lib') 180 | 181 | return lib_dir 182 | 183 | def getIncludeDir(self): 184 | dirs = [] 185 | patterns = [ 186 | re.compile('^\w+_INCLUDE_DIR:PATH *= *(.+)$'), # match a library directory 187 | re.compile('^MapServer_(?:SOURCE|BINARY)_DIR:STATIC *= *(.+)$') # match the mapserver directories 188 | ] 189 | 190 | for line in self.iterCmakeCache(): 191 | for p in patterns: 192 | match = p.match(line) 193 | if match: 194 | arg = match.groups()[0].strip() 195 | if arg: 196 | dirs.append(arg) 197 | continue # skip the other patterns 198 | 199 | return ' '.join(dirs) 200 | 201 | 202 | parser = OptionParser() 203 | parser.add_option("--include", 204 | action="store_true", default=False, 205 | help="output the mapserver include path") 206 | 207 | parser.add_option("--libraries", 208 | action="store_true", default=False, 209 | help="output the mapserver library link option") 210 | 211 | parser.add_option("--ldflags", 212 | action="store_true", default=False, 213 | help="output the mapserver library rpath option") 214 | 215 | parser.add_option("--cflags", 216 | action="store_true", default=False, 217 | help="output the mapserver cflag options") 218 | 219 | (options, args) = parser.parse_args() 220 | 221 | try: 222 | # gyp provides the build directory in an environment variable 223 | build_dir = os.environ['npm_config_mapserv_build_dir'] 224 | except KeyError: 225 | # for cases where gyp is not the caller just use `npm` directly 226 | import subprocess 227 | cmd = ['npm', 'config', 'get', 'mapserv:build_dir'] 228 | npm = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 229 | stdout, stderr = npm.communicate() 230 | if npm.returncode != 0: 231 | die('npm failed: ' + stderr) 232 | 233 | build_dir = stdout.strip() 234 | if build_dir == 'undefined': 235 | die('`npm config set mapserv:build_dir` has not been called') 236 | 237 | # get the config object, trying the new cmake system first and falling back to 238 | # the legacy autoconf build sytem 239 | try: 240 | try: 241 | config = CmakeConfig(build_dir) 242 | except ConfigError, e: 243 | try: 244 | config = AutoconfConfig(build_dir) 245 | except ConfigError, e2: 246 | warn("Failed to configure using Cmake: %s" % e) 247 | warn("Attempting configuration using autotools...") 248 | die(e2) 249 | 250 | # output the requested options 251 | if options.include: 252 | print config.getIncludeDir() 253 | 254 | if options.libraries: 255 | lib_dir = config.getLibDir() 256 | if lib_dir: 257 | print "-L%s" % lib_dir 258 | 259 | if options.ldflags: 260 | print config.getLdflags() 261 | 262 | if options.cflags: 263 | print config.getCflags() 264 | 265 | except ConfigError, e: 266 | die(e) 267 | -------------------------------------------------------------------------------- /tools/git-bisect-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## 4 | # Mapserver regression testing script for use with `git bisect run` 5 | # 6 | # Commits in mapserver will sometimes cause the node-mapserv test suite to 7 | # fail. This script can be used in conjunction with `git bisect run` to find 8 | # out which mapserver commit caused the failure: basically it builds and 9 | # installs mapserver, builds node-mapserv against the install, tests 10 | # node-mapserv and passes the test exit status back to `git bisect` 11 | # 12 | # Example: Assume you have a situation where mapserver `HEAD` has a change that 13 | # is failing the node-mapserv test suite. You know for a fact that the commit 14 | # tagged `rel-6-2-0` passed the test suite. Your Mapserver checkout is in 15 | # `/tmp/mapserver`; you are installing mapserver to `/tmp/mapserver-install`; 16 | # your node-mapserv checkout is at `/tmp/node-mapserv`: you would run the 17 | # following commands to find the commit which introduced the change that caused 18 | # the test suite to fail: 19 | # 20 | # cd /tmp/mapserver 21 | # git bisect start HEAD rel-6-2-0 -- 22 | # git bisect run /tmp/node-mapserv/tools/git-bisect-run.sh /tmp/mapserver-install 23 | # 24 | # N.B. You may need to change the `cmake` or `./configure` arguments to suit 25 | # your environment. 26 | # 27 | 28 | INSTALL_PREFIX=$1 # where is mapserver going to be installed? 29 | GIT_DIR=`pwd` # cache the working directory 30 | NODE_MAPSERV_DIR="$( dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" )" # where is the node-mapserv root directory? 31 | 32 | # build and install mapserver 33 | cd $GIT_DIR 34 | git status --short | cut -b 4- | xargs rm -rf 35 | if [ -f ./CMakeLists.txt ]; then # it's a cmake build 36 | cmake CMakeLists.txt -DWITH_THREADS=1 -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX}/ -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX}/; 37 | else # it's an autotools build 38 | ./configure --prefix=${INSTALL_PREFIX}/ --with-threads; 39 | fi 40 | make && make install 41 | 42 | # build and test node-mapserv 43 | cd $NODE_MAPSERV_DIR 44 | rm -rf build 45 | PATH="${INSTALL_PREFIX}/bin:${PATH}" npm_config_mapserv_build_dir=$GIT_DIR ./node_modules/.bin/node-gyp --debug configure build 46 | ./node_modules/.bin/vows --spec ./test/mapserv-test.js 47 | EXIT=$? # get the exit status from vows 48 | 49 | # Ensure segmentation faults are tested for: `git bisect run` fails when there 50 | # is an exit code >= 129 - segmentation faults return 139 so we must downgrade 51 | # them 52 | if [ $EXIT -eq 139 ]; then EXIT=2; fi; 53 | 54 | # clean up 55 | cd $GIT_DIR 56 | git status --short | cut -b 4- | xargs rm -rf 57 | 58 | # return the vows exit status to git-bisect 59 | exit $EXIT 60 | -------------------------------------------------------------------------------- /tools/install-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## 4 | # Install a checkout of mapserver 5 | # 6 | # This is used by Travis-CI for installing a version of mapserver against which 7 | # node-mapserv can be built and tested. 8 | 9 | die() { 10 | if [ -n "${1}" ]; then 11 | echo $1 12 | fi 13 | exit 1; 14 | } 15 | 16 | PREFIX=$1 # the directory to install into 17 | MAPSERVER_COMMIT=$2 # the commit number to checkout (optional) 18 | 19 | if [ -z "${PREFIX}" ]; then 20 | die "usage: install-deps.sh PREFIX [ MAPSERVER_COMMIT ]" 21 | fi 22 | 23 | # clone the mapserver repository 24 | git clone https://github.com/mapserver/mapserver.git $PREFIX/mapserver || die "Git clone failed" 25 | cd ${PREFIX}/mapserver || die "Cannot cd to ${PREFIX}/mapserver" 26 | if [ -n "${MAPSERVER_COMMIT}" ]; then 27 | git checkout $MAPSERVER_COMMIT || die "Cannot checkout ${MAPSERVER_COMMIT}" 28 | fi 29 | 30 | # build and install mapserver. This uses `-DWITH_THREADS` for Mapserver < 6.4 31 | # and `-DWITH_THREAD_SAFETY` for >= 6.4: the unhandled option is ignored. 32 | if [ -f ./CMakeLists.txt ]; then # it's a cmake build 33 | MAPSERVER_BUILD_DIR=${PREFIX}/mapserver/build 34 | mkdir ./build && cd ./build 35 | cmake .. \ 36 | -DWITH_THREADS=1 \ 37 | -DWITH_THREAD_SAFETY=1 \ 38 | -DCMAKE_INSTALL_PREFIX=${PREFIX}/mapserver-install \ 39 | -DWITH_PROJ=0 \ 40 | -DWITH_FRIBIDI=0 \ 41 | -DWITH_HARFBUZZ=0 \ 42 | -DWITH_FCGI=0 \ 43 | -DWITH_GEOS=0 \ 44 | -DWITH_GDAL=0 \ 45 | -DWITH_OGR=0 \ 46 | -DWITH_WCS=0 \ 47 | -DWITH_WFS=0 \ 48 | -DWITH_WMS=0 || die "cmake failed" 49 | else # it's an autotools build 50 | MAPSERVER_BUILD_DIR=${PREFIX}/mapserver 51 | autoconf || die "autoconf failed" 52 | ./configure --prefix=${PREFIX}/mapserver-install --with-threads || die "configure failed" 53 | fi 54 | 55 | make || die "make failed" 56 | make install || die "make install failed" 57 | 58 | # point `npm` at the build 59 | npm config set mapserv:build_dir "${MAPSERVER_BUILD_DIR}" 60 | --------------------------------------------------------------------------------