1# -*- coding: utf-8 -*- 2"""Convenience interface to a locally spawned QGIS Server, e.g. for unit tests 3 4.. note:: This program is free software; you can redistribute it and/or modify 5it under the terms of the GNU General Public License as published by 6the Free Software Foundation; either version 2 of the License, or 7(at your option) any later version. 8""" 9 10__author__ = 'Larry Shaffer' 11__date__ = '2014/02/11' 12__copyright__ = 'Copyright 2014, The QGIS Project' 13 14import sys 15import os 16import shutil 17import platform 18import subprocess 19import time 20import urllib.request 21import urllib.parse 22import urllib.error 23import urllib.request 24import urllib.error 25import urllib.parse 26import tempfile 27 28from utilities import ( 29 unitTestDataPath, 30 getExecutablePath, 31 openInBrowserTab, 32 getTempfilePath 33) 34 35# allow import error to be raised if qgis is not on sys.path 36try: 37 # noinspection PyUnresolvedReferences 38 from qgis.core import QgsRectangle, QgsCoordinateReferenceSystem 39except ImportError as e: 40 raise ImportError(str(e) + '\n\nPlace path to pyqgis modules on sys.path,' 41 ' or assign to PYTHONPATH') 42 43FCGIBIN = None 44MAPSERV = None 45SERVRUN = False 46 47 48class ServerProcessError(Exception): 49 50 def __init__(self, title, msg, err=''): 51 msg += '\n' + ('\n' + str(err).strip() + '\n' if err else '') 52 self.msg = """ 53#----------------------------------------------------------------# 54{0} 55{1} 56#----------------------------------------------------------------# 57 """.format(title, msg) 58 59 def __str__(self): 60 return self.msg 61 62 63class ServerProcess(object): 64 65 def __init__(self): 66 self._startenv = None 67 self._startcmd = [] 68 self._stopcmd = [] 69 self._restartcmd = [] 70 self._statuscmd = [] 71 self._process = None 72 """:type : subprocess.Popen""" 73 self._win = self._mac = self._linux = self._unix = False 74 self._dist = () 75 self._resolve_platform() 76 77 # noinspection PyMethodMayBeStatic 78 def _run(self, cmd, env=None): 79 # print repr(cmd) 80 p = subprocess.Popen(cmd, 81 stderr=subprocess.PIPE, 82 stdout=subprocess.PIPE, 83 env=env, 84 close_fds=True) 85 err = p.communicate()[1] 86 if err: 87 if p: 88 p.kill() 89 # noinspection PyUnusedLocal 90 p = None 91 cmd_s = repr(cmd).strip() + ('\n' + 'ENV: ' + repr(env).strip() + 92 '\n' if env is not None else '') 93 raise ServerProcessError('Server process command failed', 94 cmd_s, err) 95 return p 96 97 def start(self): 98 if self.running(): 99 return 100 self._process = self._run(self._startcmd, env=self._startenv) 101 102 def stop(self): 103 if not self.running(): 104 return False 105 if self._stopcmd: 106 self._run(self._stopcmd) 107 else: 108 self._process.terminate() 109 self._process = None 110 return True 111 112 def restart(self): 113 if self._restartcmd: 114 self._run(self._restartcmd) 115 else: 116 self.stop() 117 self.start() 118 119 def running(self): 120 running = False 121 if self._statuscmd: 122 try: 123 subprocess.check_call(self._statuscmd, 124 stdout=subprocess.PIPE, 125 stderr=subprocess.PIPE) 126 running = True 127 except subprocess.CalledProcessError: 128 running = False 129 elif self._process: 130 running = self._process.poll() is None 131 return running 132 133 def set_startenv(self, env): 134 self._startenv = env 135 136 def set_startcmd(self, cmd): 137 self._startcmd = cmd 138 139 def set_stopcmd(self, cmd): 140 self._stopcmd = cmd 141 142 def set_restartcmd(self, cmd): 143 self._restartcmd = cmd 144 145 def set_statuscmd(self, cmd): 146 self._statuscmd = cmd 147 148 def process(self): 149 return self._process 150 151 def pid(self): 152 pid = 0 153 if self._process: 154 pid = self._process.pid 155 return pid 156 157 def _resolve_platform(self): 158 s = platform.system().lower() 159 self._linux = s.startswith('lin') 160 self._mac = s.startswith('dar') 161 self._unix = self._linux or self._mac 162 self._win = s.startswith('win') 163 self._dist = platform.dist() 164 165 166class WebServerProcess(ServerProcess): 167 168 def __init__(self, kind, exe, conf_dir, temp_dir): 169 ServerProcess.__init__(self) 170 sufx = 'unix' if self._unix else 'win' 171 if kind == 'lighttpd': 172 conf = os.path.join(conf_dir, 'lighttpd', 'config', 173 'lighttpd_{0}.conf'.format(sufx)) 174 self.set_startenv({'QGIS_SERVER_TEMP_DIR': temp_dir}) 175 init_scr_dir = os.path.join(conf_dir, 'lighttpd', 'scripts') 176 if self._mac: 177 init_scr = os.path.join(init_scr_dir, 'lighttpd_mac.sh') 178 self.set_startcmd([init_scr, 'start', exe, conf, temp_dir]) 179 self.set_stopcmd([init_scr, 'stop']) 180 self.set_restartcmd([init_scr, 'restart', exe, conf, temp_dir]) 181 self.set_statuscmd([init_scr, 'status']) 182 elif self._linux: 183 dist = self._dist[0].lower() 184 if dist == 'debian' or dist == 'ubuntu': 185 init_scr = os.path.join(init_scr_dir, 'lighttpd_debian.sh') 186 self.set_startcmd([ 187 init_scr, 'start', exe, temp_dir, conf]) 188 self.set_stopcmd([init_scr, 'stop', exe, temp_dir]) 189 self.set_restartcmd([ 190 init_scr, 'restart', exe, temp_dir, conf]) 191 self.set_statuscmd([init_scr, 'status', exe, temp_dir]) 192 elif dist == 'fedora' or dist == 'rhel': # are these correct? 193 pass 194 else: # win 195 pass 196 197 198class FcgiServerProcess(ServerProcess): 199 200 def __init__(self, kind, exe, fcgi_bin, conf_dir, temp_dir): 201 ServerProcess.__init__(self) 202 if kind == 'spawn-fcgi': 203 if self._unix: 204 fcgi_sock = os.path.join(temp_dir, 'var', 'run', 205 'qgs_mapserv.sock') 206 init_scr_dir = os.path.join(conf_dir, 'fcgi', 'scripts') 207 self.set_startenv({ 208 'QGIS_LOG_FILE': 209 os.path.join(temp_dir, 'log', 'qgis_server.log')}) 210 if self._mac: 211 init_scr = os.path.join(init_scr_dir, 'spawn_fcgi_mac.sh') 212 self.set_startcmd([init_scr, 'start', exe, fcgi_sock, 213 temp_dir + fcgi_bin, temp_dir]) 214 self.set_stopcmd([init_scr, 'stop']) 215 self.set_restartcmd([init_scr, 'restart', exe, fcgi_sock, 216 temp_dir + fcgi_bin, temp_dir]) 217 self.set_statuscmd([init_scr, 'status']) 218 elif self._linux: 219 dist = self._dist[0].lower() 220 if dist == 'debian' or dist == 'ubuntu': 221 init_scr = os.path.join(init_scr_dir, 222 'spawn_fcgi_debian.sh') 223 self.set_startcmd([ 224 init_scr, 'start', exe, fcgi_sock, 225 temp_dir + fcgi_bin, temp_dir]) 226 self.set_stopcmd([ 227 init_scr, 'stop', exe, fcgi_sock, 228 temp_dir + fcgi_bin, temp_dir]) 229 self.set_restartcmd([ 230 init_scr, 'restart', exe, fcgi_sock, 231 temp_dir + fcgi_bin, temp_dir]) 232 self.set_statuscmd([ 233 init_scr, 'status', exe, fcgi_sock, 234 temp_dir + fcgi_bin, temp_dir]) 235 elif dist == 'fedora' or dist == 'rhel': 236 pass 237 else: # win 238 pass 239 240 241# noinspection PyPep8Naming,PyShadowingNames 242class QgisLocalServer(object): 243 244 def __init__(self, fcgi_bin): 245 msg = 'FCGI binary not found at:\n{0}'.format(fcgi_bin) 246 assert os.path.exists(fcgi_bin), msg 247 248 msg = "FCGI binary not 'qgis_mapserv.fcgi':" 249 assert fcgi_bin.endswith('qgis_mapserv.fcgi'), msg 250 251 # hardcoded url, makes all this automated 252 self._ip = '127.0.0.1' 253 self._port = '8448' 254 self._web_url = 'http://{0}:{1}'.format(self._ip, self._port) 255 self._fcgibin_path = '/cgi-bin/qgis_mapserv.fcgi' 256 self._fcgi_url = '{0}{1}'.format(self._web_url, self._fcgibin_path) 257 self._conf_dir = unitTestDataPath('qgis_local_server') 258 259 self._fcgiserv_process = self._webserv_process = None 260 self._fcgiserv_bin = fcgi_bin 261 self._fcgiserv_path = self._webserv_path = '' 262 self._fcgiserv_kind = self._webserv_kind = '' 263 self._temp_dir = '' 264 self._web_dir = '' 265 266 servers = [ 267 ('spawn-fcgi', 'lighttpd') 268 # ('fcgiwrap', 'nginx'), 269 # ('uwsgi', 'nginx'), 270 ] 271 272 chkd = '' 273 for fcgi, web in servers: 274 fcgi_path = getExecutablePath(fcgi) 275 web_path = getExecutablePath(web) 276 if fcgi_path and web_path: 277 self._fcgiserv_path = fcgi_path 278 self._webserv_path = web_path 279 self._fcgiserv_kind = fcgi 280 self._webserv_kind = web 281 break 282 else: 283 chkd += "Find '{0}': {1}\n".format(fcgi, fcgi_path) 284 chkd += "Find '{0}': {1}\n\n".format(web, web_path) 285 286 if not (self._fcgiserv_path and self._webserv_path): 287 raise ServerProcessError( 288 'Could not locate server binaries', 289 chkd, 290 'Make sure one of the sets of servers is available on PATH' 291 ) 292 293 self._temp_dir = tempfile.mkdtemp() 294 self._setup_temp_dir() 295 296 # initialize the servers 297 self._fcgiserv_process = FcgiServerProcess( 298 self._fcgiserv_kind, self._fcgiserv_path, 299 self._fcgibin_path, self._conf_dir, self._temp_dir) 300 self._webserv_process = WebServerProcess( 301 self._webserv_kind, self._webserv_path, 302 self._conf_dir, self._temp_dir) 303 304 # stop any leftover processes, if possible 305 self.stop_processes() 306 307 def startup(self, chkcapa=False): 308 if not os.path.exists(self._temp_dir): 309 self._setup_temp_dir() 310 self.start_processes() 311 if chkcapa: 312 self.check_server_capabilities() 313 314 def shutdown(self): 315 self.stop_processes() 316 self.remove_temp_dir() 317 318 def start_processes(self): 319 self._fcgiserv_process.start() 320 self._webserv_process.start() 321 322 def stop_processes(self): 323 self._fcgiserv_process.stop() 324 self._webserv_process.stop() 325 326 def restart_processes(self): 327 self._fcgiserv_process.restart() 328 self._webserv_process.restart() 329 330 def fcgi_server_process(self): 331 return self._fcgiserv_process 332 333 def web_server_process(self): 334 return self._webserv_process 335 336 def processes_running(self): 337 return (self._fcgiserv_process.running() and 338 self._webserv_process.running()) 339 340 def config_dir(self): 341 return self._conf_dir 342 343 def web_dir(self): 344 return self._web_dir 345 346 def open_web_dir(self): 347 self._open_fs_item(self._web_dir) 348 349 def web_dir_install(self, items, src_dir=''): 350 msg = 'Items parameter should be passed in as a list' 351 assert isinstance(items, list), msg 352 for item in items: 353 if item.startswith('.') or item.endswith('~'): 354 continue 355 path = item 356 if src_dir: 357 path = os.path.join(src_dir, item) 358 try: 359 if os.path.isfile(path): 360 shutil.copy2(path, self._web_dir) 361 elif os.path.isdir(path): 362 shutil.copytree(path, self._web_dir) 363 except Exception as err: 364 raise ServerProcessError('Failed to copy to web directory:', 365 item, 366 str(err)) 367 368 def clear_web_dir(self): 369 for f in os.listdir(self._web_dir): 370 path = os.path.join(self._web_dir, f) 371 try: 372 if os.path.isfile(path): 373 os.unlink(path) 374 else: 375 shutil.rmtree(path) 376 except Exception as err: 377 raise ServerProcessError('Failed to clear web directory', err) 378 379 def temp_dir(self): 380 return self._temp_dir 381 382 def open_temp_dir(self): 383 self._open_fs_item(self._temp_dir) 384 385 def remove_temp_dir(self): 386 if os.path.exists(self._temp_dir): 387 shutil.rmtree(self._temp_dir) 388 389 def ip(self): 390 return self._ip 391 392 def port(self): 393 return self._port 394 395 def web_url(self): 396 return self._web_url 397 398 def fcgi_url(self): 399 return self._fcgi_url 400 401 def check_server_capabilities(self): 402 params = { 403 'SERVICE': 'WMS', 404 'VERSION': '1.3.0', 405 'REQUEST': 'GetCapabilities' 406 } 407 if not self.get_capabilities(params, False)[0]: 408 self.shutdown() 409 raise ServerProcessError( 410 'Local QGIS Server shutdown', 411 'Test QGIS Server is not accessible at:\n' + self._fcgi_url, 412 'Error: failed to retrieve server capabilities' 413 ) 414 415 def get_capabilities(self, params, browser=False): 416 assert self.processes_running(), 'Server processes not running' 417 418 params = self._params_to_upper(params) 419 if (('REQUEST' in params and params['REQUEST'] != 'GetCapabilities') or 420 'REQUEST' not in params): 421 params['REQUEST'] = 'GetCapabilities' 422 423 url = self._fcgi_url + '?' + self.process_params(params) 424 425 res = urllib.request.urlopen(url) 426 xml = res.read().decode('utf-8') 427 if browser: 428 tmp_name = getTempfilePath('html') 429 with open(tmp_name, 'wt') as temp_html: 430 temp_html.write(xml) 431 url = tmp_name 432 openInBrowserTab(url) 433 return False, '' 434 435 success = ('error reading the project file' in xml or 436 'WMS_Capabilities' in xml) 437 return success, xml 438 439 def get_map(self, params, browser=False): 440 assert self.processes_running(), 'Server processes not running' 441 442 msg = ('Map request parameters should be passed in as a dict ' 443 '(key case can be mixed)') 444 assert isinstance(params, dict), msg 445 446 params = self._params_to_upper(params) 447 try: 448 proj = params['MAP'] 449 except KeyError as err: 450 raise KeyError(str(err) + '\nMAP not found in parameters dict') 451 452 if not os.path.exists(proj): 453 msg = '{0}'.format(proj) 454 w_proj = os.path.join(self._web_dir, proj) 455 if os.path.exists(w_proj): 456 params['MAP'] = w_proj 457 else: 458 msg += '\n or\n' + w_proj 459 raise ServerProcessError( 460 'GetMap Request Error', 461 'Project not found at:\n{0}'.format(msg) 462 ) 463 464 if (('REQUEST' in params and params['REQUEST'] != 'GetMap') or 465 'REQUEST' not in params): 466 params['REQUEST'] = 'GetMap' 467 468 url = self._fcgi_url + '?' + self.process_params(params) 469 470 if browser: 471 openInBrowserTab(url) 472 return False, '' 473 474 # try until qgis_mapserv.fcgi process is available (for 20 seconds) 475 # on some platforms the fcgi_server_process is a daemon handling the 476 # launch of the fcgi-spawner, which may be running quickly, but the 477 # qgis_mapserv.fcgi spawned process is not yet accepting connections 478 resp = None 479 tmp_png = None 480 # noinspection PyUnusedLocal 481 filepath = '' 482 # noinspection PyUnusedLocal 483 success = False 484 start_time = time.time() 485 while time.time() - start_time < 20: 486 resp = None 487 try: 488 tmp_png = urllib.request.urlopen(url) 489 except urllib.error.HTTPError as resp: 490 if resp.code == 503 or resp.code == 500: 491 time.sleep(1) 492 else: 493 raise ServerProcessError( 494 'Web/FCGI Process Request HTTPError', 495 'Could not connect to process: ' + str(resp.code), 496 resp.message 497 ) 498 except urllib.error.URLError as resp: 499 raise ServerProcessError( 500 'Web/FCGI Process Request URLError', 501 'Could not connect to process', 502 resp.reason 503 ) 504 else: 505 delta = time.time() - start_time 506 print(('Seconds elapsed for server GetMap: ' + str(delta))) 507 break 508 509 if resp is not None: 510 raise ServerProcessError( 511 'Web/FCGI Process Request Error', 512 'Could not connect to process: ' + str(resp.code) 513 ) 514 515 if (tmp_png is not None and 516 tmp_png.info().getmaintype() == 'image' and 517 tmp_png.info().getheader('Content-Type') == 'image/png'): 518 519 filepath = getTempfilePath('png') 520 with open(filepath, 'wb') as temp_image: 521 temp_image.write(tmp_png.read()) 522 success = True 523 else: 524 raise ServerProcessError( 525 'FCGI Process Request Error', 526 'No valid PNG output' 527 ) 528 529 return success, filepath, url 530 531 def process_params(self, params): 532 # set all keys to uppercase 533 params = self._params_to_upper(params) 534 # convert all convenience objects to compatible strings 535 self._convert_instances(params) 536 # encode params 537 return urllib.parse.urlencode(params, True) 538 539 @staticmethod 540 def _params_to_upper(params): 541 return dict((k.upper(), v) for k, v in list(params.items())) 542 543 @staticmethod 544 def _convert_instances(params): 545 if not params: 546 return 547 if ('LAYERS' in params and 548 isinstance(params['LAYERS'], list)): 549 params['LAYERS'] = ','.join(params['LAYERS']) 550 if ('BBOX' in params and 551 isinstance(params['BBOX'], QgsRectangle)): 552 # not needed for QGIS's 1.3.0 server? 553 # # invert x, y of rect and set precision to 16 554 # rect = self.params['BBOX'] 555 # bbox = ','.join(map(lambda x: '{0:0.16f}'.format(x), 556 # [rect.yMinimum(), rect.xMinimum(), 557 # rect.yMaximum(), rect.xMaximum()])) 558 params['BBOX'] = \ 559 params['BBOX'].toString(1).replace(' : ', ',') 560 561 if ('CRS' in params and 562 isinstance(params['CRS'], QgsCoordinateReferenceSystem)): 563 params['CRS'] = params['CRS'].authid() 564 565 def _setup_temp_dir(self): 566 self._web_dir = os.path.join(self._temp_dir, 'www', 'htdocs') 567 cgi_bin = os.path.join(self._temp_dir, 'cgi-bin') 568 569 os.makedirs(cgi_bin, mode=0o755) 570 os.makedirs(os.path.join(self._temp_dir, 'log'), mode=0o755) 571 os.makedirs(os.path.join(self._temp_dir, 'var', 'run'), mode=0o755) 572 os.makedirs(self._web_dir, mode=0o755) 573 574 # symlink or copy in components 575 shutil.copy2(os.path.join(self._conf_dir, 'index.html'), self._web_dir) 576 if not platform.system().lower().startswith('win'): 577 # symlink allow valid runningFromBuildDir results 578 os.symlink(self._fcgiserv_bin, 579 os.path.join(cgi_bin, 580 os.path.basename(self._fcgiserv_bin))) 581 else: 582 # TODO: what to do here for Win runningFromBuildDir? 583 # copy qgisbuildpath.txt from output/bin directory, too? 584 shutil.copy2(self._fcgiserv_bin, cgi_bin) 585 586 @staticmethod 587 def _exe_path(exe): 588 exe_exts = [] 589 if (platform.system().lower().startswith('win') and 590 "PATHEXT" in os.environ): 591 exe_exts = os.environ["PATHEXT"].split(os.pathsep) 592 593 for path in os.environ["PATH"].split(os.pathsep): 594 exe_path = os.path.join(path, exe) 595 if os.path.exists(exe_path): 596 return exe_path 597 for ext in exe_exts: 598 if os.path.exists(exe_path + ext): 599 return exe_path 600 return '' 601 602 @staticmethod 603 def _open_fs_item(item): 604 if not os.path.exists(item): 605 return 606 s = platform.system().lower() 607 if s.startswith('dar'): 608 subprocess.call(['open', item]) 609 elif s.startswith('lin'): 610 # xdg-open "$1" &> /dev/null & 611 subprocess.call(['xdg-open', item]) 612 elif s.startswith('win'): 613 subprocess.call([item]) 614 else: # ? 615 pass 616 617 618# noinspection PyPep8Naming 619def getLocalServer(): 620 """ Start a local test server controller that independently manages Web and 621 FCGI-spawn processes. 622 623 Input 624 NIL 625 626 Output 627 handle to QgsLocalServer, that's been tested to be valid, then shutdown 628 629 If MAPSERV is already running the handle to it will be returned. 630 631 Before unit test class add: 632 633 MAPSERV = getLocalServer() 634 635 IMPORTANT: When using MAPSERV in a test class, ensure to set these: 636 637 @classmethod 638 def setUpClass(cls): 639 MAPSERV.startup() 640 641 This ensures the subprocesses are started and the temp directory is created. 642 643 @classmethod 644 def tearDownClass(cls): 645 MAPSERV.shutdown() 646 # or, when testing, instead of shutdown... 647 # MAPSERV.stop_processes() 648 # MAPSERV.open_temp_dir() 649 650 This ensures the subprocesses are stopped and the temp directory is removed. 651 If this is not used, the server processes may continue to run after tests. 652 653 If you need to restart the qgis_mapserv.fcgi spawning process to show 654 changes to project settings, consider adding: 655 656 def setUp(self): 657 '''Run before each test.''' 658 # web server stays up across all tests 659 MAPSERV.fcgi_server_process().start() 660 661 def tearDown(self): 662 '''Run after each test.''' 663 # web server stays up across all tests 664 MAPSERV.fcgi_server_process().stop() 665 666 :rtype: QgisLocalServer 667 """ 668 global SERVRUN # pylint: disable=W0603 669 global MAPSERV # pylint: disable=W0603 670 if SERVRUN: 671 msg = 'Local server has already failed to launch or run' 672 assert MAPSERV is not None, msg 673 else: 674 SERVRUN = True 675 676 global FCGIBIN # pylint: disable=W0603 677 if FCGIBIN is None: 678 msg = 'Could not find QGIS_PREFIX_PATH (build directory) in environ' 679 assert 'QGIS_PREFIX_PATH' in os.environ, msg 680 681 fcgi_path = os.path.join(os.environ['QGIS_PREFIX_PATH'], 'bin', 682 'qgis_mapserv.fcgi') 683 msg = 'Could not locate qgis_mapserv.fcgi in build/bin directory' 684 assert os.path.exists(fcgi_path), msg 685 686 FCGIBIN = fcgi_path 687 688 if MAPSERV is None: 689 # On QgisLocalServer init, Web and FCGI-spawn executables are located, 690 # configurations to start/stop/restart those processes (relative to 691 # host platform) are loaded into controller, a temporary web 692 # directory is created, and the FCGI binary copied to its cgi-bin. 693 srv = QgisLocalServer(FCGIBIN) 694 # noinspection PyStatementEffect 695 """:type : QgisLocalServer""" 696 697 try: 698 msg = 'Temp web directory could not be created' 699 assert os.path.exists(srv.temp_dir()), msg 700 701 # install test project components to temporary web directory 702 test_proj_dir = os.path.join(srv.config_dir(), 'test-project') 703 srv.web_dir_install(os.listdir(test_proj_dir), test_proj_dir) 704 # verify they were copied 705 msg = 'Test project could not be copied to temp web directory' 706 res = os.path.exists(os.path.join(srv.web_dir(), 'test-server.qgs')) 707 assert res, msg 708 709 # verify subprocess' status can be checked 710 msg = 'Server processes status could not be checked' 711 assert not srv.processes_running(), msg 712 713 # startup server subprocesses, and check capabilities 714 srv.startup() 715 msg = 'Server processes could not be started' 716 assert srv.processes_running(), msg 717 718 # verify web server (try for 30 seconds) 719 start_time = time.time() 720 res = None 721 while time.time() - start_time < 30: 722 time.sleep(1) 723 try: 724 res = urllib.request.urlopen(srv.web_url()) 725 if res.getcode() == 200: 726 break 727 except urllib.error.URLError: 728 pass 729 msg = 'Web server basic access to root index.html failed' 730 # print repr(res) 731 assert (res is not None and 732 res.getcode() == 200 and 733 'Web Server Working' in res.read().decode('utf-8')), msg 734 735 # verify basic wms service 736 params = { 737 'SERVICE': 'WMS', 738 'VERSION': '1.3.0', 739 'REQUEST': 'GetCapabilities' 740 } 741 msg = '\nFCGI server failed to return capabilities' 742 assert srv.get_capabilities(params, False)[0], msg 743 744 params = { 745 'SERVICE': 'WMS', 746 'VERSION': '1.3.0', 747 'REQUEST': 'GetCapabilities', 748 'MAP': 'test-server.qgs' 749 } 750 msg = '\nFCGI server failed to return capabilities for project' 751 assert srv.get_capabilities(params, False)[0], msg 752 753 # verify the subprocesses can be stopped and controller shutdown 754 srv.shutdown() # should remove temp directory (and test project) 755 msg = 'Server processes could not be stopped' 756 assert not srv.processes_running(), msg 757 msg = 'Temp web directory could not be removed' 758 assert not os.path.exists(srv.temp_dir()), msg 759 760 MAPSERV = srv 761 except AssertionError as err: 762 srv.shutdown() 763 raise AssertionError(err) 764 765 return MAPSERV 766 767 768if __name__ == '__main__': 769 # NOTE: see test_qgis_local_server.py for CTest suite 770 771 import argparse 772 parser = argparse.ArgumentParser() 773 parser.add_argument( 774 'fcgi', metavar='fcgi-bin-path', 775 help='Path to qgis_mapserv.fcgi' 776 ) 777 args = parser.parse_args() 778 779 fcgi = os.path.realpath(args.fcgi) 780 if not os.path.isabs(fcgi) or not os.path.exists(fcgi): 781 print('qgis_mapserv.fcgi not resolved to existing absolute path.') 782 sys.exit(1) 783 784 local_srv = QgisLocalServer(fcgi) 785 proj_dir = os.path.join(local_srv.config_dir(), 'test-project') 786 local_srv.web_dir_install(os.listdir(proj_dir), proj_dir) 787 # local_srv.open_temp_dir() 788 # sys.exit() 789 # creating crs needs app instance to access /resources/srs.db 790 # crs = QgsCoordinateReferenceSystem('EPSG:32613') 791 # default for labeling test data sources: WGS 84 / UTM zone 13N 792 req_params = { 793 'SERVICE': 'WMS', 794 'VERSION': '1.3.0', 795 'REQUEST': 'GetMap', 796 # 'MAP': os.path.join(local_srv.web_dir(), 'test-server.qgs'), 797 'MAP': 'test-server.qgs', 798 # layer stacking order for rendering: bottom,to,top 799 'LAYERS': ['background', 'aoi'], # or 'background,aoi' 800 'STYLES': ',', 801 # 'CRS': QgsCoordinateReferenceSystem obj 802 'CRS': 'EPSG:32613', 803 # 'BBOX': QgsRectangle(606510, 4823130, 612510, 4827130) 804 'BBOX': '606510,4823130,612510,4827130', 805 'FORMAT': 'image/png', # or: 'image/png; mode=8bit' 806 'WIDTH': '600', 807 'HEIGHT': '400', 808 'DPI': '72', 809 'MAP_RESOLUTION': '72', 810 'FORMAT_OPTIONS': 'dpi:72', 811 'TRANSPARENT': 'FALSE', 812 'IgnoreGetMapUrl': '1' 813 } 814 815 # local_srv.web_server_process().start() 816 # openInBrowserTab('http://127.0.0.1:8448') 817 # local_srv.web_server_process().stop() 818 # sys.exit() 819 local_srv.startup(False) 820 openInBrowserTab('http://127.0.0.1:8448') 821 try: 822 local_srv.check_server_capabilities() 823 # open resultant png with system 824 result, png, url = local_srv.get_map(req_params) 825 finally: 826 local_srv.shutdown() 827 828 if result: 829 # print png 830 openInBrowserTab('file://' + png) 831 else: 832 raise ServerProcessError('GetMap Test', 'Failed to generate PNG') 833