Disable XML now that we have WSME/Pecan support
We've had a desire to disable XML from the Ironic API for some time, however to do so needed support in WSME/Pecan. That's now been added, so we can implement the Ironic side. To do so, we wrap wsmeext.pecan.expose() and replace all the decorators with a call to ironic.api.expose.expose(). Change-Id: I94d0387722225c4356c867f63b6f1a61356f317d Closes-bug: 1271317
This commit is contained in:
parent
7e3896d248
commit
f91a1fd6e7
@ -19,11 +19,11 @@
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
from ironic.api.controllers import v1
|
||||
from ironic.api import expose
|
||||
|
||||
|
||||
class Version(base.APIBase):
|
||||
@ -79,7 +79,7 @@ class RootController(rest.RestController):
|
||||
|
||||
v1 = v1.Controller()
|
||||
|
||||
@wsme_pecan.wsexpose(Root)
|
||||
@expose.expose(Root)
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
|
@ -26,7 +26,6 @@ import pecan
|
||||
from pecan import rest
|
||||
from webob import exc
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
@ -34,6 +33,7 @@ from ironic.api.controllers.v1 import chassis
|
||||
from ironic.api.controllers.v1 import driver
|
||||
from ironic.api.controllers.v1 import node
|
||||
from ironic.api.controllers.v1 import port
|
||||
from ironic.api import expose
|
||||
from ironic.common.i18n import _
|
||||
|
||||
BASE_VERSION = 1
|
||||
@ -158,7 +158,7 @@ class Controller(rest.RestController):
|
||||
chassis = chassis.ChassisController()
|
||||
drivers = driver.DriversController()
|
||||
|
||||
@wsme_pecan.wsexpose(V1)
|
||||
@expose.expose(V1)
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
|
@ -19,7 +19,6 @@ import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
@ -27,6 +26,7 @@ from ironic.api.controllers.v1 import collection
|
||||
from ironic.api.controllers.v1 import node
|
||||
from ironic.api.controllers.v1 import types
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic import objects
|
||||
|
||||
@ -164,7 +164,7 @@ class ChassisController(rest.RestController):
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(ChassisCollection, types.uuid,
|
||||
@expose.expose(ChassisCollection, types.uuid,
|
||||
int, wtypes.text, wtypes.text)
|
||||
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of chassis.
|
||||
@ -176,7 +176,7 @@ class ChassisController(rest.RestController):
|
||||
"""
|
||||
return self._get_chassis_collection(marker, limit, sort_key, sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(ChassisCollection, types.uuid, int,
|
||||
@expose.expose(ChassisCollection, types.uuid, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of chassis with detail.
|
||||
@ -196,7 +196,7 @@ class ChassisController(rest.RestController):
|
||||
return self._get_chassis_collection(marker, limit, sort_key, sort_dir,
|
||||
expand, resource_url)
|
||||
|
||||
@wsme_pecan.wsexpose(Chassis, types.uuid)
|
||||
@expose.expose(Chassis, types.uuid)
|
||||
def get_one(self, chassis_uuid):
|
||||
"""Retrieve information about the given chassis.
|
||||
|
||||
@ -206,7 +206,7 @@ class ChassisController(rest.RestController):
|
||||
chassis_uuid)
|
||||
return Chassis.convert_with_links(rpc_chassis)
|
||||
|
||||
@wsme_pecan.wsexpose(Chassis, body=Chassis, status_code=201)
|
||||
@expose.expose(Chassis, body=Chassis, status_code=201)
|
||||
def post(self, chassis):
|
||||
"""Create a new chassis.
|
||||
|
||||
@ -220,7 +220,7 @@ class ChassisController(rest.RestController):
|
||||
return Chassis.convert_with_links(new_chassis)
|
||||
|
||||
@wsme.validate(types.uuid, [ChassisPatchType])
|
||||
@wsme_pecan.wsexpose(Chassis, types.uuid, body=[ChassisPatchType])
|
||||
@expose.expose(Chassis, types.uuid, body=[ChassisPatchType])
|
||||
def patch(self, chassis_uuid, patch):
|
||||
"""Update an existing chassis.
|
||||
|
||||
@ -250,7 +250,7 @@ class ChassisController(rest.RestController):
|
||||
rpc_chassis.save()
|
||||
return Chassis.convert_with_links(rpc_chassis)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
||||
@expose.expose(None, types.uuid, status_code=204)
|
||||
def delete(self, chassis_uuid):
|
||||
"""Delete a chassis.
|
||||
|
||||
|
@ -17,10 +17,10 @@ import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
|
||||
@ -114,7 +114,7 @@ class DriverPassthruController(rest.RestController):
|
||||
'methods': ['GET']
|
||||
}
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, wtypes.text)
|
||||
@expose.expose(wtypes.text, wtypes.text)
|
||||
def methods(self, driver_name):
|
||||
"""Retrieve information about vendor methods of the given driver.
|
||||
|
||||
@ -132,7 +132,7 @@ class DriverPassthruController(rest.RestController):
|
||||
|
||||
return _VENDOR_METHODS[driver_name]
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, wtypes.text, wtypes.text,
|
||||
@expose.expose(wtypes.text, wtypes.text, wtypes.text,
|
||||
body=wtypes.text)
|
||||
def _default(self, driver_name, method, data=None):
|
||||
"""Call a driver API extension.
|
||||
@ -166,7 +166,7 @@ class DriversController(rest.RestController):
|
||||
'properties': ['GET'],
|
||||
}
|
||||
|
||||
@wsme_pecan.wsexpose(DriverList)
|
||||
@expose.expose(DriverList)
|
||||
def get_all(self):
|
||||
"""Retrieve a list of drivers."""
|
||||
# FIXME(deva): formatting of the auto-generated REST API docs
|
||||
@ -176,7 +176,7 @@ class DriversController(rest.RestController):
|
||||
driver_list = pecan.request.dbapi.get_active_driver_dict()
|
||||
return DriverList.convert_with_links(driver_list)
|
||||
|
||||
@wsme_pecan.wsexpose(Driver, wtypes.text)
|
||||
@expose.expose(Driver, wtypes.text)
|
||||
def get_one(self, driver_name):
|
||||
"""Retrieve a single driver."""
|
||||
# NOTE(russell_h): There is no way to make this more efficient than
|
||||
@ -191,7 +191,7 @@ class DriversController(rest.RestController):
|
||||
|
||||
raise exception.DriverNotFound(driver_name=driver_name)
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, wtypes.text)
|
||||
@expose.expose(wtypes.text, wtypes.text)
|
||||
def properties(self, driver_name):
|
||||
"""Retrieve property information of the given driver.
|
||||
|
||||
|
@ -23,7 +23,6 @@ import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
@ -31,6 +30,7 @@ from ironic.api.controllers.v1 import collection
|
||||
from ironic.api.controllers.v1 import port
|
||||
from ironic.api.controllers.v1 import types
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states as ir_states
|
||||
@ -132,7 +132,7 @@ class BootDeviceController(rest.RestController):
|
||||
return pecan.request.rpcapi.get_boot_device(pecan.request.context,
|
||||
rpc_node.uuid, topic)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text, types.boolean,
|
||||
@expose.expose(None, types.uuid_or_name, wtypes.text, types.boolean,
|
||||
status_code=204)
|
||||
def put(self, node_ident, boot_device, persistent=False):
|
||||
"""Set the boot device for a node.
|
||||
@ -155,7 +155,7 @@ class BootDeviceController(rest.RestController):
|
||||
persistent=persistent,
|
||||
topic=topic)
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name)
|
||||
@expose.expose(wtypes.text, types.uuid_or_name)
|
||||
def get(self, node_ident):
|
||||
"""Get the current boot device for a node.
|
||||
|
||||
@ -170,7 +170,7 @@ class BootDeviceController(rest.RestController):
|
||||
"""
|
||||
return self._get_boot_device(node_ident)
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name)
|
||||
@expose.expose(wtypes.text, types.uuid_or_name)
|
||||
def supported(self, node_ident):
|
||||
"""Get a list of the supported boot devices.
|
||||
|
||||
@ -207,7 +207,7 @@ class ConsoleInfo(base.APIBase):
|
||||
|
||||
class NodeConsoleController(rest.RestController):
|
||||
|
||||
@wsme_pecan.wsexpose(ConsoleInfo, types.uuid_or_name)
|
||||
@expose.expose(ConsoleInfo, types.uuid_or_name)
|
||||
def get(self, node_ident):
|
||||
"""Get connection information about the console.
|
||||
|
||||
@ -225,7 +225,7 @@ class NodeConsoleController(rest.RestController):
|
||||
|
||||
return ConsoleInfo(console_enabled=console_state, console_info=console)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, types.boolean,
|
||||
@expose.expose(None, types.uuid_or_name, types.boolean,
|
||||
status_code=202)
|
||||
def put(self, node_ident, enabled):
|
||||
"""Start and stop the node console.
|
||||
@ -302,7 +302,7 @@ class NodeStatesController(rest.RestController):
|
||||
console = NodeConsoleController()
|
||||
"""Expose console as a sub-element of states"""
|
||||
|
||||
@wsme_pecan.wsexpose(NodeStates, types.uuid_or_name)
|
||||
@expose.expose(NodeStates, types.uuid_or_name)
|
||||
def get(self, node_ident):
|
||||
"""List the states of the node.
|
||||
|
||||
@ -314,7 +314,7 @@ class NodeStatesController(rest.RestController):
|
||||
rpc_node = api_utils.get_rpc_node(node_ident)
|
||||
return NodeStates.convert(rpc_node)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text,
|
||||
@expose.expose(None, types.uuid_or_name, wtypes.text,
|
||||
status_code=202)
|
||||
def power(self, node_ident, target):
|
||||
"""Set the power state of the node.
|
||||
@ -352,7 +352,7 @@ class NodeStatesController(rest.RestController):
|
||||
url_args = '/'.join([node_ident, 'states'])
|
||||
pecan.response.location = link.build_url('nodes', url_args)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text,
|
||||
@expose.expose(None, types.uuid_or_name, wtypes.text,
|
||||
wtypes.text, status_code=202)
|
||||
def provision(self, node_ident, target, configdrive=None):
|
||||
"""Asynchronous trigger the provisioning of the node.
|
||||
@ -665,7 +665,7 @@ class NodeVendorPassthruController(rest.RestController):
|
||||
'methods': ['GET']
|
||||
}
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name)
|
||||
@expose.expose(wtypes.text, types.uuid_or_name)
|
||||
def methods(self, node_ident):
|
||||
"""Retrieve information about vendor methods of the given node.
|
||||
|
||||
@ -685,7 +685,7 @@ class NodeVendorPassthruController(rest.RestController):
|
||||
|
||||
return _VENDOR_METHODS[rpc_node.driver]
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name, wtypes.text,
|
||||
@expose.expose(wtypes.text, types.uuid_or_name, wtypes.text,
|
||||
body=wtypes.text)
|
||||
def _default(self, node_ident, method, data=None):
|
||||
"""Call a vendor extension.
|
||||
@ -728,7 +728,7 @@ class NodeMaintenanceController(rest.RestController):
|
||||
pecan.request.rpcapi.update_node(pecan.request.context,
|
||||
rpc_node, topic=topic)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, wtypes.text,
|
||||
@expose.expose(None, types.uuid_or_name, wtypes.text,
|
||||
status_code=202)
|
||||
def put(self, node_ident, reason=None):
|
||||
"""Put the node in maintenance mode.
|
||||
@ -739,7 +739,7 @@ class NodeMaintenanceController(rest.RestController):
|
||||
"""
|
||||
self._set_maintenance(node_ident, True, reason=reason)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, status_code=202)
|
||||
@expose.expose(None, types.uuid_or_name, status_code=202)
|
||||
def delete(self, node_ident):
|
||||
"""Remove the node from maintenance mode.
|
||||
|
||||
@ -832,7 +832,7 @@ class NodesController(rest.RestController):
|
||||
except exception.InstanceNotFound:
|
||||
return []
|
||||
|
||||
@wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid,
|
||||
@expose.expose(NodeCollection, types.uuid, types.uuid,
|
||||
types.boolean, types.boolean, types.uuid, int, wtypes.text,
|
||||
wtypes.text)
|
||||
def get_all(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
||||
@ -859,7 +859,7 @@ class NodesController(rest.RestController):
|
||||
associated, maintenance, marker,
|
||||
limit, sort_key, sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(NodeCollection, types.uuid, types.uuid,
|
||||
@expose.expose(NodeCollection, types.uuid, types.uuid,
|
||||
types.boolean, types.boolean, types.uuid, int, wtypes.text,
|
||||
wtypes.text)
|
||||
def detail(self, chassis_uuid=None, instance_uuid=None, associated=None,
|
||||
@ -894,7 +894,7 @@ class NodesController(rest.RestController):
|
||||
limit, sort_key, sort_dir, expand,
|
||||
resource_url)
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text, types.uuid_or_name, types.uuid)
|
||||
@expose.expose(wtypes.text, types.uuid_or_name, types.uuid)
|
||||
def validate(self, node=None, node_uuid=None):
|
||||
"""Validate the driver interfaces, using the node's UUID or name.
|
||||
|
||||
@ -917,7 +917,7 @@ class NodesController(rest.RestController):
|
||||
return pecan.request.rpcapi.validate_driver_interfaces(
|
||||
pecan.request.context, rpc_node.uuid, topic)
|
||||
|
||||
@wsme_pecan.wsexpose(Node, types.uuid_or_name)
|
||||
@expose.expose(Node, types.uuid_or_name)
|
||||
def get_one(self, node_ident):
|
||||
"""Retrieve information about the given node.
|
||||
|
||||
@ -929,7 +929,7 @@ class NodesController(rest.RestController):
|
||||
rpc_node = api_utils.get_rpc_node(node_ident)
|
||||
return Node.convert_with_links(rpc_node)
|
||||
|
||||
@wsme_pecan.wsexpose(Node, body=Node, status_code=201)
|
||||
@expose.expose(Node, body=Node, status_code=201)
|
||||
def post(self, node):
|
||||
"""Create a new node.
|
||||
|
||||
@ -972,7 +972,7 @@ class NodesController(rest.RestController):
|
||||
return Node.convert_with_links(new_node)
|
||||
|
||||
@wsme.validate(types.uuid, [NodePatchType])
|
||||
@wsme_pecan.wsexpose(Node, types.uuid_or_name, body=[NodePatchType])
|
||||
@expose.expose(Node, types.uuid_or_name, body=[NodePatchType])
|
||||
def patch(self, node_ident, patch):
|
||||
"""Update an existing node.
|
||||
|
||||
@ -1066,7 +1066,7 @@ class NodesController(rest.RestController):
|
||||
|
||||
return Node.convert_with_links(new_node)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, status_code=204)
|
||||
@expose.expose(None, types.uuid_or_name, status_code=204)
|
||||
def delete(self, node_ident):
|
||||
"""Delete a node.
|
||||
|
||||
|
@ -20,13 +20,13 @@ import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from ironic.api.controllers import base
|
||||
from ironic.api.controllers import link
|
||||
from ironic.api.controllers.v1 import collection
|
||||
from ironic.api.controllers.v1 import types
|
||||
from ironic.api.controllers.v1 import utils as api_utils
|
||||
from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic import objects
|
||||
@ -228,7 +228,7 @@ class PortsController(rest.RestController):
|
||||
except exception.PortNotFound:
|
||||
return []
|
||||
|
||||
@wsme_pecan.wsexpose(PortCollection, types.uuid_or_name, types.uuid,
|
||||
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
||||
types.macaddress, types.uuid, int, wtypes.text,
|
||||
wtypes.text)
|
||||
def get_all(self, node=None, node_uuid=None, address=None, marker=None,
|
||||
@ -260,7 +260,7 @@ class PortsController(rest.RestController):
|
||||
return self._get_ports_collection(node_uuid or node, address, marker,
|
||||
limit, sort_key, sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(PortCollection, types.uuid_or_name, types.uuid,
|
||||
@expose.expose(PortCollection, types.uuid_or_name, types.uuid,
|
||||
types.macaddress, types.uuid, int, wtypes.text,
|
||||
wtypes.text)
|
||||
def detail(self, node=None, node_uuid=None, address=None, marker=None,
|
||||
@ -300,7 +300,7 @@ class PortsController(rest.RestController):
|
||||
limit, sort_key, sort_dir, expand,
|
||||
resource_url)
|
||||
|
||||
@wsme_pecan.wsexpose(Port, types.uuid)
|
||||
@expose.expose(Port, types.uuid)
|
||||
def get_one(self, port_uuid):
|
||||
"""Retrieve information about the given port.
|
||||
|
||||
@ -312,7 +312,7 @@ class PortsController(rest.RestController):
|
||||
rpc_port = objects.Port.get_by_uuid(pecan.request.context, port_uuid)
|
||||
return Port.convert_with_links(rpc_port)
|
||||
|
||||
@wsme_pecan.wsexpose(Port, body=Port, status_code=201)
|
||||
@expose.expose(Port, body=Port, status_code=201)
|
||||
def post(self, port):
|
||||
"""Create a new port.
|
||||
|
||||
@ -329,7 +329,7 @@ class PortsController(rest.RestController):
|
||||
return Port.convert_with_links(new_port)
|
||||
|
||||
@wsme.validate(types.uuid, [PortPatchType])
|
||||
@wsme_pecan.wsexpose(Port, types.uuid, body=[PortPatchType])
|
||||
@expose.expose(Port, types.uuid, body=[PortPatchType])
|
||||
def patch(self, port_uuid, patch):
|
||||
"""Update an existing port.
|
||||
|
||||
@ -372,7 +372,7 @@ class PortsController(rest.RestController):
|
||||
|
||||
return Port.convert_with_links(new_port)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
||||
@expose.expose(None, types.uuid, status_code=204)
|
||||
def delete(self, port_uuid):
|
||||
"""Delete a port.
|
||||
|
||||
|
24
ironic/api/expose.py
Normal file
24
ironic/api/expose.py
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright 2015 Rackspace, Inc
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
|
||||
def expose(*args, **kwargs):
|
||||
"""Ensure that only JSON, and not XML, is supported."""
|
||||
if 'rest_content_types' not in kwargs:
|
||||
kwargs['rest_content_types'] = ('json',)
|
||||
return wsme_pecan.wsexpose(*args, **kwargs)
|
@ -32,6 +32,8 @@ class AuthTokenMiddleware(auth_token.AuthProtocol):
|
||||
|
||||
"""
|
||||
def __init__(self, app, conf, public_api_routes=[]):
|
||||
# TODO(mrda): Remove .xml and ensure that doesn't result in a
|
||||
# 401 Authentication Required instead of 404 Not Found
|
||||
route_pattern_tpl = '%s(\.json|\.xml)?$'
|
||||
|
||||
try:
|
||||
|
@ -88,7 +88,10 @@ class TestACL(base.FunctionalTest):
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
||||
def test_public_api_with_path_extensions(self):
|
||||
for route in ('/v1/', '/v1.json', '/v1.xml'):
|
||||
response = self.get_json(route,
|
||||
routes = {'/v1/': 200,
|
||||
'/v1.json': 200,
|
||||
'/v1.xml': 404}
|
||||
for url in routes:
|
||||
response = self.get_json(url,
|
||||
path_prefix='', expect_errors=True)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(routes[url], response.status_int)
|
||||
|
Loading…
x
Reference in New Issue
Block a user