Example #1
0
	void start(const QVariant &vrequest, Mode mode)
	{
		outSeq = 0;
		outCredits = 0;
		quiet = false;

		ZhttpRequestPacket request;
		if(!request.fromVariant(vrequest))
		{
			log_warning("failed to parse zurl request");

			QVariantHash vhash = vrequest.toHash();
			rid = vhash.value("id").toByteArray();
			toAddress = vhash.value("from").toByteArray();
			QByteArray type = vhash.value("type").toByteArray();
			if(!toAddress.isEmpty() && type != "error" && type != "cancel")
			{
				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
			}
			else
			{
				cleanup();
				QMetaObject::invokeMethod(q, "finished", Qt::QueuedConnection);
			}

			return;
		}

		rid = request.id;
		toAddress = request.from;
		userData = request.userData;
		sentHeader = false;
		stuffToRead = false;
		bytesReceived = 0;

		ignorePolicies = request.ignorePolicies;

		if(request.uri.isEmpty())
		{
			log_warning("missing request uri");

			QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
			return;
		}

		QString scheme = request.uri.scheme();
		if(scheme == "https" || scheme == "http")
		{
			transport = HttpTransport;
		}
		else if(scheme == "wss" || scheme == "ws")
		{
			transport = WebSocketTransport;
		}
		else
		{
			log_warning("unsupported scheme");

			QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
			return;
		}

		if(transport == WebSocketTransport && mode != Worker::Stream)
		{
			log_warning("websocket must be used from stream interface");

			QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
			return;
		}

		int defaultPort;
		if(scheme == "https" || scheme == "wss")
			defaultPort = 443;
		else // http || wss
			defaultPort = 80;

		HttpHeaders headers = request.headers;

		if(transport == HttpTransport)
		{
			// fire and forget
			if(mode == Worker::Stream && (rid.isEmpty() || toAddress.isEmpty()))
				quiet = true;

			// streaming only allowed on streaming interface
			if(mode == Worker::Stream)
				outStream = request.stream;
			else
				outStream = false;

			if(request.method.isEmpty())
			{
				log_warning("missing request method");

				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
				return;
			}

			log_info("IN id=%s, %s %s", request.id.data(), qPrintable(request.method), request.uri.toEncoded().data());

			// inbound streaming must start with sequence number of 0
			if(mode == Worker::Stream && request.more && request.seq != 0)
			{
				log_warning("streamed input must start with seq 0");

				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
				return;
			}

			// can't use these two together
			if(mode == Worker::Single && request.more)
			{
				log_warning("cannot use streamed input on router interface");

				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
				return;
			}

			bodySent = false;

			inSeq = request.seq;

			if(!isAllowed(request.uri.host()) || (!request.connectHost.isEmpty() && !isAllowed(request.connectHost)))
			{
				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "policy-violation"));
				return;
			}

			QByteArray hostHeader = request.uri.host().toUtf8();

			// only tack on the port if it isn't being overridden
			int port = request.uri.port(defaultPort);
			if(request.connectPort == -1 && port != defaultPort)
				hostHeader += ":" + QByteArray::number(port);

			headers.removeAll("Host");
			headers += HttpHeader("Host", hostHeader);

			hreq = new HttpRequest(dns, this);
			connect(hreq, SIGNAL(nextAddress(const QHostAddress &)), SLOT(req_nextAddress(const QHostAddress &)));
			connect(hreq, SIGNAL(readyRead()), SLOT(req_readyRead()));
			connect(hreq, SIGNAL(bytesWritten(int)), SLOT(req_bytesWritten(int)));
			connect(hreq, SIGNAL(error()), SLOT(req_error()));

			maxResponseSize = request.maxSize;
			sessionTimeout = request.timeout;

			if(!request.connectHost.isEmpty())
				hreq->setConnectHost(request.connectHost);
			if(request.connectPort != -1)
				request.uri.setPort(request.connectPort);

			hreq->setIgnoreTlsErrors(request.ignoreTlsErrors);
			if(request.followRedirects)
				hreq->setFollowRedirects(8);

			if(request.credits != -1)
				outCredits += request.credits;
		}
		else // WebSocketTransport
		{
			log_info("IN id=%s, %s", request.id.data(), request.uri.toEncoded().data());

			// inbound streaming must start with sequence number of 0
			if(request.seq != 0)
			{
				log_warning("websocket input must start with seq 0");

				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
				return;
			}

			if(toAddress.isEmpty())
			{
				log_warning("websocket input must provide from address");

				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "bad-request"));
				return;
			}

			inSeq = request.seq;

			if(!isAllowed(request.uri.host()) || (!request.connectHost.isEmpty() && !isAllowed(request.connectHost)))
			{
				QMetaObject::invokeMethod(this, "respondError", Qt::QueuedConnection, Q_ARG(QByteArray, "policy-violation"));
				return;
			}

			QByteArray hostHeader = request.uri.host().toUtf8();

			// only tack on the port if it isn't being overridden
			int port = request.uri.port(defaultPort);
			if(request.connectPort == -1 && port != defaultPort)
				hostHeader += ":" + QByteArray::number(port);

			headers.removeAll("Host");
			headers += HttpHeader("Host", hostHeader);

			ws = new WebSocket(dns, this);
			connect(ws, SIGNAL(nextAddress(const QHostAddress &)), SLOT(req_nextAddress(const QHostAddress &)));
			connect(ws, SIGNAL(connected()), SLOT(ws_connected()));
			connect(ws, SIGNAL(readyRead()), SLOT(ws_readyRead()));
			connect(ws, SIGNAL(framesWritten(int)), SLOT(ws_framesWritten(int)));
			connect(ws, SIGNAL(peerClosing()), SLOT(ws_peerClosing()));
			connect(ws, SIGNAL(closed()), SLOT(ws_closed()));
			connect(ws, SIGNAL(error()), SLOT(ws_error()));

			if(!request.connectHost.isEmpty())
				ws->setConnectHost(request.connectHost);
			if(request.connectPort != -1)
				request.uri.setPort(request.connectPort);

			ws->setIgnoreTlsErrors(request.ignoreTlsErrors);
			ws->setMaxFrameSize(config->sessionBufferSize);

			if(request.credits != -1)
				outCredits += request.credits;
		}

		httpActivityTimer = new QTimer(this);
		connect(httpActivityTimer, SIGNAL(timeout()), SLOT(httpActivity_timeout()));
		httpActivityTimer->setSingleShot(true);
		httpActivityTimer->start(config->activityTimeout * 1000);

		if(sessionTimeout != -1)
		{
			httpSessionTimer = new QTimer(this);
			connect(httpSessionTimer, SIGNAL(timeout()), SLOT(httpSession_timeout()));
			httpSessionTimer->setSingleShot(true);
			httpSessionTimer->start(sessionTimeout);
		}

		if(transport == WebSocketTransport || (transport == HttpTransport && mode == Worker::Stream))
		{
			expireTimer = new QTimer(this);
			connect(expireTimer, SIGNAL(timeout()), SLOT(expire_timeout()));
			expireTimer->setSingleShot(true);
			expireTimer->start(SESSION_EXPIRE);

			keepAliveTimer = new QTimer(this);
			connect(keepAliveTimer, SIGNAL(timeout()), SLOT(keepAlive_timeout()));
			keepAliveTimer->start(SESSION_EXPIRE / 2);
		}

		if(transport == HttpTransport)
		{
			if(!request.body.isEmpty() && !request.more && !headers.contains("Content-Length"))
				headers += HttpHeader("Content-Length", QByteArray::number(request.body.size()));

			bool hasOrMightHaveBody = (!request.body.isEmpty() || request.more);

			hreq->start(request.method, request.uri, headers, hasOrMightHaveBody);

			if(hasOrMightHaveBody)
			{
				if(!request.body.isEmpty())
					hreq->writeBody(request.body);

				if(!request.more)
				{
					bodySent = true;
					hreq->endBody();
				}
			}
			else
				bodySent = true;

			if(mode == Stream)
			{
				if(request.more)
				{
					// send cts
					ZhttpResponsePacket resp;
					resp.type = ZhttpResponsePacket::Credit;
					resp.credits = config->sessionBufferSize;
					writeResponse(resp);
				}
				else
				{
					// send ack
					ZhttpResponsePacket resp;
					resp.type = ZhttpResponsePacket::KeepAlive;
					writeResponse(resp);
				}
			}
		}
		else // WebSocketTransport
		{
			ws->start(request.uri, headers);
		}
	}
Example #2
0
struct msg *
rsp_send_next(struct context *ctx, struct conn *conn)
{
    rstatus_t status;
    struct msg *msg, *pmsg; /* response and it's peer request */

    ASSERT((conn->client && !conn->proxy) || (conn->dnode_client && !conn->dnode_server));

    pmsg = TAILQ_FIRST(&conn->omsg_q);
    if (pmsg == NULL || !req_done(conn, pmsg)) {
        /* nothing is outstanding, initiate close? */
        if (pmsg == NULL && conn->eof) {
            conn->done = 1;
            log_debug(LOG_INFO, "c %d is done", conn->sd);
        }

        status = event_del_out(ctx->evb, conn);
        if (status != DN_OK) {
            conn->err = errno;
        }

        return NULL;
    }

    msg = conn->smsg;
    if (msg != NULL) {
        ASSERT(!msg->request && msg->peer != NULL);
        ASSERT(req_done(conn, msg->peer));
        pmsg = TAILQ_NEXT(msg->peer, c_tqe);
    }

    if (pmsg == NULL || !req_done(conn, pmsg)) {
        conn->smsg = NULL;
        return NULL;
    }
    ASSERT(pmsg->request && !pmsg->swallow);

    if (req_error(conn, pmsg)) {
        msg = rsp_make_error(ctx, conn, pmsg);
        if (msg == NULL) {
            conn->err = errno;
            return NULL;
        }
        msg->peer = pmsg;
        pmsg->peer = msg;
        if (!conn->dyn_mode) {
           stats_pool_incr(ctx, conn->owner, forward_error);
        } else {  //dyn_mode
           stats_pool_incr(ctx, conn->owner, peer_forward_error);
        }
    } else {
        msg = pmsg->peer;
    }
    ASSERT(!msg->request);

    conn->smsg = msg;

    log_debug(LOG_VVERB, "send next rsp %"PRIu64" on c %d", msg->id, conn->sd);

    return msg;
}
Example #3
0
struct msg *
rsp_send_next(struct context *ctx, struct conn *conn)
{
    rstatus_t status;
    struct msg *rsp, *req; /* response and it's peer request */

    ASSERT_LOG((conn->type == CONN_DNODE_PEER_CLIENT) ||
               (conn->type = CONN_CLIENT), "conn %s", conn_get_type_string(conn));

    req = TAILQ_FIRST(&conn->omsg_q);
    if (req == NULL || !req->selected_rsp) {
        /* nothing is outstanding, initiate close? */
        if (req == NULL && conn->eof) {
            conn->done = 1;
            log_debug(LOG_INFO, "c %d is done", conn->sd);
        }

        status = event_del_out(ctx->evb, conn);
        if (status != DN_OK) {
            conn->err = errno;
        }

        return NULL;
    }

    rsp = conn->smsg;
    if (rsp != NULL) {
        ASSERT(!rsp->request);
        ASSERT(rsp->peer != NULL);
        req = TAILQ_NEXT(rsp->peer, c_tqe);
    }

    if (req == NULL || !req_done(conn, req)) {
        conn->smsg = NULL;
        return NULL;
    }
    ASSERT(req->request && !req->swallow);

    if (req_error(conn, req)) {
        rsp = rsp_make_error(ctx, conn, req);
        if (rsp == NULL) {
            conn->err = errno;
            return NULL;
        }
        rsp->peer = req;
        req->selected_rsp = rsp;
        log_error("creating new error rsp %p", rsp);
        if (conn->dyn_mode) {
      	  stats_pool_incr(ctx, conn->owner, peer_forward_error);
        } else {
      	  stats_pool_incr(ctx, conn->owner, forward_error);
        }
    } else {
        rsp = req->selected_rsp;
    }
    ASSERT(!rsp->request);

    conn->smsg = rsp;

    if (log_loggable(LOG_VVERB)) {
       log_debug(LOG_VVERB, "send next rsp %"PRIu64" on c %d", rsp->id, conn->sd);
    }

    return rsp;
}
	void handleRequest(ZhttpRequest *_req, const QByteArray &jsonpCallback, const QByteArray &lastPart, const QByteArray &body)
	{
		connect(_req, SIGNAL(bytesWritten(int)), SLOT(req_bytesWritten(int)));
		connect(_req, SIGNAL(error()), SLOT(req_error()));

		if(lastPart == "xhr" || lastPart == "jsonp")
		{
			if(req)
			{
				QVariantList out;
				out += 2010;
				out += QString("Another connection still open");

				requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
				respondOk(_req, out, "c", jsonpCallback);
				return;
			}

			if(peerClosed)
			{
				QVariantList out;
				out += 3000;
				out += QString("Client already closed connection");

				requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
				respondOk(_req, out, "c", jsonpCallback);
				return;
			}

			req = _req;
			requests.insert(req, new RequestItem(req, jsonpCallback, RequestItem::Receive));
			keepAliveTimer->start(KEEPALIVE_TIMEOUT * 1000);

			tryWrite();
		}
		else if(lastPart == "xhr_send" || lastPart == "jsonp_send")
		{
			// only allow one outstanding send request at a time
			if(findFirstSendRequest())
			{
				requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
				respondError(_req, 400, "Bad Request", "Already sending");
				return;
			}

			QByteArray param;

			if(_req->requestMethod() == "POST")
			{
				if(lastPart == "xhr_send")
				{
					// assume json
					param = body;
				}
				else // jsonp_send
				{
					// assume form encoded
					foreach(const QByteArray &kv, body.split('&'))
					{
						int at = kv.indexOf('=');
						if(at == -1)
							continue;
						if(QUrl::fromPercentEncoding(kv.mid(0, at)) == "d")
						{
							param = QUrl::fromPercentEncoding(kv.mid(at + 1)).toUtf8();
							break;
						}
					}
				}
			}
			else // GET
			{
				param = _req->requestUri().queryItemValue("d").toUtf8();
			}

			QJson::Parser parser;
			bool ok;
			QVariant vmessages = parser.parse(param, &ok);
			if(!ok || vmessages.type() != QVariant::List)
			{
				requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
				respondError(_req, 400, "Bad Request", "Payload expected");
				return;
			}

			QList<Frame> frames;
			int bytes = 0;
			foreach(const QVariant &vmessage, vmessages.toList())
			{
				if(vmessage.type() != QVariant::String)
				{
					requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
					respondError(_req, 400, "Bad Request", "Payload expected");
					return;
				}

				QByteArray data = vmessage.toString().toUtf8();
				if(data.size() > BUFFER_SIZE)
				{
					requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
					respondError(_req, 400, "Bad Request", "Message too large");
					return;
				}

				frames += Frame(Frame::Text, data, false);
				bytes += data.size();
			}

			if(frames.isEmpty())
			{
				requests.insert(_req, new RequestItem(_req, jsonpCallback, RequestItem::Background, true));
				respondOk(_req, QString("ok"), jsonpCallback);
				return;
			}

			RequestItem *ri = new RequestItem(_req, jsonpCallback, RequestItem::Send);
			requests.insert(_req, ri);
			ri->sendFrames = frames;
			ri->sendBytes = bytes;

			tryRead();
		}