def options(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") log.info("[%s] OPTIONS ticket=%s", req.client_addr, ticket_id) if ticket_id == "*": # Reporting the meta-capabilities for all images. allow = ["OPTIONS", "GET", "PUT", "PATCH"] features = ["zero", "flush"] else: # Reporting real image capabilities per ticket. try: ticket = auth.authorize(ticket_id, "read") except errors.AuthorizationError as e: raise http.Error(http.FORBIDDEN, str(e)) # Accessing ticket options considered as client activity. ticket.touch() allow = ["OPTIONS"] features = [] if ticket.may("read"): allow.append("GET") if ticket.may("write"): allow.extend(("PUT", "PATCH")) features = ["zero", "flush"] resp.headers["allow"] = ",".join(allow) msg = {"features": features, "unix_socket": self.config.images.socket} resp.send_json(msg)
def _zero(self, req, resp, ticket_id, msg): size = validate.integer(msg, "size", minval=0) offset = validate.integer(msg, "offset", minval=0, default=0) flush = validate.boolean(msg, "flush", default=False) try: ticket = auth.authorize(ticket_id, "write") except errors.AuthorizationError as e: raise http.Error(http.FORBIDDEN, str(e)) validate.allowed_range(offset, size, ticket) log.info( "[%s] ZERO size=%d offset=%d flush=%s ticket=%s", req.client_addr, size, offset, flush, ticket_id) op = ops.Zero( self._backend(req, ticket), size, offset=offset, flush=flush, buffersize=self.config.daemon.buffer_size, clock=req.clock) try: ticket.run(op) except errors.PartialContent as e: raise http.Error(http.BAD_REQUEST, str(e))
def get(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") try: ticket = auth.get(ticket_id) except KeyError: raise http.Error(http.NOT_FOUND, "No such ticket {!r}".format(ticket_id)) ticket_info = ticket.info() log.debug("[%s] GET ticket=%s", req.client_addr, ticket_info) resp.send_json(ticket_info)
def get(self, req, resp): complete = self.file.seek(0, os.SEEK_END) offset = 0 size = complete # Handle range request. if req.range: offset = req.range.first if offset < 0: offset += complete size = complete - offset elif req.range.last is not None: size = req.range.last - offset + 1 else: size = complete - offset if offset + size > complete: raise http.Error(http.REQUESTED_RANGE_NOT_SATISFIABLE, "Requested {} bytes, available {} bytes".format( size, complete - offset), content_range="bytes */{}".format(complete - offset)) resp.headers["content-length"] = size if req.range: resp.status_code = http.PARTIAL_CONTENT resp.headers["content-range"] = "bytes %d-%d/%d" % ( offset, offset + size - 1, complete) self.file.seek(offset) resp.write(self.file.read(size))
def put(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") # TODO: Reject too big ticket json. We know the size of a ticket based # on the size of the keys and values. try: ticket_dict = json.loads(req.read()) except ValueError as e: raise http.Error(http.BAD_REQUEST, "Ticket is not in a json format: {}".format(e)) log.info("[%s] ADD ticket=%s", req.client_addr, ticket_dict) try: auth.add(ticket_dict) except errors.InvalidTicket as e: raise http.Error(http.BAD_REQUEST, "Invalid ticket: {}".format(e))
def _start_profiling(self, clock): with lock: if yappi.is_running(): raise http.Error(http.BAD_REQUEST, "profile is already running") log.info("Starting profiling using %r clock", clock) yappi.set_clock_type(clock) yappi.start(builtins=True, profile_threads=True)
def _flush(self, req, resp, ticket_id, msg): try: ticket = auth.authorize(ticket_id, "write") except errors.AuthorizationError as e: raise http.Error(http.FORBIDDEN, str(e)) log.info("[%s] FLUSH ticket=%s", req.client_addr, ticket_id) op = ops.Flush(self._backend(req, ticket), clock=req.clock) ticket.run(op)
def patch(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") # TODO: Reject requests with too big payloads. We know the maximum size # of a payload based on the keys and the size of offset and size. try: msg = json.loads(req.read()) except ValueError as e: raise http.Error( http.BAD_REQUEST, "Invalid JSON message {}" .format(e)) op = validate.enum(msg, "op", ("zero", "flush")) if op == "zero": return self._zero(req, resp, ticket_id, msg) elif op == "flush": return self._flush(req, resp, ticket_id, msg) else: raise RuntimeError("Unreachable")
def put(self, req, resp, name): count = req.content_length with open("/dev/null", "wb") as f: while count: with req.clock.run("read"): chunk = req.read(1024 * 1024) if not chunk: raise http.Error(400, "Client disconnected") with req.clock.run("write"): f.write(chunk) count -= len(chunk)
def _stop_profiling(self): with lock: if not yappi.is_running(): raise http.Error(http.BAD_REQUEST, "profile is not running") log.info("Stopping profiling, writing profile to %r", self.config.profile.filename) yappi.stop() stats = yappi.get_func_stats() stats.save(self.config.profile.filename, type="pstat") yappi.clear_stats()
def patch(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") # TODO: Reject requests with too big payload. We know the size of a # ticket patch message based on the keys and values. try: patch = json.loads(req.read()) except ValueError as e: raise http.Error(http.BAD_REQUEST, "Invalid patch: {}".format(e)) timeout = validate.integer(patch, "timeout", minval=0) try: ticket = auth.get(ticket_id) except KeyError: raise http.Error(http.NOT_FOUND, "No such ticket: {}".format(ticket_id)) log.info("[%s] EXTEND timeout=%s ticket=%s", req.client_addr, timeout, ticket_id) ticket.extend(timeout)
def put(self, req, resp, ticket): if req.headers.get("expect") == "100-continue": resp.send_info(http.CONTINUE) count = req.content_length resp.headers["content-length"] = count while count: chunk = req.read(1024 * 1024) if not chunk: raise http.Error(http.BAD_REQUEST, "Client disconnected") resp.write(chunk) count -= len(chunk)
def put(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") size = req.content_length if size is None: raise http.Error( http.BAD_REQUEST, "Content-Length header is required") offset = req.content_range.first if req.content_range else 0 # For backward compatibility, we flush by default. flush = validate.enum(req.query, "flush", ("y", "n"), default="y") flush = (flush == "y") try: ticket = auth.authorize(ticket_id, "write") except errors.AuthorizationError as e: raise http.Error(http.FORBIDDEN, str(e)) validate.allowed_range(offset, size, ticket) log.info( "[%s] WRITE size=%d offset=%d flush=%s ticket=%s", req.client_addr, size, offset, flush, ticket_id) op = ops.Receive( self._backend(req, ticket), req, size, offset=offset, flush=flush, buffersize=self.config.daemon.buffer_size, clock=req.clock) try: ticket.run(op) except errors.PartialContent as e: raise http.Error(http.BAD_REQUEST, str(e))
def post(self, req, resp): """ Start of stop the profiler. """ if yappi is None: raise http.Error(http.NOT_FOUND, "yappi is not installed") run = validate.enum(req.query, "run", ("y", "n")) if run == "y": clock = validate.enum(req.query, "clock", ("cpu", "wall"), default="cpu") self._start_profiling(clock) else: self._stop_profiling()
def get(self, req, resp): # Fail after sending the first part of the response. The # connection shold be closed. resp.headers["content-length"] = 1000 resp.write(b"Starting response...") raise http.Error(http.INTERNAL_SERVER_ERROR, "No more data for you!")
def get(self, req, resp, ticket_id): if not ticket_id: raise http.Error(http.BAD_REQUEST, "Ticket id is required") offset = 0 size = None if req.range: offset = req.range.first if offset < 0: # TODO: support suffix-byte-range-spec "bytes=-last". # See https://tools.ietf.org/html/rfc7233#section-2.1. raise http.Error( http.REQUESTED_RANGE_NOT_SATISFIABLE, "suffix-byte-range-spec not supported yet") if req.range.last is not None: # Add 1 to include the last byte in the payload. size = req.range.last - offset + 1 # TODO: validate size with actual image size. try: ticket = auth.authorize(ticket_id, "read") except errors.AuthorizationError as e: raise http.Error(http.FORBIDDEN, str(e)) backend = self._backend(req, ticket) if size is not None: validate.allowed_range(offset, size, ticket) validate.available_range(offset, size, ticket, backend) else: size = min(ticket.size, backend.size()) - offset log.info( "[%s] READ size=%d offset=%d ticket=%s", req.client_addr, size, offset, ticket_id) content_disposition = "attachment" if ticket.filename: content_disposition += "; filename=%s" % ticket.filename resp.headers["content-length"] = size resp.headers["content-type"] = "application/octet-stream" resp.headers["content-disposition"] = content_disposition if req.range: resp.status_code = http.PARTIAL_CONTENT resp.headers["content-range"] = "bytes %d-%d/%d" % ( offset, offset + size - 1, ticket.size) op = ops.Send( self._backend(req, ticket), resp, size, offset=offset, buffersize=self.config.daemon.buffer_size, clock=req.clock) try: ticket.run(op) except errors.PartialContent as e: raise http.Error(http.BAD_REQUEST, str(e))
def put(self, req, resp): offset = req.content_range.first if req.content_range else 0 self.file.seek(offset) self.file.write(req.read()) if req.length != 0: raise http.Error(http.BAD_REQUEST, "Unexpected EOF")
def get(self, req, resp): if yappi is None: raise http.Error(http.NOT_FOUND, "yappi is not installed") msg = {"running": yappi.is_running()} resp.send_json(msg)
def get(self, req, resp, name): if name not in req.context: raise http.Error(http.NOT_FOUND, "No such name {!r}".format(name)) value = req.context[name] resp.headers["content-length"] = len(value) resp.write(value)
def get(self, req, resp, name): raise http.Error(http.FORBIDDEN, "No data for you!")
def put(self, req, resp): # Fail after reading the entire request payload, so the server # should keep the connection open. req.read() raise http.Error(http.FORBIDDEN, "No data for you!")
def put(self, req, resp, name): # Raising without reading payload wil fail with EPIPE on the # client side. If the client is careful, it will get error 403. raise http.Error(http.FORBIDDEN, "No data for you!")