diff options
Diffstat (limited to 'src')
63 files changed, 1873 insertions, 1663 deletions
diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c index b59b62a6aa..8e7cb27cfd 100644 --- a/src/core/channel/client_channel.c +++ b/src/core/channel/client_channel.c @@ -51,7 +51,7 @@ typedef struct call_data call_data; -typedef struct { +typedef struct client_channel_channel_data { /** metadata context for this channel */ grpc_mdctx *mdctx; /** resolver for this channel */ diff --git a/src/core/client_config/lb_policies/pick_first.c b/src/core/client_config/lb_policies/pick_first.c index 28155d0fbc..4b3aaab09c 100644 --- a/src/core/client_config/lb_policies/pick_first.c +++ b/src/core/client_config/lb_policies/pick_first.c @@ -101,6 +101,9 @@ void pf_destroy(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol) { for (i = 0; i < p->num_subchannels; i++) { GRPC_SUBCHANNEL_UNREF(exec_ctx, p->subchannels[i], "pick_first"); } + if (p->selected) { + GRPC_SUBCHANNEL_UNREF(exec_ctx, p->selected, "picked_first"); + } grpc_connectivity_state_destroy(exec_ctx, &p->state_tracker); gpr_free(p->subchannels); gpr_mu_destroy(&p->mu); @@ -172,6 +175,35 @@ void pf_pick(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, } } +static void destroy_subchannels(grpc_exec_ctx *exec_ctx, void *arg, + int iomgr_success) { + pick_first_lb_policy *p = arg; + size_t i; + grpc_transport_op op; + size_t num_subchannels = p->num_subchannels; + grpc_subchannel **subchannels; + grpc_subchannel *exclude_subchannel; + + gpr_mu_lock(&p->mu); + subchannels = p->subchannels; + p->num_subchannels = 0; + p->subchannels = NULL; + exclude_subchannel = p->selected; + gpr_mu_unlock(&p->mu); + GRPC_LB_POLICY_UNREF(exec_ctx, &p->base, "destroy_subchannels"); + + for (i = 0; i < num_subchannels; i++) { + if (subchannels[i] != exclude_subchannel) { + memset(&op, 0, sizeof(op)); + op.disconnect = 1; + grpc_subchannel_process_transport_op(exec_ctx, subchannels[i], &op); + } + GRPC_SUBCHANNEL_UNREF(exec_ctx, subchannels[i], "pick_first"); + } + + gpr_free(subchannels); +} + static void pf_connectivity_changed(grpc_exec_ctx *exec_ctx, void *arg, int iomgr_success) { pick_first_lb_policy *p = arg; @@ -200,6 +232,11 @@ static void pf_connectivity_changed(grpc_exec_ctx *exec_ctx, void *arg, grpc_connectivity_state_set(exec_ctx, &p->state_tracker, GRPC_CHANNEL_READY, "connecting_ready"); p->selected = p->subchannels[p->checking_subchannel]; + GRPC_SUBCHANNEL_REF(p->selected, "picked_first"); + /* drop the pick list: we are connected now */ + GRPC_LB_POLICY_REF(&p->base, "destroy_subchannels"); + grpc_exec_ctx_enqueue(exec_ctx, grpc_closure_create(destroy_subchannels, p), 1); + /* update any calls that were waiting for a pick */ while ((pp = p->pending_picks)) { p->pending_picks = pp->next; *pp->target = p->selected; @@ -279,10 +316,15 @@ static void pf_broadcast(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, size_t i; size_t n; grpc_subchannel **subchannels; + grpc_subchannel *selected; gpr_mu_lock(&p->mu); n = p->num_subchannels; subchannels = gpr_malloc(n * sizeof(*subchannels)); + selected = p->selected; + if (selected) { + GRPC_SUBCHANNEL_REF(selected, "pf_broadcast_to_selected"); + } for (i = 0; i < n; i++) { subchannels[i] = p->subchannels[i]; GRPC_SUBCHANNEL_REF(subchannels[i], "pf_broadcast"); @@ -290,9 +332,14 @@ static void pf_broadcast(grpc_exec_ctx *exec_ctx, grpc_lb_policy *pol, gpr_mu_unlock(&p->mu); for (i = 0; i < n; i++) { + if (selected == subchannels[i]) continue; grpc_subchannel_process_transport_op(exec_ctx, subchannels[i], op); GRPC_SUBCHANNEL_UNREF(exec_ctx, subchannels[i], "pf_broadcast"); } + if (p->selected) { + grpc_subchannel_process_transport_op(exec_ctx, selected, op); + GRPC_SUBCHANNEL_UNREF(exec_ctx, selected, "pf_broadcast_to_selected"); + } gpr_free(subchannels); } diff --git a/src/core/iomgr/closure.c b/src/core/iomgr/closure.c index 3265425789..d91681990f 100644 --- a/src/core/iomgr/closure.c +++ b/src/core/iomgr/closure.c @@ -33,6 +33,8 @@ #include "src/core/iomgr/closure.h" +#include <grpc/support/alloc.h> + void grpc_closure_init(grpc_closure *closure, grpc_iomgr_cb_func cb, void *cb_arg) { closure->cb = cb; @@ -69,3 +71,25 @@ void grpc_closure_list_move(grpc_closure_list *src, grpc_closure_list *dst) { } src->head = src->tail = NULL; } + +typedef struct { + grpc_iomgr_cb_func cb; + void *cb_arg; + grpc_closure wrapper; +} wrapped_closure; + +static void closure_wrapper(grpc_exec_ctx *exec_ctx, void *arg, int success) { + wrapped_closure *wc = arg; + grpc_iomgr_cb_func cb = wc->cb; + void *cb_arg = wc->cb_arg; + gpr_free(wc); + cb(exec_ctx, cb_arg, success); +} + +grpc_closure *grpc_closure_create(grpc_iomgr_cb_func cb, void *cb_arg) { + wrapped_closure *wc = gpr_malloc(sizeof(*wc)); + wc->cb = cb; + wc->cb_arg = cb_arg; + grpc_closure_init(&wc->wrapper, closure_wrapper, wc); + return &wc->wrapper; +} diff --git a/src/core/iomgr/closure.h b/src/core/iomgr/closure.h index 982ffa4e1b..d812659af0 100644 --- a/src/core/iomgr/closure.h +++ b/src/core/iomgr/closure.h @@ -77,6 +77,9 @@ struct grpc_closure { void grpc_closure_init(grpc_closure *closure, grpc_iomgr_cb_func cb, void *cb_arg); +/* Create a heap allocated closure: try to avoid except for very rare events */ +grpc_closure *grpc_closure_create(grpc_iomgr_cb_func cb, void *cb_arg); + #define GRPC_CLOSURE_LIST_INIT \ { NULL, NULL } diff --git a/src/core/iomgr/fd_posix.c b/src/core/iomgr/fd_posix.c index b48b7f050a..231bc988a8 100644 --- a/src/core/iomgr/fd_posix.c +++ b/src/core/iomgr/fd_posix.c @@ -45,10 +45,8 @@ #include <grpc/support/log.h> #include <grpc/support/useful.h> -enum descriptor_state { - NOT_READY = 0, - READY = 1 -}; /* or a pointer to a closure to call */ +#define CLOSURE_NOT_READY ((grpc_closure *)0) +#define CLOSURE_READY ((grpc_closure *)1) /* We need to keep a freelist not because of any concerns of malloc performance * but instead so that implementations with multiple threads in (for example) @@ -88,14 +86,13 @@ static grpc_fd *alloc_fd(int fd) { gpr_mu_unlock(&fd_freelist_mu); if (r == NULL) { r = gpr_malloc(sizeof(grpc_fd)); - gpr_mu_init(&r->set_state_mu); - gpr_mu_init(&r->watcher_mu); + gpr_mu_init(&r->mu); } gpr_atm_rel_store(&r->refst, 1); - gpr_atm_rel_store(&r->readst, NOT_READY); - gpr_atm_rel_store(&r->writest, NOT_READY); - gpr_atm_rel_store(&r->shutdown, 0); + r->shutdown = 0; + r->read_closure = CLOSURE_NOT_READY; + r->write_closure = CLOSURE_NOT_READY; r->fd = fd; r->inactive_watcher_root.next = r->inactive_watcher_root.prev = &r->inactive_watcher_root; @@ -107,8 +104,7 @@ static grpc_fd *alloc_fd(int fd) { } static void destroy(grpc_fd *fd) { - gpr_mu_destroy(&fd->set_state_mu); - gpr_mu_destroy(&fd->watcher_mu); + gpr_mu_destroy(&fd->mu); gpr_free(fd); } @@ -173,39 +169,35 @@ int grpc_fd_is_orphaned(grpc_fd *fd) { return (gpr_atm_acq_load(&fd->refst) & 1) == 0; } -static void pollset_kick_locked(grpc_pollset *pollset) { - gpr_mu_lock(GRPC_POLLSET_MU(pollset)); - grpc_pollset_kick(pollset, NULL); - gpr_mu_unlock(GRPC_POLLSET_MU(pollset)); +static void pollset_kick_locked(grpc_fd_watcher *watcher) { + gpr_mu_lock(GRPC_POLLSET_MU(watcher->pollset)); + GPR_ASSERT(watcher->worker); + grpc_pollset_kick_ext(watcher->pollset, watcher->worker, + GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP); + gpr_mu_unlock(GRPC_POLLSET_MU(watcher->pollset)); } static void maybe_wake_one_watcher_locked(grpc_fd *fd) { if (fd->inactive_watcher_root.next != &fd->inactive_watcher_root) { - pollset_kick_locked(fd->inactive_watcher_root.next->pollset); + pollset_kick_locked(fd->inactive_watcher_root.next); } else if (fd->read_watcher) { - pollset_kick_locked(fd->read_watcher->pollset); + pollset_kick_locked(fd->read_watcher); } else if (fd->write_watcher) { - pollset_kick_locked(fd->write_watcher->pollset); + pollset_kick_locked(fd->write_watcher); } } -static void maybe_wake_one_watcher(grpc_fd *fd) { - gpr_mu_lock(&fd->watcher_mu); - maybe_wake_one_watcher_locked(fd); - gpr_mu_unlock(&fd->watcher_mu); -} - static void wake_all_watchers_locked(grpc_fd *fd) { grpc_fd_watcher *watcher; for (watcher = fd->inactive_watcher_root.next; watcher != &fd->inactive_watcher_root; watcher = watcher->next) { - pollset_kick_locked(watcher->pollset); + pollset_kick_locked(watcher); } if (fd->read_watcher) { - pollset_kick_locked(fd->read_watcher->pollset); + pollset_kick_locked(fd->read_watcher); } if (fd->write_watcher && fd->write_watcher != fd->read_watcher) { - pollset_kick_locked(fd->write_watcher->pollset); + pollset_kick_locked(fd->write_watcher); } } @@ -218,7 +210,7 @@ void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done, const char *reason) { fd->on_done_closure = on_done; shutdown(fd->fd, SHUT_RDWR); - gpr_mu_lock(&fd->watcher_mu); + gpr_mu_lock(&fd->mu); REF_BY(fd, 1, reason); /* remove active status, but keep referenced */ if (!has_watchers(fd)) { fd->closed = 1; @@ -227,7 +219,7 @@ void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done, } else { wake_all_watchers_locked(fd); } - gpr_mu_unlock(&fd->watcher_mu); + gpr_mu_unlock(&fd->mu); UNREF_BY(fd, 2, reason); /* drop the reference */ } @@ -247,136 +239,121 @@ void grpc_fd_ref(grpc_fd *fd) { ref_by(fd, 2); } void grpc_fd_unref(grpc_fd *fd) { unref_by(fd, 2); } #endif -static void notify_on(grpc_exec_ctx *exec_ctx, grpc_fd *fd, gpr_atm *st, - grpc_closure *closure) { - switch (gpr_atm_acq_load(st)) { - case NOT_READY: - /* There is no race if the descriptor is already ready, so we skip - the interlocked op in that case. As long as the app doesn't - try to set the same upcall twice (which it shouldn't) then - oldval should never be anything other than READY or NOT_READY. We - don't - check for user error on the fast path. */ - if (gpr_atm_rel_cas(st, NOT_READY, (gpr_intptr)closure)) { - /* swap was successful -- the closure will run after the next - set_ready call. NOTE: we don't have an ABA problem here, - since we should never have concurrent calls to the same - notify_on function. */ - maybe_wake_one_watcher(fd); - return; - } - /* swap was unsuccessful due to an intervening set_ready call. - Fall through to the READY code below */ - case READY: - GPR_ASSERT(gpr_atm_no_barrier_load(st) == READY); - gpr_atm_rel_store(st, NOT_READY); - grpc_exec_ctx_enqueue(exec_ctx, closure, - !gpr_atm_acq_load(&fd->shutdown)); - return; - default: /* WAITING */ - /* upcallptr was set to a different closure. This is an error! */ - gpr_log(GPR_ERROR, - "User called a notify_on function with a previous callback still " - "pending"); - abort(); +static void notify_on_locked(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure **st, grpc_closure *closure) { + if (*st == CLOSURE_NOT_READY) { + /* not ready ==> switch to a waiting state by setting the closure */ + *st = closure; + } else if (*st == CLOSURE_READY) { + /* already ready ==> queue the closure to run immediately */ + *st = CLOSURE_NOT_READY; + grpc_exec_ctx_enqueue(exec_ctx, closure, !fd->shutdown); + maybe_wake_one_watcher_locked(fd); + } else { + /* upcallptr was set to a different closure. This is an error! */ + gpr_log(GPR_ERROR, + "User called a notify_on function with a previous callback still " + "pending"); + abort(); } - gpr_log(GPR_ERROR, "Corrupt memory in &st->state"); - abort(); } -static void set_ready_locked(grpc_exec_ctx *exec_ctx, grpc_fd *fd, - gpr_atm *st) { - gpr_intptr state = gpr_atm_acq_load(st); - - switch (state) { - case READY: - /* duplicate ready, ignore */ - return; - case NOT_READY: - if (gpr_atm_rel_cas(st, NOT_READY, READY)) { - /* swap was successful -- the closure will run after the next - notify_on call. */ - return; - } - /* swap was unsuccessful due to an intervening set_ready call. - Fall through to the WAITING code below */ - state = gpr_atm_acq_load(st); - default: /* waiting */ - GPR_ASSERT(gpr_atm_no_barrier_load(st) != READY && - gpr_atm_no_barrier_load(st) != NOT_READY); - grpc_exec_ctx_enqueue(exec_ctx, (grpc_closure *)state, - !gpr_atm_acq_load(&fd->shutdown)); - gpr_atm_rel_store(st, NOT_READY); - return; +/* returns 1 if state becomes not ready */ +static int set_ready_locked(grpc_exec_ctx *exec_ctx, grpc_fd *fd, + grpc_closure **st) { + if (*st == CLOSURE_READY) { + /* duplicate ready ==> ignore */ + return 0; + } else if (*st == CLOSURE_NOT_READY) { + /* not ready, and not waiting ==> flag ready */ + *st = CLOSURE_READY; + return 0; + } else { + /* waiting ==> queue closure */ + grpc_exec_ctx_enqueue(exec_ctx, *st, !fd->shutdown); + *st = CLOSURE_NOT_READY; + return 1; } } -static void set_ready(grpc_exec_ctx *exec_ctx, grpc_fd *fd, gpr_atm *st) { +static void set_ready(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure **st) { /* only one set_ready can be active at once (but there may be a racing notify_on) */ - gpr_mu_lock(&fd->set_state_mu); + gpr_mu_lock(&fd->mu); set_ready_locked(exec_ctx, fd, st); - gpr_mu_unlock(&fd->set_state_mu); + gpr_mu_unlock(&fd->mu); } void grpc_fd_shutdown(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { - gpr_mu_lock(&fd->set_state_mu); + gpr_mu_lock(&fd->mu); GPR_ASSERT(!gpr_atm_no_barrier_load(&fd->shutdown)); - gpr_atm_rel_store(&fd->shutdown, 1); - set_ready_locked(exec_ctx, fd, &fd->readst); - set_ready_locked(exec_ctx, fd, &fd->writest); - gpr_mu_unlock(&fd->set_state_mu); + fd->shutdown = 1; + set_ready_locked(exec_ctx, fd, &fd->read_closure); + set_ready_locked(exec_ctx, fd, &fd->write_closure); + gpr_mu_unlock(&fd->mu); } void grpc_fd_notify_on_read(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *closure) { - notify_on(exec_ctx, fd, &fd->readst, closure); + gpr_mu_lock(&fd->mu); + notify_on_locked(exec_ctx, fd, &fd->read_closure, closure); + gpr_mu_unlock(&fd->mu); } void grpc_fd_notify_on_write(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *closure) { - notify_on(exec_ctx, fd, &fd->writest, closure); + gpr_mu_lock(&fd->mu); + notify_on_locked(exec_ctx, fd, &fd->write_closure, closure); + gpr_mu_unlock(&fd->mu); } gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, - gpr_uint32 read_mask, gpr_uint32 write_mask, - grpc_fd_watcher *watcher) { + grpc_pollset_worker *worker, gpr_uint32 read_mask, + gpr_uint32 write_mask, grpc_fd_watcher *watcher) { gpr_uint32 mask = 0; + grpc_closure *cur; + int requested; /* keep track of pollers that have requested our events, in case they change */ GRPC_FD_REF(fd, "poll"); - gpr_mu_lock(&fd->watcher_mu); + gpr_mu_lock(&fd->mu); + /* if we are shutdown, then don't add to the watcher set */ if (gpr_atm_no_barrier_load(&fd->shutdown)) { watcher->fd = NULL; watcher->pollset = NULL; - gpr_mu_unlock(&fd->watcher_mu); + watcher->worker = NULL; + gpr_mu_unlock(&fd->mu); GRPC_FD_UNREF(fd, "poll"); return 0; } + /* if there is nobody polling for read, but we need to, then start doing so */ - if (read_mask && !fd->read_watcher && - (gpr_uintptr)gpr_atm_acq_load(&fd->readst) > READY) { + cur = fd->read_closure; + requested = cur != CLOSURE_READY; + if (read_mask && fd->read_watcher == NULL && requested) { fd->read_watcher = watcher; mask |= read_mask; } /* if there is nobody polling for write, but we need to, then start doing so */ - if (write_mask && !fd->write_watcher && - (gpr_uintptr)gpr_atm_acq_load(&fd->writest) > READY) { + cur = fd->write_closure; + requested = cur != CLOSURE_READY; + if (write_mask && fd->write_watcher == NULL && requested) { fd->write_watcher = watcher; mask |= write_mask; } /* if not polling, remember this watcher in case we need someone to later */ - if (mask == 0) { + if (mask == 0 && worker != NULL) { watcher->next = &fd->inactive_watcher_root; watcher->prev = watcher->next->prev; watcher->next->prev = watcher->prev->next = watcher; } watcher->pollset = pollset; + watcher->worker = worker; watcher->fd = fd; - gpr_mu_unlock(&fd->watcher_mu); + gpr_mu_unlock(&fd->mu); return mask; } @@ -391,24 +368,39 @@ void grpc_fd_end_poll(grpc_exec_ctx *exec_ctx, grpc_fd_watcher *watcher, return; } - gpr_mu_lock(&fd->watcher_mu); + gpr_mu_lock(&fd->mu); + if (watcher == fd->read_watcher) { /* remove read watcher, kick if we still need a read */ was_polling = 1; - kick = kick || !got_read; + if (!got_read) { + kick = 1; + } fd->read_watcher = NULL; } if (watcher == fd->write_watcher) { /* remove write watcher, kick if we still need a write */ was_polling = 1; - kick = kick || !got_write; + if (!got_write) { + kick = 1; + } fd->write_watcher = NULL; } - if (!was_polling) { + if (!was_polling && watcher->worker != NULL) { /* remove from inactive list */ watcher->next->prev = watcher->prev; watcher->prev->next = watcher->next; } + if (got_read) { + if (set_ready_locked(exec_ctx, fd, &fd->read_closure)) { + kick = 1; + } + } + if (got_write) { + if (set_ready_locked(exec_ctx, fd, &fd->write_closure)) { + kick = 1; + } + } if (kick) { maybe_wake_one_watcher_locked(fd); } @@ -417,17 +409,17 @@ void grpc_fd_end_poll(grpc_exec_ctx *exec_ctx, grpc_fd_watcher *watcher, close(fd->fd); grpc_exec_ctx_enqueue(exec_ctx, fd->on_done_closure, 1); } - gpr_mu_unlock(&fd->watcher_mu); + gpr_mu_unlock(&fd->mu); GRPC_FD_UNREF(fd, "poll"); } void grpc_fd_become_readable(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { - set_ready(exec_ctx, fd, &fd->readst); + set_ready(exec_ctx, fd, &fd->read_closure); } void grpc_fd_become_writable(grpc_exec_ctx *exec_ctx, grpc_fd *fd) { - set_ready(exec_ctx, fd, &fd->writest); + set_ready(exec_ctx, fd, &fd->write_closure); } #endif diff --git a/src/core/iomgr/fd_posix.h b/src/core/iomgr/fd_posix.h index 089aa4d717..dc917ebbc0 100644 --- a/src/core/iomgr/fd_posix.h +++ b/src/core/iomgr/fd_posix.h @@ -46,6 +46,7 @@ typedef struct grpc_fd_watcher { struct grpc_fd_watcher *next; struct grpc_fd_watcher *prev; grpc_pollset *pollset; + grpc_pollset_worker *worker; grpc_fd *fd; } grpc_fd_watcher; @@ -58,8 +59,8 @@ struct grpc_fd { and just unref by 1 when we're ready to flag the object as orphaned */ gpr_atm refst; - gpr_mu set_state_mu; - gpr_atm shutdown; + gpr_mu mu; + int shutdown; int closed; /* The watcher list. @@ -84,18 +85,16 @@ struct grpc_fd { If at a later time there becomes need of a poller to poll, one of the inactive pollers may be kicked out of their poll loops to take that responsibility. */ - gpr_mu watcher_mu; grpc_fd_watcher inactive_watcher_root; grpc_fd_watcher *read_watcher; grpc_fd_watcher *write_watcher; - gpr_atm readst; - gpr_atm writest; + grpc_closure *read_closure; + grpc_closure *write_closure; struct grpc_fd *freelist_next; grpc_closure *on_done_closure; - grpc_closure *shutdown_closures[2]; grpc_iomgr_object iomgr_object; }; @@ -126,10 +125,12 @@ void grpc_fd_orphan(grpc_exec_ctx *exec_ctx, grpc_fd *fd, grpc_closure *on_done, fd's current interest (such as epoll) do not need to call this function. MUST NOT be called with a pollset lock taken */ gpr_uint32 grpc_fd_begin_poll(grpc_fd *fd, grpc_pollset *pollset, - gpr_uint32 read_mask, gpr_uint32 write_mask, - grpc_fd_watcher *rec); + grpc_pollset_worker *worker, gpr_uint32 read_mask, + gpr_uint32 write_mask, grpc_fd_watcher *rec); /* Complete polling previously started with grpc_fd_begin_poll - MUST NOT be called with a pollset lock taken */ + MUST NOT be called with a pollset lock taken + if got_read or got_write are 1, also does the become_{readable,writable} as + appropriate. */ void grpc_fd_end_poll(grpc_exec_ctx *exec_ctx, grpc_fd_watcher *rec, int got_read, int got_write); diff --git a/src/core/iomgr/pollset_multipoller_with_epoll.c b/src/core/iomgr/pollset_multipoller_with_epoll.c index faf0a6362b..ba9ba73f9d 100644 --- a/src/core/iomgr/pollset_multipoller_with_epoll.c +++ b/src/core/iomgr/pollset_multipoller_with_epoll.c @@ -72,7 +72,7 @@ static void finally_add_fd(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, /* We pretend to be polling whilst adding an fd to keep the fd from being closed during the add. This may result in a spurious wakeup being assigned to this pollset whilst adding, but that should be benign. */ - GPR_ASSERT(grpc_fd_begin_poll(fd, pollset, 0, 0, &watcher) == 0); + GPR_ASSERT(grpc_fd_begin_poll(fd, pollset, NULL, 0, 0, &watcher) == 0); if (watcher.fd != NULL) { ev.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); ev.data.ptr = fd; diff --git a/src/core/iomgr/pollset_multipoller_with_poll_posix.c b/src/core/iomgr/pollset_multipoller_with_poll_posix.c index 1356ebe7a0..faa6c14491 100644 --- a/src/core/iomgr/pollset_multipoller_with_poll_posix.c +++ b/src/core/iomgr/pollset_multipoller_with_poll_posix.c @@ -102,6 +102,9 @@ static void multipoll_with_poll_pollset_del_fd(grpc_exec_ctx *exec_ctx, static void multipoll_with_poll_pollset_maybe_work_and_unlock( grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, grpc_pollset_worker *worker, gpr_timespec deadline, gpr_timespec now) { +#define POLLOUT_CHECK (POLLOUT | POLLHUP | POLLERR) +#define POLLIN_CHECK (POLLIN | POLLHUP | POLLERR) + int timeout; int r; size_t i, j, fd_count; @@ -147,8 +150,8 @@ static void multipoll_with_poll_pollset_maybe_work_and_unlock( gpr_mu_unlock(&pollset->mu); for (i = 2; i < pfd_count; i++) { - pfds[i].events = (short)grpc_fd_begin_poll(watchers[i].fd, pollset, POLLIN, - POLLOUT, &watchers[i]); + pfds[i].events = (short)grpc_fd_begin_poll(watchers[i].fd, pollset, worker, + POLLIN, POLLOUT, &watchers[i]); } /* TODO(vpai): Consider first doing a 0 timeout poll here to avoid @@ -157,34 +160,29 @@ static void multipoll_with_poll_pollset_maybe_work_and_unlock( r = grpc_poll_function(pfds, pfd_count, timeout); GRPC_SCHEDULING_END_BLOCKING_REGION; - for (i = 2; i < pfd_count; i++) { - grpc_fd_end_poll(exec_ctx, &watchers[i], pfds[i].revents & POLLIN, - pfds[i].revents & POLLOUT); - } - if (r < 0) { - if (errno != EINTR) { - gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + for (i = 2; i < pfd_count; i++) { + grpc_fd_end_poll(exec_ctx, &watchers[i], 0, 0); } } else if (r == 0) { - /* do nothing */ + for (i = 2; i < pfd_count; i++) { + grpc_fd_end_poll(exec_ctx, &watchers[i], 0, 0); + } } else { - if (pfds[0].revents & POLLIN) { + if (pfds[0].revents & POLLIN_CHECK) { grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd); } - if (pfds[1].revents & POLLIN) { + if (pfds[1].revents & POLLIN_CHECK) { grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd); } for (i = 2; i < pfd_count; i++) { if (watchers[i].fd == NULL) { + grpc_fd_end_poll(exec_ctx, &watchers[i], 0, 0); continue; } - if (pfds[i].revents & (POLLIN | POLLHUP | POLLERR)) { - grpc_fd_become_readable(exec_ctx, watchers[i].fd); - } - if (pfds[i].revents & (POLLOUT | POLLHUP | POLLERR)) { - grpc_fd_become_writable(exec_ctx, watchers[i].fd); - } + grpc_fd_end_poll(exec_ctx, &watchers[i], pfds[i].revents & POLLIN_CHECK, + pfds[i].revents & POLLOUT_CHECK); } } diff --git a/src/core/iomgr/pollset_posix.c b/src/core/iomgr/pollset_posix.c index 42ee407938..d4362bb53a 100644 --- a/src/core/iomgr/pollset_posix.c +++ b/src/core/iomgr/pollset_posix.c @@ -98,31 +98,63 @@ static void push_front_worker(grpc_pollset *p, grpc_pollset_worker *worker) { worker->prev->next = worker->next->prev = worker; } -void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { +void grpc_pollset_kick_ext(grpc_pollset *p, + grpc_pollset_worker *specific_worker, + gpr_uint32 flags) { /* pollset->mu already held */ if (specific_worker != NULL) { if (specific_worker == GRPC_POLLSET_KICK_BROADCAST) { + GPR_ASSERT((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) == 0); for (specific_worker = p->root_worker.next; specific_worker != &p->root_worker; specific_worker = specific_worker->next) { grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); } p->kicked_without_pollers = 1; + return; } else if (gpr_tls_get(&g_current_thread_worker) != (gpr_intptr)specific_worker) { + if ((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) != 0) { + specific_worker->reevaluate_polling_on_wakeup = 1; + } + grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); + return; + } else if ((flags & GRPC_POLLSET_CAN_KICK_SELF) != 0) { + if ((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) != 0) { + specific_worker->reevaluate_polling_on_wakeup = 1; + } grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); + return; } } else if (gpr_tls_get(&g_current_thread_poller) != (gpr_intptr)p) { + GPR_ASSERT((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) == 0); specific_worker = pop_front_worker(p); if (specific_worker != NULL) { + if (gpr_tls_get(&g_current_thread_worker) == + (gpr_intptr)specific_worker) { + push_back_worker(p, specific_worker); + specific_worker = pop_front_worker(p); + if ((flags & GRPC_POLLSET_CAN_KICK_SELF) == 0 && + gpr_tls_get(&g_current_thread_worker) == + (gpr_intptr)specific_worker) { + push_back_worker(p, specific_worker); + return; + } + } push_back_worker(p, specific_worker); grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd); + return; } else { p->kicked_without_pollers = 1; + return; } } } +void grpc_pollset_kick(grpc_pollset *p, grpc_pollset_worker *specific_worker) { + grpc_pollset_kick_ext(p, specific_worker, 0); +} + /* global state management */ void grpc_pollset_global_init(void) { @@ -195,52 +227,88 @@ void grpc_pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, /* pollset->mu already held */ int added_worker = 0; int locked = 1; + int queued_work = 0; + int keep_polling = 0; /* this must happen before we (potentially) drop pollset->mu */ worker->next = worker->prev = NULL; + worker->reevaluate_polling_on_wakeup = 0; /* TODO(ctiller): pool these */ grpc_wakeup_fd_init(&worker->wakeup_fd); + /* If there's work waiting for the pollset to be idle, and the + pollset is idle, then do that work */ if (!grpc_pollset_has_workers(pollset) && !grpc_closure_list_empty(pollset->idle_jobs)) { grpc_exec_ctx_enqueue_list(exec_ctx, &pollset->idle_jobs); goto done; } + /* Check alarms - these are a global resource so we just ping + each time through on every pollset. + May update deadline to ensure timely wakeups. + TODO(ctiller): can this work be localized? */ if (grpc_timer_check(exec_ctx, now, &deadline)) { gpr_mu_unlock(&pollset->mu); locked = 0; goto done; } + /* If we're shutting down then we don't execute any extended work */ if (pollset->shutting_down) { goto done; } + /* Give do_promote priority so we don't starve it out */ if (pollset->in_flight_cbs) { - /* Give do_promote priority so we don't starve it out */ gpr_mu_unlock(&pollset->mu); locked = 0; goto done; } - if (!pollset->kicked_without_pollers) { - push_front_worker(pollset, worker); - added_worker = 1; - gpr_tls_set(&g_current_thread_poller, (gpr_intptr)pollset); - gpr_tls_set(&g_current_thread_worker, (gpr_intptr)worker); - pollset->vtable->maybe_work_and_unlock(exec_ctx, pollset, worker, deadline, - now); - locked = 0; - gpr_tls_set(&g_current_thread_poller, 0); - gpr_tls_set(&g_current_thread_worker, 0); - } else { - pollset->kicked_without_pollers = 0; - } -done: - if (!locked) { - grpc_exec_ctx_flush(exec_ctx); - gpr_mu_lock(&pollset->mu); - locked = 1; + /* Start polling, and keep doing so while we're being asked to + re-evaluate our pollers (this allows poll() based pollers to + ensure they don't miss wakeups) */ + keep_polling = 1; + while (keep_polling) { + keep_polling = 0; + if (!pollset->kicked_without_pollers) { + if (!added_worker) { + push_front_worker(pollset, worker); + added_worker = 1; + } + gpr_tls_set(&g_current_thread_poller, (gpr_intptr)pollset); + gpr_tls_set(&g_current_thread_worker, (gpr_intptr)worker); + pollset->vtable->maybe_work_and_unlock(exec_ctx, pollset, worker, + deadline, now); + locked = 0; + gpr_tls_set(&g_current_thread_poller, 0); + gpr_tls_set(&g_current_thread_worker, 0); + } else { + pollset->kicked_without_pollers = 0; + } + /* Finished execution - start cleaning up. + Note that we may arrive here from outside the enclosing while() loop. + In that case we won't loop though as we haven't added worker to the + worker list, which means nobody could ask us to re-evaluate polling). */ + done: + if (!locked) { + queued_work |= grpc_exec_ctx_flush(exec_ctx); + gpr_mu_lock(&pollset->mu); + locked = 1; + } + /* If we're forced to re-evaluate polling (via grpc_pollset_kick with + GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) then we land here and force + a loop */ + if (worker->reevaluate_polling_on_wakeup) { + worker->reevaluate_polling_on_wakeup = 0; + pollset->kicked_without_pollers = 0; + if (queued_work) { + /* If there's queued work on the list, then set the deadline to be + immediate so we get back out of the polling loop quickly */ + deadline = gpr_inf_past(GPR_CLOCK_MONOTONIC); + } + keep_polling = 1; + } } - grpc_wakeup_fd_destroy(&worker->wakeup_fd); if (added_worker) { remove_worker(pollset, worker); } + grpc_wakeup_fd_destroy(&worker->wakeup_fd); if (pollset->shutting_down) { if (grpc_pollset_has_workers(pollset)) { grpc_pollset_kick(pollset, NULL); @@ -454,6 +522,9 @@ static void basic_pollset_maybe_work_and_unlock(grpc_exec_ctx *exec_ctx, grpc_pollset_worker *worker, gpr_timespec deadline, gpr_timespec now) { +#define POLLOUT_CHECK (POLLOUT | POLLHUP | POLLERR) +#define POLLIN_CHECK (POLLIN | POLLHUP | POLLERR) + struct pollfd pfd[3]; grpc_fd *fd; grpc_fd_watcher fd_watcher; @@ -479,8 +550,8 @@ static void basic_pollset_maybe_work_and_unlock(grpc_exec_ctx *exec_ctx, pfd[2].revents = 0; GRPC_FD_REF(fd, "basicpoll_begin"); gpr_mu_unlock(&pollset->mu); - pfd[2].events = - (short)grpc_fd_begin_poll(fd, pollset, POLLIN, POLLOUT, &fd_watcher); + pfd[2].events = (short)grpc_fd_begin_poll(fd, pollset, worker, POLLIN, + POLLOUT, &fd_watcher); if (pfd[2].events != 0) { nfds++; } @@ -497,31 +568,27 @@ static void basic_pollset_maybe_work_and_unlock(grpc_exec_ctx *exec_ctx, GRPC_SCHEDULING_END_BLOCKING_REGION; GRPC_TIMER_MARK(GRPC_PTAG_POLL_FINISHED, r); - if (fd) { - grpc_fd_end_poll(exec_ctx, &fd_watcher, pfd[2].revents & POLLIN, - pfd[2].revents & POLLOUT); - } - if (r < 0) { - if (errno != EINTR) { - gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + gpr_log(GPR_ERROR, "poll() failed: %s", strerror(errno)); + if (fd) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, 0, 0); } } else if (r == 0) { - /* do nothing */ + if (fd) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, 0, 0); + } } else { - if (pfd[0].revents & POLLIN) { + if (pfd[0].revents & POLLIN_CHECK) { grpc_wakeup_fd_consume_wakeup(&grpc_global_wakeup_fd); } - if (pfd[1].revents & POLLIN) { + if (pfd[1].revents & POLLIN_CHECK) { grpc_wakeup_fd_consume_wakeup(&worker->wakeup_fd); } if (nfds > 2) { - if (pfd[2].revents & (POLLIN | POLLHUP | POLLERR)) { - grpc_fd_become_readable(exec_ctx, fd); - } - if (pfd[2].revents & (POLLOUT | POLLHUP | POLLERR)) { - grpc_fd_become_writable(exec_ctx, fd); - } + grpc_fd_end_poll(exec_ctx, &fd_watcher, pfd[2].revents & POLLIN_CHECK, + pfd[2].revents & POLLOUT_CHECK); + } else if (fd) { + grpc_fd_end_poll(exec_ctx, &fd_watcher, 0, 0); } } diff --git a/src/core/iomgr/pollset_posix.h b/src/core/iomgr/pollset_posix.h index 83c5258539..34f76db2af 100644 --- a/src/core/iomgr/pollset_posix.h +++ b/src/core/iomgr/pollset_posix.h @@ -50,6 +50,7 @@ struct grpc_fd; typedef struct grpc_pollset_worker { grpc_wakeup_fd wakeup_fd; + int reevaluate_polling_on_wakeup; struct grpc_pollset_worker *next; struct grpc_pollset_worker *prev; } grpc_pollset_worker; @@ -111,6 +112,16 @@ void grpc_kick_drain(grpc_pollset *p); int grpc_poll_deadline_to_millis_timeout(gpr_timespec deadline, gpr_timespec now); +/* Allow kick to wakeup the currently polling worker */ +#define GRPC_POLLSET_CAN_KICK_SELF 1 +/* Force the wakee to repoll when awoken */ +#define GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP 2 +/* As per grpc_pollset_kick, with an extended set of flags (defined above) + -- mostly for fd_posix's use. */ +void grpc_pollset_kick_ext(grpc_pollset *p, + grpc_pollset_worker *specific_worker, + gpr_uint32 flags); + /* turn a pollset into a multipoller: platform specific */ typedef void (*grpc_platform_become_multipoller_type)(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset, diff --git a/src/core/iomgr/tcp_server_windows.c b/src/core/iomgr/tcp_server_windows.c index db3319b3c6..3fea8b5b35 100644 --- a/src/core/iomgr/tcp_server_windows.c +++ b/src/core/iomgr/tcp_server_windows.c @@ -336,6 +336,8 @@ static void on_accept(grpc_exec_ctx *exec_ctx, void *arg, int from_iocp) { peer_name_string); gpr_free(fd_name); gpr_free(peer_name_string); + } else { + closesocket(sock); } } diff --git a/src/core/iomgr/udp_server.c b/src/core/iomgr/udp_server.c index 1304f2067e..9903e970e6 100644 --- a/src/core/iomgr/udp_server.c +++ b/src/core/iomgr/udp_server.c @@ -278,7 +278,7 @@ static void on_read(grpc_exec_ctx *exec_ctx, void *arg, int success) { /* Tell the registered callback that data is available to read. */ GPR_ASSERT(sp->read_cb); - sp->read_cb(sp->emfd, sp->server->grpc_server); + sp->read_cb(exec_ctx, sp->emfd, sp->server->grpc_server); /* Re-arm the notification event so we get another chance to read. */ grpc_fd_notify_on_read(exec_ctx, sp->emfd, &sp->read_closure); diff --git a/src/core/iomgr/udp_server.h b/src/core/iomgr/udp_server.h index dbbe097109..de5736c426 100644 --- a/src/core/iomgr/udp_server.h +++ b/src/core/iomgr/udp_server.h @@ -43,7 +43,8 @@ typedef struct grpc_server grpc_server; typedef struct grpc_udp_server grpc_udp_server; /* Called when data is available to read from the socket. */ -typedef void (*grpc_udp_server_read_cb)(grpc_fd *emfd, grpc_server *server); +typedef void (*grpc_udp_server_read_cb)(grpc_exec_ctx *exec_ctx, grpc_fd *emfd, + grpc_server *server); /* Create a server, initially not bound to any ports */ grpc_udp_server *grpc_udp_server_create(void); diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 08626108d0..d687cca2e0 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -425,7 +425,12 @@ static grpc_cq_completion *allocate_completion(grpc_call *call) { if (call->allocated_completions & (1u << i)) { continue; } - call->allocated_completions |= (gpr_uint8)(1u << i); + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + call->allocated_completions = + (gpr_uint8)(call->allocated_completions | (1u << i)); gpr_mu_unlock(&call->completion_mu); return &call->completions[i]; } @@ -736,7 +741,11 @@ static void finish_live_ioreq_op(grpc_call *call, grpc_ioreq_op op, size_t i; /* ioreq is live: we need to do something */ master = &call->masters[master_set]; - master->complete_mask |= (gpr_uint16)(1u << op); + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + master->complete_mask = (gpr_uint16)(master->complete_mask | (1u << op)); if (!success) { master->success = 0; } @@ -927,6 +936,7 @@ static int add_slice_to_message(grpc_call *call, gpr_slice slice) { } /* we have to be reading a message to know what to do here */ if (!call->reading_message) { + gpr_slice_unref(slice); cancel_with_status(call, GRPC_STATUS_INVALID_ARGUMENT, "Received payload data while not reading a message"); return 0; @@ -1246,7 +1256,11 @@ static grpc_call_error start_ioreq(grpc_call *call, const grpc_ioreq *reqs, GRPC_MDSTR_REF(reqs[i].data.send_status.details)); } } - have_ops |= (gpr_uint16)(1u << op); + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + have_ops = (gpr_uint16)(have_ops | (1u << op)); call->request_data[op] = data; call->request_flags[op] = reqs[i].flags; diff --git a/src/core/transport/chttp2/bin_encoder.c b/src/core/transport/chttp2/bin_encoder.c index f1bbf9aa91..9c9070ede4 100644 --- a/src/core/transport/chttp2/bin_encoder.c +++ b/src/core/transport/chttp2/bin_encoder.c @@ -185,8 +185,12 @@ gpr_slice grpc_chttp2_huffman_compress(gpr_slice input) { } if (temp_length) { - *out++ = (gpr_uint8)(temp << (8u - temp_length)) | - (gpr_uint8)(0xffu >> temp_length); + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + *out++ = (gpr_uint8)((gpr_uint8)(temp << (8u - temp_length)) | + (gpr_uint8)(0xffu >> temp_length)); } GPR_ASSERT(out == GPR_SLICE_END_PTR(output)); @@ -265,8 +269,12 @@ gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input) { } if (out.temp_length) { - *out.out++ = (gpr_uint8)(out.temp << (8u - out.temp_length)) | - (gpr_uint8)(0xffu >> out.temp_length); + /* NB: the following integer arithmetic operation needs to be in its + * expanded form due to the "integral promotion" performed (see section + * 3.2.1.1 of the C89 draft standard). A cast to the smaller container type + * is then required to avoid the compiler warning */ + *out.out++ = (gpr_uint8)((gpr_uint8)(out.temp << (8u - out.temp_length)) | + (gpr_uint8)(0xffu >> out.temp_length)); } GPR_ASSERT(out.out <= GPR_SLICE_END_PTR(output)); diff --git a/src/core/tsi/fake_transport_security.c b/src/core/tsi/fake_transport_security.c index cbb6f17ae1..a40268a7f0 100644 --- a/src/core/tsi/fake_transport_security.c +++ b/src/core/tsi/fake_transport_security.c @@ -118,10 +118,10 @@ static gpr_uint32 load32_little_endian(const unsigned char *buf) { } static void store32_little_endian(gpr_uint32 value, unsigned char *buf) { - buf[3] = (unsigned char)(value >> 24) & 0xFF; - buf[2] = (unsigned char)(value >> 16) & 0xFF; - buf[1] = (unsigned char)(value >> 8) & 0xFF; - buf[0] = (unsigned char)(value)&0xFF; + buf[3] = (unsigned char)((value >> 24) & 0xFF); + buf[2] = (unsigned char)((value >> 16) & 0xFF); + buf[1] = (unsigned char)((value >> 8) & 0xFF); + buf[0] = (unsigned char)((value) & 0xFF); } static void tsi_fake_frame_reset(tsi_fake_frame *frame, int needs_draining) { diff --git a/src/node/.istanbul.yml b/src/node/.istanbul.yml deleted file mode 100644 index 9ff1379f51..0000000000 --- a/src/node/.istanbul.yml +++ /dev/null @@ -1,6 +0,0 @@ -reporting: - watermarks: - statements: [80, 95] - lines: [80, 95] - functions: [80, 95] - branches: [80, 95] diff --git a/src/node/LICENSE b/src/node/LICENSE deleted file mode 100644 index 0209b570e1..0000000000 --- a/src/node/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2015, Google Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/node/README.md b/src/node/README.md index 7719d08290..5d89e2228d 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -5,51 +5,19 @@ Beta ## PREREQUISITES - `node`: This requires `node` to be installed. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package. -- [homebrew][] on Mac OS X. These simplify the installation of the gRPC C core. ## INSTALLATION -**Linux (Debian):** - -Add [Debian jessie-backports][] to your `sources.list` file. Example: - -```sh -echo "deb http://http.debian.net/debian jessie-backports main" | \ -sudo tee -a /etc/apt/sources.list -``` - -Install the gRPC Debian package - -```sh -sudo apt-get update -sudo apt-get install libgrpc-dev -``` - Install the gRPC NPM package ```sh npm install grpc ``` -**Mac OS X** - -Install [homebrew][]. Run the following command to install gRPC Node.js. -```sh -$ curl -fsSL https://goo.gl/getgrpc | bash -s nodejs -``` -This will download and run the [gRPC install script][], then install the latest version of gRPC Nodejs npm package. - ## BUILD FROM SOURCE 1. Clone [the grpc Git Repository](https://github.com/grpc/grpc). - 2. Follow the instructions in the `INSTALL` file in the root of that repository to install the C core library that this package depends on. 3. Run `npm install`. -If you install the gRPC C core library in a custom location, then you need to set some environment variables to install this library. The command will look like this: - -```sh -CXXFLAGS=-I<custom location>/include LDFLAGS=-L<custom location>/lib npm install [grpc] -``` - ## TESTING To run the test suite, simply run `npm test` in the install location. @@ -110,7 +78,3 @@ ServerCredentials ``` An object with factory methods for creating credential objects for servers. - -[homebrew]:http://brew.sh -[gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install -[Debian jessie-backports]:http://backports.debian.org/Instructions/ diff --git a/src/node/bin/README.md b/src/node/bin/README.md deleted file mode 100644 index 2f856e428e..0000000000 --- a/src/node/bin/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Command Line Tools - -# Service Packager - -The command line tool `bin/service_packager`, when called with the following command line: - -```bash -service_packager proto_file -o output_path -n name -v version [-i input_path...] -``` - -Populates `output_path` with a node package consisting of a `package.json` populated with `name` and `version`, an `index.js`, a `LICENSE` file copied from gRPC, and a `service.json`, which is compiled from `proto_file` and the given `input_path`s. `require('output_path')` returns an object that is equivalent to - -```js -{ client: require('grpc').load('service.json'), - auth: require('google-auth-library') } -``` diff --git a/src/node/bin/service_packager b/src/node/bin/service_packager deleted file mode 100755 index c7f2460997..0000000000 --- a/src/node/bin/service_packager +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -require(__dirname+'/../cli/service_packager.js').main(process.argv.slice(2));
\ No newline at end of file diff --git a/src/node/binding.gyp b/src/node/binding.gyp deleted file mode 100644 index 247719e981..0000000000 --- a/src/node/binding.gyp +++ /dev/null @@ -1,100 +0,0 @@ -{ - "variables" : { - 'config': '<!(echo $CONFIG)' - }, - "targets" : [ - { - 'include_dirs': [ - "<!(node -e \"require('nan')\")" - ], - 'cflags': [ - '-std=c++0x', - '-Wall', - '-pthread', - '-g', - '-zdefs', - '-Werror', - '-Wno-error=deprecated-declarations' - ], - 'ldflags': [ - '-g' - ], - "conditions": [ - ['OS != "win"', { - 'variables': { - 'pkg_config_grpc': '<!(pkg-config --exists grpc >/dev/null 2>&1 && echo true || echo false)' - }, - 'conditions': [ - ['config=="gcov"', { - 'cflags': [ - '-ftest-coverage', - '-fprofile-arcs', - '-O0' - ], - 'ldflags': [ - '-ftest-coverage', - '-fprofile-arcs' - ] - } - ], - ['pkg_config_grpc == "true"', { - 'link_settings': { - 'libraries': [ - '<!@(pkg-config --libs-only-l --static grpc)' - ] - }, - 'cflags': [ - '<!@(pkg-config --cflags grpc)' - ], - 'libraries': [ - '<!@(pkg-config --libs-only-L --static grpc)' - ], - 'ldflags': [ - '<!@(pkg-config --libs-only-other --static grpc)' - ] - }, { - 'link_settings': { - 'libraries': [ - '-lpthread', - '-lgrpc', - '-lgpr' - ], - }, - 'conditions':[ - ['OS != "mac"', { - 'link_settings': { - 'libraries': [ - '-lrt' - ] - } - }] - ] - } - ] - ] - }], - ['OS == "mac"', { - 'xcode_settings': { - 'MACOSX_DEPLOYMENT_TARGET': '10.9', - 'OTHER_CFLAGS': [ - '-std=c++11', - '-stdlib=libc++' - ] - } - }] - ], - "target_name": "grpc", - "sources": [ - "ext/byte_buffer.cc", - "ext/call.cc", - "ext/channel.cc", - "ext/completion_queue_async_worker.cc", - "ext/credentials.cc", - "ext/node_grpc.cc", - "ext/server.cc", - "ext/server_credentials.cc", - "ext/timeval.cc" - ] - } - ] -} diff --git a/src/node/cli/service_packager.js b/src/node/cli/service_packager.js deleted file mode 100644 index c92c450a01..0000000000 --- a/src/node/cli/service_packager.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * - * Copyright 2015, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -'use strict'; - -var fs = require('fs'); -var path = require('path'); - -var _ = require('lodash'); -var async = require('async'); -var pbjs = require('protobufjs/cli/pbjs'); -var parseArgs = require('minimist'); -var Mustache = require('mustache'); - -var package_json = require('../package.json'); - -var template_path = path.resolve(__dirname, 'service_packager'); - -var package_tpl_path = path.join(template_path, 'package.json.template'); - -var arg_format = { - string: ['include', 'out', 'name', 'version'], - alias: { - include: 'i', - out: 'o', - name: 'n', - version: 'v' - } -}; - -// TODO(mlumish): autogenerate README.md from proto file - -/** - * Render package.json file from template using provided parameters. - * @param {Object} params Map of parameter names to values - * @param {function(Error, string)} callback Callback to pass rendered template - * text to - */ -function generatePackage(params, callback) { - fs.readFile(package_tpl_path, {encoding: 'utf-8'}, function(err, template) { - if (err) { - callback(err); - } else { - var rendered = Mustache.render(template, params); - callback(null, rendered); - } - }); -} - -/** - * Copy a file - * @param {string} src_path The filepath to copy from - * @param {string} dest_path The filepath to copy to - */ -function copyFile(src_path, dest_path) { - fs.createReadStream(src_path).pipe(fs.createWriteStream(dest_path)); -} - -/** - * Run the script. Copies the index.js and LICENSE files to the output path, - * renders the package.json template to the output path, and generates a - * service.json file from the input proto files using pbjs. The arguments are - * taken directly from the command line, and handled as follows: - * -i (--include) : An include path for pbjs (can be dpulicated) - * -o (--output): The output path - * -n (--name): The name of the package - * -v (--version): The package version - * @param {Array} argv The argument vector - */ -function main(argv) { - var args = parseArgs(argv, arg_format); - var out_path = path.resolve(args.out); - var include_dirs = []; - if (args.include) { - include_dirs = _.map(_.flatten([args.include]), function(p) { - return path.resolve(p); - }); - } - args.grpc_version = package_json.version; - generatePackage(args, function(err, rendered) { - if (err) throw err; - fs.writeFile(path.join(out_path, 'package.json'), rendered, function(err) { - if (err) throw err; - }); - }); - copyFile(path.join(template_path, 'index.js'), - path.join(out_path, 'index.js')); - copyFile(path.join(__dirname, '..', 'LICENSE'), - path.join(out_path, 'LICENSE')); - - var service_stream = fs.createWriteStream(path.join(out_path, - 'service.json')); - var pbjs_args = _.flatten(['node', 'pbjs', - args._[0], - '-legacy', - _.map(include_dirs, function(dir) { - return "-path=" + dir; - })]); - var old_stdout = process.stdout; - process.__defineGetter__('stdout', function() { - return service_stream; - }); - var pbjs_status = pbjs.main(pbjs_args); - process.__defineGetter__('stdout', function() { - return old_stdout; - }); - if (pbjs_status !== pbjs.STATUS_OK) { - throw new Error('pbjs failed with status code ' + pbjs_status); - } -} - -exports.main = main; diff --git a/src/node/cli/service_packager/index.js b/src/node/cli/service_packager/index.js deleted file mode 100644 index 811e08b89a..0000000000 --- a/src/node/cli/service_packager/index.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * Copyright 2015, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -var grpc = require('grpc'); -exports.client = grpc.load(__dirname + '/service.json', 'json'); -exports.auth = require('google-auth-library'); diff --git a/src/node/cli/service_packager/package.json.template b/src/node/cli/service_packager/package.json.template deleted file mode 100644 index 9f9019172e..0000000000 --- a/src/node/cli/service_packager/package.json.template +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "{{{name}}}", - "version": "{{{version}}}", - "author": "Google Inc.", - "description": "Client library for {{{name}}} built on gRPC", - "license": "Apache-2.0", - "dependencies": { - "grpc": "{{{grpc_version}}}", - "google-auth-library": "^0.9.2" - }, - "main": "index.js", - "files": [ - "LICENSE", - "index.js", - "service.json" - ] -} diff --git a/src/node/examples/stock.proto b/src/node/examples/stock.proto deleted file mode 100644 index 5ee2bcbce6..0000000000 --- a/src/node/examples/stock.proto +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -syntax = "proto3"; - -package examples; - -// Protocol type definitions -message StockRequest { - string symbol = 1; - int32 num_trades_to_watch = 2; -} - -message StockReply { - float price = 1; - string symbol = 2; -} - - -// Interface exported by the server -service Stock { - // Simple blocking RPC - rpc GetLastTradePrice(StockRequest) returns (StockReply) { - } - // Bidirectional streaming RPC - rpc GetLastTradePriceMultiple(stream StockRequest) returns - (stream StockReply) { - } - // Unidirectional server-to-client streaming RPC - rpc WatchFutureTrades(StockRequest) returns (stream StockReply) { - } - // Unidirectional client-to-server streaming RPC - rpc GetHighestTradePrice(stream StockRequest) returns (StockReply) { - } - -} diff --git a/src/node/examples/stock_client.js b/src/node/examples/stock_client.js deleted file mode 100644 index ab9b050e9b..0000000000 --- a/src/node/examples/stock_client.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * - * Copyright 2015, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -var grpc = require('..'); -var examples = grpc.load(__dirname + '/stock.proto').examples; - -/** - * This exports a client constructor for the Stock service. The usage looks like - * - * var StockClient = require('stock_client.js'); - * var stockClient = new StockClient(server_address, - * grpc.Credentials.createInsecure()); - * stockClient.getLastTradePrice({symbol: 'GOOG'}, function(error, response) { - * console.log(error || response); - * }); - */ -module.exports = examples.Stock; diff --git a/src/node/examples/stock_server.js b/src/node/examples/stock_server.js deleted file mode 100644 index 12e5479584..0000000000 --- a/src/node/examples/stock_server.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * - * Copyright 2015, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -'use strict'; - -var _ = require('lodash'); -var grpc = require('..'); -var examples = grpc.load(__dirname + '/stock.proto').examples; - -function getLastTradePrice(call, callback) { - callback(null, {symbol: call.request.symbol, price: 88}); -} - -function watchFutureTrades(call) { - for (var i = 0; i < call.request.num_trades_to_watch; i++) { - call.write({price: 88.00 + i * 10.00}); - } - call.end(); -} - -function getHighestTradePrice(call, callback) { - var trades = []; - call.on('data', function(data) { - trades.push({symbol: data.symbol, price: _.random(0, 100)}); - }); - call.on('end', function() { - if(_.isEmpty(trades)) { - callback(null, {}); - } else { - callback(null, _.max(trades, function(trade){return trade.price;})); - } - }); -} - -function getLastTradePriceMultiple(call) { - call.on('data', function(data) { - call.write({price: 88}); - }); - call.on('end', function() { - call.end(); - }); -} - -var stockServer = new grpc.Server(); -stockServer.addProtoService(examples.Stock.service, { - getLastTradePrice: getLastTradePrice, - getLastTradePriceMultiple: getLastTradePriceMultiple, - watchFutureTrades: watchFutureTrades, - getHighestTradePrice: getHighestTradePrice -}); - -if (require.main === module) { - stockServer.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure()); - stockServer.start(); -} - -module.exports = stockServer; diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc index b08a9f96d8..b63e294f9a 100644 --- a/src/node/ext/call.cc +++ b/src/node/ext/call.cc @@ -39,12 +39,14 @@ #include "grpc/support/log.h" #include "grpc/grpc.h" +#include "grpc/grpc_security.h" #include "grpc/support/alloc.h" #include "grpc/support/time.h" #include "byte_buffer.h" #include "call.h" #include "channel.h" #include "completion_queue_async_worker.h" +#include "call_credentials.h" #include "timeval.h" using std::unique_ptr; @@ -168,8 +170,9 @@ Local<Value> ParseMetadata(const grpc_metadata_array *metadata_array) { } if (EndsWith(elem->key, "-bin")) { Nan::Set(array, index_map[elem->key], - Nan::CopyBuffer(elem->value, - elem->value_length).ToLocalChecked()); + MakeFastBuffer( + Nan::CopyBuffer(elem->value, + elem->value_length).ToLocalChecked())); } else { Nan::Set(array, index_map[elem->key], Nan::New(elem->value).ToLocalChecked()); @@ -501,6 +504,7 @@ void Call::Init(Local<Object> exports) { Nan::SetPrototypeMethod(tpl, "cancel", Cancel); Nan::SetPrototypeMethod(tpl, "cancelWithStatus", CancelWithStatus); Nan::SetPrototypeMethod(tpl, "getPeer", GetPeer); + Nan::SetPrototypeMethod(tpl, "setCredentials", SetCredentials); fun_tpl.Reset(tpl); Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked(); Nan::Set(exports, Nan::New("Call").ToLocalChecked(), ctr); @@ -724,5 +728,26 @@ NAN_METHOD(Call::GetPeer) { info.GetReturnValue().Set(peer_value); } +NAN_METHOD(Call::SetCredentials) { + Nan::HandleScope scope; + if (!HasInstance(info.This())) { + return Nan::ThrowTypeError( + "setCredentials can only be called on Call objects"); + } + if (!CallCredentials::HasInstance(info[0])) { + return Nan::ThrowTypeError( + "setCredentials' first argument must be a CallCredentials"); + } + Call *call = ObjectWrap::Unwrap<Call>(info.This()); + CallCredentials *creds_object = ObjectWrap::Unwrap<CallCredentials>( + Nan::To<Object>(info[0]).ToLocalChecked()); + grpc_credentials *creds = creds_object->GetWrappedCredentials(); + grpc_call_error error = GRPC_CALL_ERROR; + if (creds) { + error = grpc_call_set_credentials(call->wrapped_call, creds); + } + info.GetReturnValue().Set(Nan::New<Uint32>(error)); +} + } // namespace node } // namespace grpc diff --git a/src/node/ext/call.h b/src/node/ext/call.h index 2f8e1f17aa..dd6c38e4f8 100644 --- a/src/node/ext/call.h +++ b/src/node/ext/call.h @@ -73,6 +73,10 @@ struct Resources { std::vector<unique_ptr<PersistentValue> > handles; }; +bool CreateMetadataArray(v8::Local<v8::Object> metadata, + grpc_metadata_array *array, + shared_ptr<Resources> resources); + class Op { public: virtual v8::Local<v8::Value> GetNodeValue() const = 0; @@ -122,6 +126,7 @@ class Call : public Nan::ObjectWrap { static NAN_METHOD(Cancel); static NAN_METHOD(CancelWithStatus); static NAN_METHOD(GetPeer); + static NAN_METHOD(SetCredentials); static Nan::Callback *constructor; // Used for typechecking instances of this javascript class static Nan::Persistent<v8::FunctionTemplate> fun_tpl; diff --git a/src/node/ext/call_credentials.cc b/src/node/ext/call_credentials.cc new file mode 100644 index 0000000000..839bb567e4 --- /dev/null +++ b/src/node/ext/call_credentials.cc @@ -0,0 +1,259 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <node.h> + +#include "grpc/grpc.h" +#include "grpc/grpc_security.h" +#include "grpc/support/log.h" +#include "call_credentials.h" +#include "call.h" + +namespace grpc { +namespace node { + +using Nan::Callback; +using Nan::EscapableHandleScope; +using Nan::HandleScope; +using Nan::Maybe; +using Nan::MaybeLocal; +using Nan::ObjectWrap; +using Nan::Persistent; +using Nan::Utf8String; + +using v8::Exception; +using v8::External; +using v8::Function; +using v8::FunctionTemplate; +using v8::Integer; +using v8::Local; +using v8::Object; +using v8::ObjectTemplate; +using v8::Value; + +Nan::Callback *CallCredentials::constructor; +Persistent<FunctionTemplate> CallCredentials::fun_tpl; + +CallCredentials::CallCredentials(grpc_credentials *credentials) + : wrapped_credentials(credentials) {} + +CallCredentials::~CallCredentials() { + grpc_credentials_release(wrapped_credentials); +} + +void CallCredentials::Init(Local<Object> exports) { + HandleScope scope; + Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New); + tpl->SetClassName(Nan::New("CallCredentials").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + Nan::SetPrototypeMethod(tpl, "compose", Compose); + fun_tpl.Reset(tpl); + Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked(); + Nan::Set(ctr, Nan::New("createFromPlugin").ToLocalChecked(), + Nan::GetFunction( + Nan::New<FunctionTemplate>(CreateFromPlugin)).ToLocalChecked()); + Nan::Set(exports, Nan::New("CallCredentials").ToLocalChecked(), ctr); + constructor = new Nan::Callback(ctr); +} + +bool CallCredentials::HasInstance(Local<Value> val) { + HandleScope scope; + return Nan::New(fun_tpl)->HasInstance(val); +} + +Local<Value> CallCredentials::WrapStruct(grpc_credentials *credentials) { + EscapableHandleScope scope; + const int argc = 1; + if (credentials == NULL) { + return scope.Escape(Nan::Null()); + } + Local<Value> argv[argc] = { + Nan::New<External>(reinterpret_cast<void *>(credentials))}; + MaybeLocal<Object> maybe_instance = Nan::NewInstance( + constructor->GetFunction(), argc, argv); + if (maybe_instance.IsEmpty()) { + return scope.Escape(Nan::Null()); + } else { + return scope.Escape(maybe_instance.ToLocalChecked()); + } +} + +grpc_credentials *CallCredentials::GetWrappedCredentials() { + return wrapped_credentials; +} + +NAN_METHOD(CallCredentials::New) { + if (info.IsConstructCall()) { + if (!info[0]->IsExternal()) { + return Nan::ThrowTypeError( + "CallCredentials can only be created with the provided functions"); + } + Local<External> ext = info[0].As<External>(); + grpc_credentials *creds_value = + reinterpret_cast<grpc_credentials *>(ext->Value()); + CallCredentials *credentials = new CallCredentials(creds_value); + credentials->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); + return; + } else { + const int argc = 1; + Local<Value> argv[argc] = {info[0]}; + MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance( + argc, argv); + if (maybe_instance.IsEmpty()) { + // There's probably a pending exception + return; + } else { + info.GetReturnValue().Set(maybe_instance.ToLocalChecked()); + } + } +} + +NAN_METHOD(CallCredentials::Compose) { + if (!CallCredentials::HasInstance(info.This())) { + return Nan::ThrowTypeError( + "compose can only be called on CallCredentials objects"); + } + if (!CallCredentials::HasInstance(info[0])) { + return Nan::ThrowTypeError( + "compose's first argument must be a CallCredentials object"); + } + CallCredentials *self = ObjectWrap::Unwrap<CallCredentials>(info.This()); + CallCredentials *other = ObjectWrap::Unwrap<CallCredentials>( + Nan::To<Object>(info[0]).ToLocalChecked()); + grpc_credentials *creds = grpc_composite_credentials_create( + self->wrapped_credentials, other->wrapped_credentials, NULL); + info.GetReturnValue().Set(WrapStruct(creds)); +} + + + +NAN_METHOD(CallCredentials::CreateFromPlugin) { + if (!info[0]->IsFunction()) { + return Nan::ThrowTypeError( + "createFromPlugin's argument must be a function"); + } + grpc_metadata_credentials_plugin plugin; + plugin_state *state = new plugin_state; + state->callback = new Nan::Callback(info[0].As<Function>()); + plugin.get_metadata = plugin_get_metadata; + plugin.destroy = plugin_destroy_state; + plugin.state = reinterpret_cast<void*>(state); + grpc_credentials *creds = grpc_metadata_credentials_create_from_plugin(plugin, + NULL); + info.GetReturnValue().Set(WrapStruct(creds)); +} + +NAN_METHOD(PluginCallback) { + // Arguments: status code, error details, metadata + if (!info[0]->IsUint32()) { + return Nan::ThrowTypeError( + "The callback's first argument must be a status code"); + } + if (!info[1]->IsString()) { + return Nan::ThrowTypeError( + "The callback's second argument must be a string"); + } + if (!info[2]->IsObject()) { + return Nan::ThrowTypeError( + "The callback's third argument must be an object"); + } + shared_ptr<Resources> resources(new Resources); + grpc_status_code code = static_cast<grpc_status_code>( + Nan::To<uint32_t>(info[0]).FromJust()); + char *details = *Utf8String(info[1]); + grpc_metadata_array array; + if (!CreateMetadataArray(Nan::To<Object>(info[2]).ToLocalChecked(), + &array, resources)){ + return Nan::ThrowError("Failed to parse metadata"); + } + grpc_credentials_plugin_metadata_cb cb = + reinterpret_cast<grpc_credentials_plugin_metadata_cb>( + Nan::Get(info.Callee(), + Nan::New("cb").ToLocalChecked() + ).ToLocalChecked().As<External>()->Value()); + void *user_data = + Nan::Get(info.Callee(), + Nan::New("user_data").ToLocalChecked() + ).ToLocalChecked().As<External>()->Value(); + cb(user_data, array.metadata, array.count, code, details); +} + +NAUV_WORK_CB(SendPluginCallback) { + Nan::HandleScope scope; + plugin_callback_data *data = reinterpret_cast<plugin_callback_data*>( + async->data); + // Attach cb and user_data to plugin_callback so that it can access them later + v8::Local<v8::Function> plugin_callback = Nan::GetFunction( + Nan::New<v8::FunctionTemplate>(PluginCallback)).ToLocalChecked(); + Nan::Set(plugin_callback, Nan::New("cb").ToLocalChecked(), + Nan::New<v8::External>(reinterpret_cast<void*>(data->cb))); + Nan::Set(plugin_callback, Nan::New("user_data").ToLocalChecked(), + Nan::New<v8::External>(data->user_data)); + const int argc = 2; + v8::Local<v8::Value> argv[argc] = { + Nan::New(data->service_url).ToLocalChecked(), + plugin_callback + }; + Nan::Callback *callback = data->state->callback; + callback->Call(argc, argv); + delete data; + uv_unref((uv_handle_t *)async); + uv_close((uv_handle_t *)async, (uv_close_cb)free); +} + +void plugin_get_metadata(void *state, const char *service_url, + grpc_credentials_plugin_metadata_cb cb, + void *user_data) { + uv_async_t *async = static_cast<uv_async_t*>(malloc(sizeof(uv_async_t))); + uv_async_init(uv_default_loop(), + async, + SendPluginCallback); + plugin_callback_data *data = new plugin_callback_data; + data->state = reinterpret_cast<plugin_state*>(state); + data->service_url = service_url; + data->cb = cb; + data->user_data = user_data; + async->data = data; + /* libuv says that it will coalesce calls to uv_async_send. If there is ever a + * problem with a callback not getting called, that is probably the reason */ + uv_async_send(async); +} + +void plugin_destroy_state(void *ptr) { + plugin_state *state = reinterpret_cast<plugin_state *>(ptr); + delete state->callback; +} + +} // namespace node +} // namespace grpc diff --git a/src/node/ext/call_credentials.h b/src/node/ext/call_credentials.h new file mode 100644 index 0000000000..618292d19e --- /dev/null +++ b/src/node/ext/call_credentials.h @@ -0,0 +1,100 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_NODE_CALL_CREDENTIALS_H_ +#define GRPC_NODE_CALL_CREDENTIALS_H_ + +#include <node.h> +#include <nan.h> +#include "grpc/grpc_security.h" + +namespace grpc { +namespace node { + +class CallCredentials : public Nan::ObjectWrap { + public: + static void Init(v8::Local<v8::Object> exports); + static bool HasInstance(v8::Local<v8::Value> val); + /* Wrap a grpc_credentials struct in a javascript object */ + static v8::Local<v8::Value> WrapStruct(grpc_credentials *credentials); + + /* Returns the grpc_credentials struct that this object wraps */ + grpc_credentials *GetWrappedCredentials(); + + private: + explicit CallCredentials(grpc_credentials *credentials); + ~CallCredentials(); + + // Prevent copying + CallCredentials(const CallCredentials &); + CallCredentials &operator=(const CallCredentials &); + + static NAN_METHOD(New); + static NAN_METHOD(CreateSsl); + static NAN_METHOD(CreateFromPlugin); + + static NAN_METHOD(Compose); + static Nan::Callback *constructor; + // Used for typechecking instances of this javascript class + static Nan::Persistent<v8::FunctionTemplate> fun_tpl; + + grpc_credentials *wrapped_credentials; +}; + +/* Auth metadata plugin functionality */ + +typedef struct plugin_state { + Nan::Callback *callback; +} plugin_state; + +typedef struct plugin_callback_data { + plugin_state *state; + const char *service_url; + grpc_credentials_plugin_metadata_cb cb; + void *user_data; +} plugin_callback_data; + +void plugin_get_metadata(void *state, const char *service_url, + grpc_credentials_plugin_metadata_cb cb, + void *user_data); + +void plugin_destroy_state(void *state); + +NAN_METHOD(PluginCallback); + +NAUV_WORK_CB(SendPluginCallback); + +} // namespace node +} // namepsace grpc + +#endif // GRPC_NODE_CALL_CREDENTIALS_H_ diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc index 6eb1e77688..a328c01713 100644 --- a/src/node/ext/channel.cc +++ b/src/node/ext/channel.cc @@ -42,7 +42,7 @@ #include "call.h" #include "channel.h" #include "completion_queue_async_worker.h" -#include "credentials.h" +#include "channel_credentials.h" #include "timeval.h" namespace grpc { @@ -112,11 +112,11 @@ NAN_METHOD(Channel::New) { // Owned by the Channel object Utf8String host(info[0]); grpc_credentials *creds; - if (!Credentials::HasInstance(info[1])) { + if (!ChannelCredentials::HasInstance(info[1])) { return Nan::ThrowTypeError( - "Channel's second argument must be a credential"); + "Channel's second argument must be a ChannelCredentials"); } - Credentials *creds_object = ObjectWrap::Unwrap<Credentials>( + ChannelCredentials *creds_object = ObjectWrap::Unwrap<ChannelCredentials>( Nan::To<Object>(info[1]).ToLocalChecked()); creds = creds_object->GetWrappedCredentials(); grpc_channel_args *channel_args_ptr; diff --git a/src/node/ext/credentials.cc b/src/node/ext/channel_credentials.cc index 4f41c92f6a..3d47ff293d 100644 --- a/src/node/ext/credentials.cc +++ b/src/node/ext/channel_credentials.cc @@ -36,7 +36,9 @@ #include "grpc/grpc.h" #include "grpc/grpc_security.h" #include "grpc/support/log.h" -#include "credentials.h" +#include "channel_credentials.h" +#include "call_credentials.h" +#include "call.h" namespace grpc { namespace node { @@ -60,51 +62,40 @@ using v8::Object; using v8::ObjectTemplate; using v8::Value; -Nan::Callback *Credentials::constructor; -Persistent<FunctionTemplate> Credentials::fun_tpl; +Nan::Callback *ChannelCredentials::constructor; +Persistent<FunctionTemplate> ChannelCredentials::fun_tpl; -Credentials::Credentials(grpc_credentials *credentials) +ChannelCredentials::ChannelCredentials(grpc_credentials *credentials) : wrapped_credentials(credentials) {} -Credentials::~Credentials() { +ChannelCredentials::~ChannelCredentials() { grpc_credentials_release(wrapped_credentials); } -void Credentials::Init(Local<Object> exports) { +void ChannelCredentials::Init(Local<Object> exports) { HandleScope scope; Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New); - tpl->SetClassName(Nan::New("Credentials").ToLocalChecked()); + tpl->SetClassName(Nan::New("ChannelCredentials").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); + Nan::SetPrototypeMethod(tpl, "compose", Compose); fun_tpl.Reset(tpl); Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked(); - Nan::Set(ctr, Nan::New("createDefault").ToLocalChecked(), - Nan::GetFunction( - Nan::New<FunctionTemplate>(CreateDefault)).ToLocalChecked()); Nan::Set(ctr, Nan::New("createSsl").ToLocalChecked(), Nan::GetFunction( Nan::New<FunctionTemplate>(CreateSsl)).ToLocalChecked()); - Nan::Set(ctr, Nan::New("createComposite").ToLocalChecked(), - Nan::GetFunction( - Nan::New<FunctionTemplate>(CreateComposite)).ToLocalChecked()); - Nan::Set(ctr, Nan::New("createGce").ToLocalChecked(), - Nan::GetFunction( - Nan::New<FunctionTemplate>(CreateGce)).ToLocalChecked()); - Nan::Set(ctr, Nan::New("createIam").ToLocalChecked(), - Nan::GetFunction( - Nan::New<FunctionTemplate>(CreateIam)).ToLocalChecked()); Nan::Set(ctr, Nan::New("createInsecure").ToLocalChecked(), Nan::GetFunction( Nan::New<FunctionTemplate>(CreateInsecure)).ToLocalChecked()); - Nan::Set(exports, Nan::New("Credentials").ToLocalChecked(), ctr); + Nan::Set(exports, Nan::New("ChannelCredentials").ToLocalChecked(), ctr); constructor = new Nan::Callback(ctr); } -bool Credentials::HasInstance(Local<Value> val) { +bool ChannelCredentials::HasInstance(Local<Value> val) { HandleScope scope; return Nan::New(fun_tpl)->HasInstance(val); } -Local<Value> Credentials::WrapStruct(grpc_credentials *credentials) { +Local<Value> ChannelCredentials::WrapStruct(grpc_credentials *credentials) { EscapableHandleScope scope; const int argc = 1; Local<Value> argv[argc] = { @@ -118,20 +109,20 @@ Local<Value> Credentials::WrapStruct(grpc_credentials *credentials) { } } -grpc_credentials *Credentials::GetWrappedCredentials() { +grpc_credentials *ChannelCredentials::GetWrappedCredentials() { return wrapped_credentials; } -NAN_METHOD(Credentials::New) { +NAN_METHOD(ChannelCredentials::New) { if (info.IsConstructCall()) { if (!info[0]->IsExternal()) { return Nan::ThrowTypeError( - "Credentials can only be created with the provided functions"); + "ChannelCredentials can only be created with the provided functions"); } Local<External> ext = info[0].As<External>(); grpc_credentials *creds_value = reinterpret_cast<grpc_credentials *>(ext->Value()); - Credentials *credentials = new Credentials(creds_value); + ChannelCredentials *credentials = new ChannelCredentials(creds_value); credentials->Wrap(info.This()); info.GetReturnValue().Set(info.This()); return; @@ -149,16 +140,7 @@ NAN_METHOD(Credentials::New) { } } -NAN_METHOD(Credentials::CreateDefault) { - grpc_credentials *creds = grpc_google_default_credentials_create(); - if (creds == NULL) { - info.GetReturnValue().SetNull(); - } else { - info.GetReturnValue().Set(WrapStruct(creds)); - } -} - -NAN_METHOD(Credentials::CreateSsl) { +NAN_METHOD(ChannelCredentials::CreateSsl) { char *root_certs = NULL; grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL}; if (::node::Buffer::HasInstance(info[0])) { @@ -188,49 +170,25 @@ NAN_METHOD(Credentials::CreateSsl) { } } -NAN_METHOD(Credentials::CreateComposite) { - if (!HasInstance(info[0])) { +NAN_METHOD(ChannelCredentials::Compose) { + if (!ChannelCredentials::HasInstance(info.This())) { return Nan::ThrowTypeError( - "createComposite's first argument must be a Credentials object"); + "compose can only be called on ChannelCredentials objects"); } - if (!HasInstance(info[1])) { + if (!CallCredentials::HasInstance(info[0])) { return Nan::ThrowTypeError( - "createComposite's second argument must be a Credentials object"); + "compose's first argument must be a CallCredentials object"); } - Credentials *creds1 = ObjectWrap::Unwrap<Credentials>( + ChannelCredentials *self = ObjectWrap::Unwrap<ChannelCredentials>( + info.This()); + if (self->wrapped_credentials == NULL) { + return Nan::ThrowTypeError( + "Cannot compose insecure credential"); + } + CallCredentials *other = ObjectWrap::Unwrap<CallCredentials>( Nan::To<Object>(info[0]).ToLocalChecked()); - Credentials *creds2 = ObjectWrap::Unwrap<Credentials>( - Nan::To<Object>(info[1]).ToLocalChecked()); grpc_credentials *creds = grpc_composite_credentials_create( - creds1->wrapped_credentials, creds2->wrapped_credentials, NULL); - if (creds == NULL) { - info.GetReturnValue().SetNull(); - } else { - info.GetReturnValue().Set(WrapStruct(creds)); - } -} - -NAN_METHOD(Credentials::CreateGce) { - Nan::HandleScope scope; - grpc_credentials *creds = grpc_google_compute_engine_credentials_create(NULL); - if (creds == NULL) { - info.GetReturnValue().SetNull(); - } else { - info.GetReturnValue().Set(WrapStruct(creds)); - } -} - -NAN_METHOD(Credentials::CreateIam) { - if (!info[0]->IsString()) { - return Nan::ThrowTypeError("createIam's first argument must be a string"); - } - if (!info[1]->IsString()) { - return Nan::ThrowTypeError("createIam's second argument must be a string"); - } - Utf8String auth_token(info[0]); - Utf8String auth_selector(info[1]); - grpc_credentials *creds = - grpc_google_iam_credentials_create(*auth_token, *auth_selector, NULL); + self->wrapped_credentials, other->GetWrappedCredentials(), NULL); if (creds == NULL) { info.GetReturnValue().SetNull(); } else { @@ -238,7 +196,7 @@ NAN_METHOD(Credentials::CreateIam) { } } -NAN_METHOD(Credentials::CreateInsecure) { +NAN_METHOD(ChannelCredentials::CreateInsecure) { info.GetReturnValue().Set(WrapStruct(NULL)); } diff --git a/src/node/ext/credentials.h b/src/node/ext/channel_credentials.h index 1b211175d4..31ea0987bc 100644 --- a/src/node/ext/credentials.h +++ b/src/node/ext/channel_credentials.h @@ -31,8 +31,8 @@ * */ -#ifndef NET_GRPC_NODE_CREDENTIALS_H_ -#define NET_GRPC_NODE_CREDENTIALS_H_ +#ifndef NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_ +#define NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_ #include <node.h> #include <nan.h> @@ -43,7 +43,7 @@ namespace grpc { namespace node { /* Wrapper class for grpc_credentials structs */ -class Credentials : public Nan::ObjectWrap { +class ChannelCredentials : public Nan::ObjectWrap { public: static void Init(v8::Local<v8::Object> exports); static bool HasInstance(v8::Local<v8::Value> val); @@ -54,21 +54,18 @@ class Credentials : public Nan::ObjectWrap { grpc_credentials *GetWrappedCredentials(); private: - explicit Credentials(grpc_credentials *credentials); - ~Credentials(); + explicit ChannelCredentials(grpc_credentials *credentials); + ~ChannelCredentials(); // Prevent copying - Credentials(const Credentials &); - Credentials &operator=(const Credentials &); + ChannelCredentials(const ChannelCredentials &); + ChannelCredentials &operator=(const ChannelCredentials &); static NAN_METHOD(New); - static NAN_METHOD(CreateDefault); static NAN_METHOD(CreateSsl); - static NAN_METHOD(CreateComposite); - static NAN_METHOD(CreateGce); - static NAN_METHOD(CreateFake); - static NAN_METHOD(CreateIam); static NAN_METHOD(CreateInsecure); + + static NAN_METHOD(Compose); static Nan::Callback *constructor; // Used for typechecking instances of this javascript class static Nan::Persistent<v8::FunctionTemplate> fun_tpl; @@ -79,4 +76,4 @@ class Credentials : public Nan::ObjectWrap { } // namespace node } // namespace grpc -#endif // NET_GRPC_NODE_CREDENTIALS_H_ +#endif // NET_GRPC_NODE_CHANNEL_CREDENTIALS_H_ diff --git a/src/node/ext/node_grpc.cc b/src/node/ext/node_grpc.cc index caca0fc452..5b5f3c1c5b 100644 --- a/src/node/ext/node_grpc.cc +++ b/src/node/ext/node_grpc.cc @@ -37,10 +37,11 @@ #include "grpc/grpc.h" #include "call.h" +#include "call_credentials.h" #include "channel.h" +#include "channel_credentials.h" #include "server.h" #include "completion_queue_async_worker.h" -#include "credentials.h" #include "server_credentials.h" using v8::Local; @@ -240,11 +241,12 @@ void init(Local<Object> exports) { InitWriteFlags(exports); grpc::node::Call::Init(exports); + grpc::node::CallCredentials::Init(exports); grpc::node::Channel::Init(exports); + grpc::node::ChannelCredentials::Init(exports); grpc::node::Server::Init(exports); grpc::node::CompletionQueueAsyncWorker::Init(exports); - grpc::node::Credentials::Init(exports); grpc::node::ServerCredentials::Init(exports); } -NODE_MODULE(grpc, init) +NODE_MODULE(grpc_node, init) diff --git a/src/node/index.js b/src/node/index.js index 02b73f66ee..591d9dd915 100644 --- a/src/node/index.js +++ b/src/node/index.js @@ -43,7 +43,7 @@ var server = require('./src/server.js'); var Metadata = require('./src/metadata.js'); -var grpc = require('bindings')('grpc'); +var grpc = require('bindings')('grpc_node'); /** * Load a gRPC object from an existing ProtoBuf.Reflect object. @@ -90,37 +90,10 @@ exports.load = function load(filename, format) { default: throw new Error('Unrecognized format "' + format + '"'); } - return loadObject(builder.ns); }; /** - * Get a function that a client can use to update metadata with authentication - * information from a Google Auth credential object, which comes from the - * google-auth-library. - * @param {Object} credential The credential object to use - * @return {function(Object, callback)} Metadata updater function - */ -exports.getGoogleAuthDelegate = function getGoogleAuthDelegate(credential) { - /** - * Update a metadata object with authentication information. - * @param {string} authURI The uri to authenticate to - * @param {Object} metadata Metadata object - * @param {function(Error, Object)} callback - */ - return function updateMetadata(authURI, metadata, callback) { - credential.getRequestMetadata(authURI, function(err, header) { - if (err) { - callback(err); - return; - } - metadata.add('authorization', header.Authorization); - callback(null, metadata); - }); - }; -}; - -/** * @see module:src/server.Server */ exports.Server = server.Server; @@ -153,7 +126,7 @@ exports.writeFlags = grpc.writeFlags; /** * Credentials factories */ -exports.Credentials = grpc.Credentials; +exports.credentials = require('./src/credentials.js'); /** * ServerCredentials factories diff --git a/src/node/interop/empty.proto b/src/node/interop/empty.proto deleted file mode 100644 index 6d0eb937d6..0000000000 --- a/src/node/interop/empty.proto +++ /dev/null @@ -1,43 +0,0 @@ - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -syntax = "proto3"; - -package grpc.testing; - -// An empty message that you can re-use to avoid defining duplicated empty -// messages in your project. A typical example is to use it as argument or the -// return value of a service API. For instance: -// -// service Foo { -// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { }; -// }; -// -message Empty {} diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js index 215d42121c..14cc6c0efe 100644 --- a/src/node/interop/interop_client.js +++ b/src/node/interop/interop_client.js @@ -37,7 +37,9 @@ var fs = require('fs'); var path = require('path'); var _ = require('lodash'); var grpc = require('..'); -var testProto = grpc.load(__dirname + '/test.proto').grpc.testing; +var testProto = grpc.load({ + root: __dirname + '/../../..', + file: 'test/proto/test.proto'}).grpc.testing; var GoogleAuth = require('google-auth-library'); var assert = require('assert'); @@ -49,6 +51,9 @@ var AUTH_USER = ('155450119199-vefjjaekcc6cmsd5914v6lqufunmh9ue' + var COMPUTE_ENGINE_USER = ('155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel' + '@developer.gserviceaccount.com'); +var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial'; +var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin'; + /** * Create a buffer filled with size zeroes * @param {number} size The length of the buffer @@ -61,6 +66,27 @@ function zeroBuffer(size) { } /** + * This is used for testing functions with multiple asynchronous calls that + * can happen in different orders. This should be passed the number of async + * function invocations that can occur last, and each of those should call this + * function's return value + * @param {function()} done The function that should be called when a test is + * complete. + * @param {number} count The number of calls to the resulting function if the + * test passes. + * @return {function()} The function that should be called at the end of each + * sequence of asynchronous functions. + */ +function multiDone(done, count) { + return function() { + count -= 1; + if (count <= 0) { + done(); + } + }; +} + +/** * Run the empty_unary test * @param {Client} client The client to test against * @param {function} done Callback to call when the test is completed. Included @@ -271,6 +297,54 @@ function timeoutOnSleepingServer(client, done) { }); } +function customMetadata(client, done) { + done = multiDone(done, 5); + var metadata = new grpc.Metadata(); + metadata.set(ECHO_INITIAL_KEY, 'test_initial_metadata_value'); + metadata.set(ECHO_TRAILING_KEY, new Buffer('ababab', 'hex')); + var arg = { + response_type: 'COMPRESSABLE', + response_size: 314159, + payload: { + body: zeroBuffer(271828) + } + }; + var streaming_arg = { + payload: { + body: zeroBuffer(271828) + } + }; + var unary = client.unaryCall(arg, function(err, resp) { + assert.ifError(err); + done(); + }, metadata); + unary.on('metadata', function(metadata) { + assert.deepEqual(metadata.get(ECHO_INITIAL_KEY), + ['test_initial_metadata_value']); + done(); + }); + unary.on('status', function(status) { + var echo_trailer = status.metadata.get(ECHO_TRAILING_KEY); + assert(echo_trailer.length > 0); + assert.strictEqual(echo_trailer[0].toString('hex'), 'ababab'); + done(); + }); + var stream = client.fullDuplexCall(metadata); + stream.on('metadata', function(metadata) { + assert.deepEqual(metadata.get(ECHO_INITIAL_KEY), + ['test_initial_metadata_value']); + done(); + }); + stream.on('status', function(status) { + var echo_trailer = status.metadata.get(ECHO_TRAILING_KEY); + assert(echo_trailer.length > 0); + assert.strictEqual(echo_trailer[0].toString('hex'), 'ababab'); + done(); + }); + stream.write(streaming_arg); + stream.end(); +} + /** * Run one of the authentication tests. * @param {string} expected_user The expected username in the response @@ -280,33 +354,26 @@ function timeoutOnSleepingServer(client, done) { * primarily for use with mocha */ function authTest(expected_user, scope, client, done) { - (new GoogleAuth()).getApplicationDefault(function(err, credential) { + var arg = { + response_type: 'COMPRESSABLE', + response_size: 314159, + payload: { + body: zeroBuffer(271828) + }, + fill_username: true, + fill_oauth_scope: true + }; + client.unaryCall(arg, function(err, resp) { assert.ifError(err); - if (credential.createScopedRequired() && scope) { - credential = credential.createScoped(scope); + assert.strictEqual(resp.payload.type, 'COMPRESSABLE'); + assert.strictEqual(resp.payload.body.length, 314159); + assert.strictEqual(resp.username, expected_user); + if (scope) { + assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE); + } + if (done) { + done(); } - client.$updateMetadata = grpc.getGoogleAuthDelegate(credential); - var arg = { - response_type: 'COMPRESSABLE', - response_size: 314159, - payload: { - body: zeroBuffer(271828) - }, - fill_username: true, - fill_oauth_scope: true - }; - client.unaryCall(arg, function(err, resp) { - assert.ifError(err); - assert.strictEqual(resp.payload.type, 'COMPRESSABLE'); - assert.strictEqual(resp.payload.body.length, 314159); - assert.strictEqual(resp.username, expected_user); - if (scope) { - assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE); - } - if (done) { - done(); - } - }); }); } @@ -345,24 +412,86 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) { }); } +function perRpcAuthTest(expected_user, scope, per_rpc, client, done) { + (new GoogleAuth()).getApplicationDefault(function(err, credential) { + assert.ifError(err); + var arg = { + fill_username: true, + fill_oauth_scope: true + }; + if (credential.createScopedRequired() && scope) { + credential = credential.createScoped(scope); + } + var creds = grpc.credentials.createFromGoogleCredential(credential); + client.unaryCall(arg, function(err, resp) { + assert.ifError(err); + assert.strictEqual(resp.username, expected_user); + assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE); + if (done) { + done(); + } + }, null, {credentials: creds}); + }); +} + +function getApplicationCreds(scope, callback) { + (new GoogleAuth()).getApplicationDefault(function(err, credential) { + if (err) { + callback(err); + return; + } + if (credential.createScopedRequired() && scope) { + credential = credential.createScoped(scope); + } + callback(null, grpc.credentials.createFromGoogleCredential(credential)); + }); +} + +function getOauth2Creds(scope, callback) { + (new GoogleAuth()).getApplicationDefault(function(err, credential) { + if (err) { + callback(err); + return; + } + credential = credential.createScoped(scope); + credential.getAccessToken(function(err, token) { + if (err) { + callback(err); + return; + } + var updateMd = function(service_url, callback) { + var metadata = new grpc.Metadata(); + metadata.add('authorization', 'Bearer ' + token); + callback(null, metadata); + }; + callback(null, grpc.credentials.createFromMetadataGenerator(updateMd)); + }); + }); +} + /** * Map from test case names to test functions */ var test_cases = { - empty_unary: emptyUnary, - large_unary: largeUnary, - client_streaming: clientStreaming, - server_streaming: serverStreaming, - ping_pong: pingPong, - empty_stream: emptyStream, - cancel_after_begin: cancelAfterBegin, - cancel_after_first_response: cancelAfterFirstResponse, - timeout_on_sleeping_server: timeoutOnSleepingServer, - compute_engine_creds: _.partial(authTest, COMPUTE_ENGINE_USER, null), - service_account_creds: _.partial(authTest, AUTH_USER, AUTH_SCOPE), - jwt_token_creds: _.partial(authTest, AUTH_USER, null), - oauth2_auth_token: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false), - per_rpc_creds: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, true) + empty_unary: {run: emptyUnary}, + large_unary: {run: largeUnary}, + client_streaming: {run: clientStreaming}, + server_streaming: {run: serverStreaming}, + ping_pong: {run: pingPong}, + empty_stream: {run: emptyStream}, + cancel_after_begin: {run: cancelAfterBegin}, + cancel_after_first_response: {run: cancelAfterFirstResponse}, + timeout_on_sleeping_server: {run: timeoutOnSleepingServer}, + custom_metadata: {run: customMetadata}, + compute_engine_creds: {run: _.partial(authTest, COMPUTE_ENGINE_USER, null), + getCreds: _.partial(getApplicationCreds, null)}, + service_account_creds: {run: _.partial(authTest, AUTH_USER, AUTH_SCOPE), + getCreds: _.partial(getApplicationCreds, AUTH_SCOPE)}, + jwt_token_creds: {run: _.partial(authTest, AUTH_USER, null), + getCreds: _.partial(getApplicationCreds, null)}, + oauth2_auth_token: {run: _.partial(oauth2Test, AUTH_USER, AUTH_SCOPE, false), + getCreds: _.partial(getOauth2Creds, AUTH_SCOPE)}, + per_rpc_creds: {run: _.partial(perRpcAuthTest, AUTH_USER, AUTH_SCOPE, true)} }; /** @@ -388,17 +517,30 @@ function runTest(address, host_override, test_case, tls, test_ca, done) { ca_path = process.env.SSL_CERT_FILE; } var ca_data = fs.readFileSync(ca_path); - creds = grpc.Credentials.createSsl(ca_data); + creds = grpc.credentials.createSsl(ca_data); if (host_override) { options['grpc.ssl_target_name_override'] = host_override; options['grpc.default_authority'] = host_override; } } else { - creds = grpc.Credentials.createInsecure(); + creds = grpc.credentials.createInsecure(); } - var client = new testProto.TestService(address, creds, options); + var test = test_cases[test_case]; - test_cases[test_case](client, done); + var execute = function(err, creds) { + assert.ifError(err); + var client = new testProto.TestService(address, creds, options); + test.run(client, done); + }; + + if (test.getCreds) { + test.getCreds(function(err, new_creds) { + execute(err, grpc.credentials.combineChannelCredentials( + creds, new_creds)); + }); + } else { + execute(null, creds); + } } if (require.main === module) { diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js index 99155e9958..3e83304faa 100644 --- a/src/node/interop/interop_server.js +++ b/src/node/interop/interop_server.js @@ -37,7 +37,12 @@ var fs = require('fs'); var path = require('path'); var _ = require('lodash'); var grpc = require('..'); -var testProto = grpc.load(__dirname + '/test.proto').grpc.testing; +var testProto = grpc.load({ + root: __dirname + '/../../..', + file: 'test/proto/test.proto'}).grpc.testing; + +var ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial'; +var ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin'; /** * Create a buffer filled with size zeroes @@ -51,6 +56,34 @@ function zeroBuffer(size) { } /** + * Echos a header metadata item as specified in the interop spec. + * @param {Call} call The call to echo metadata on + */ +function echoHeader(call) { + var echo_initial = call.metadata.get(ECHO_INITIAL_KEY); + if (echo_initial.length > 0) { + var response_metadata = new grpc.Metadata(); + response_metadata.set(ECHO_INITIAL_KEY, echo_initial[0]); + call.sendMetadata(response_metadata); + } +} + +/** + * Gets the trailer metadata that should be echoed when the call is done, + * as specified in the interop spec. + * @param {Call} call The call to get metadata from + * @return {grpc.Metadata} The metadata to send as a trailer + */ +function getEchoTrailer(call) { + var echo_trailer = call.metadata.get(ECHO_TRAILING_KEY); + var response_trailer = new grpc.Metadata(); + if (echo_trailer.length > 0) { + response_trailer.set(ECHO_TRAILING_KEY, echo_trailer[0]); + } + return response_trailer; +} + +/** * Respond to an empty parameter with an empty response. * NOTE: this currently does not work due to issue #137 * @param {Call} call Call to handle @@ -58,7 +91,8 @@ function zeroBuffer(size) { * or error */ function handleEmpty(call, callback) { - callback(null, {}); + echoHeader(call); + callback(null, {}, getEchoTrailer(call)); } /** @@ -68,6 +102,7 @@ function handleEmpty(call, callback) { * error */ function handleUnary(call, callback) { + echoHeader(call); var req = call.request; var zeros = zeroBuffer(req.response_size); var payload_type = req.response_type; @@ -75,7 +110,8 @@ function handleUnary(call, callback) { payload_type = ['COMPRESSABLE', 'UNCOMPRESSABLE'][Math.random() < 0.5 ? 0 : 1]; } - callback(null, {payload: {type: payload_type, body: zeros}}); + callback(null, {payload: {type: payload_type, body: zeros}}, + getEchoTrailer(call)); } /** @@ -85,12 +121,14 @@ function handleUnary(call, callback) { * error */ function handleStreamingInput(call, callback) { + echoHeader(call); var aggregate_size = 0; call.on('data', function(value) { aggregate_size += value.payload.body.length; }); call.on('end', function() { - callback(null, {aggregated_payload_size: aggregate_size}); + callback(null, {aggregated_payload_size: aggregate_size}, + getEchoTrailer(call)); }); } @@ -99,6 +137,7 @@ function handleStreamingInput(call, callback) { * @param {Call} call Call to handle */ function handleStreamingOutput(call) { + echoHeader(call); var req = call.request; var payload_type = req.response_type; if (payload_type === 'RANDOM') { @@ -113,7 +152,7 @@ function handleStreamingOutput(call) { } }); }); - call.end(); + call.end(getEchoTrailer(call)); } /** @@ -122,6 +161,7 @@ function handleStreamingOutput(call) { * @param {Call} call Call to handle */ function handleFullDuplex(call) { + echoHeader(call); call.on('data', function(value) { var payload_type = value.response_type; if (payload_type === 'RANDOM') { @@ -138,7 +178,7 @@ function handleFullDuplex(call) { }); }); call.on('end', function() { - call.end(); + call.end(getEchoTrailer(call)); }); } diff --git a/src/node/interop/messages.proto b/src/node/interop/messages.proto deleted file mode 100644 index 7df85e3c13..0000000000 --- a/src/node/interop/messages.proto +++ /dev/null @@ -1,132 +0,0 @@ - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Message definitions to be used by integration test service definitions. - -syntax = "proto3"; - -package grpc.testing; - -// The type of payload that should be returned. -enum PayloadType { - // Compressable text format. - COMPRESSABLE = 0; - - // Uncompressable binary format. - UNCOMPRESSABLE = 1; - - // Randomly chosen from all other formats defined in this enum. - RANDOM = 2; -} - -// A block of data, to simply increase gRPC message size. -message Payload { - // The type of data in body. - PayloadType type = 1; - // Primary contents of payload. - bytes body = 2; -} - -// Unary request. -message SimpleRequest { - // Desired payload type in the response from the server. - // If response_type is RANDOM, server randomly chooses one from other formats. - PayloadType response_type = 1; - - // Desired payload size in the response from the server. - // If response_type is COMPRESSABLE, this denotes the size before compression. - int32 response_size = 2; - - // Optional input payload sent along with the request. - Payload payload = 3; - - // Whether SimpleResponse should include username. - bool fill_username = 4; - - // Whether SimpleResponse should include OAuth scope. - bool fill_oauth_scope = 5; -} - -// Unary response, as configured by the request. -message SimpleResponse { - // Payload to increase message size. - Payload payload = 1; - // The user the request came from, for verifying authentication was - // successful when the client expected it. - string username = 2; - // OAuth scope. - string oauth_scope = 3; -} - -// Client-streaming request. -message StreamingInputCallRequest { - // Optional input payload sent along with the request. - Payload payload = 1; - - // Not expecting any payload from the response. -} - -// Client-streaming response. -message StreamingInputCallResponse { - // Aggregated size of payloads received from the client. - int32 aggregated_payload_size = 1; -} - -// Configuration for a particular response. -message ResponseParameters { - // Desired payload sizes in responses from the server. - // If response_type is COMPRESSABLE, this denotes the size before compression. - int32 size = 1; - - // Desired interval between consecutive responses in the response stream in - // microseconds. - int32 interval_us = 2; -} - -// Server-streaming request. -message StreamingOutputCallRequest { - // Desired payload type in the response from the server. - // If response_type is RANDOM, the payload from each response in the stream - // might be of different types. This is to simulate a mixed type of payload - // stream. - PayloadType response_type = 1; - - // Configuration for each expected response message. - repeated ResponseParameters response_parameters = 2; - - // Optional input payload sent along with the request. - Payload payload = 3; -} - -// Server-streaming response, as configured by the request and parameters. -message StreamingOutputCallResponse { - // Payload to increase response size. - Payload payload = 1; -} diff --git a/src/node/interop/test.proto b/src/node/interop/test.proto deleted file mode 100644 index 24e67497fa..0000000000 --- a/src/node/interop/test.proto +++ /dev/null @@ -1,72 +0,0 @@ - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// An integration test service that covers all the method signature permutations -// of unary/streaming requests/responses. - -syntax = "proto3"; - -import "empty.proto"; -import "messages.proto"; - -package grpc.testing; - -// A simple service to test the various types of RPCs and experiment with -// performance with various types of payload. -service TestService { - // One empty request followed by one empty response. - rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty); - - // One request followed by one response. - rpc UnaryCall(SimpleRequest) returns (SimpleResponse); - - // One request followed by a sequence of responses (streamed download). - // The server returns the payload with client desired type and sizes. - rpc StreamingOutputCall(StreamingOutputCallRequest) - returns (stream StreamingOutputCallResponse); - - // A sequence of requests followed by one response (streamed upload). - // The server returns the aggregated size of client payload as the result. - rpc StreamingInputCall(stream StreamingInputCallRequest) - returns (StreamingInputCallResponse); - - // A sequence of requests with each request served by the server immediately. - // As one request could lead to multiple responses, this interface - // demonstrates the idea of full duplexing. - rpc FullDuplexCall(stream StreamingOutputCallRequest) - returns (stream StreamingOutputCallResponse); - - // A sequence of requests followed by a sequence of responses. - // The server buffers all the client requests and then serves them in order. A - // stream of responses are returned to the client when the server starts with - // first request. - rpc HalfDuplexCall(stream StreamingOutputCallRequest) - returns (stream StreamingOutputCallResponse); -} diff --git a/src/node/package.json b/src/node/package.json deleted file mode 100644 index 0aeca5f659..0000000000 --- a/src/node/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "grpc", - "version": "0.11.1", - "author": "Google Inc.", - "description": "gRPC Library for Node", - "homepage": "http://www.grpc.io/", - "repository": { - "type": "git", - "url": "https://github.com/grpc/grpc.git" - }, - "bugs": "https://github.com/grpc/grpc/issues", - "contributors": [ - { - "name": "Michael Lumish", - "email": "mlumish@google.com" - } - ], - "directories": { - "lib": "src", - "example": "examples" - }, - "scripts": { - "lint": "node ./node_modules/jshint/bin/jshint src test examples interop index.js", - "test": "./node_modules/.bin/mocha && npm run-script lint", - "gen_docs": "./node_modules/.bin/jsdoc -c jsdoc_conf.json", - "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha" - }, - "dependencies": { - "bindings": "^1.2.0", - "lodash": "^3.9.3", - "nan": "^2.0.0", - "protobufjs": "^4.0.0" - }, - "devDependencies": { - "async": "^0.9.0", - "google-auth-library": "^0.9.2", - "istanbul": "^0.3.21", - "jsdoc": "^3.3.2", - "jshint": "^2.5.0", - "minimist": "^1.1.0", - "mocha": "~1.21.0", - "mustache": "^2.0.0", - "strftime": "^0.8.2" - }, - "engines": { - "node": ">=0.10.13" - }, - "files": [ - "LICENSE", - "README.md", - "index.js", - "binding.gyp", - "bin", - "cli", - "examples", - "ext", - "health_check", - "interop", - "src", - "test" - ], - "main": "index.js", - "license": "BSD-3-Clause" -} diff --git a/src/node/examples/perf_test.js b/src/node/performance/perf_test.js index ba8fbf88d2..fe51e4a603 100644 --- a/src/node/examples/perf_test.js +++ b/src/node/performance/perf_test.js @@ -42,7 +42,7 @@ function runTest(iterations, callback) { var testServer = interop_server.getServer(0, false); testServer.server.start(); var client = new testProto.TestService('localhost:' + testServer.port, - grpc.Credentials.createInsecure()); + grpc.credentials.createInsecure()); function runIterations(finish) { var start = process.hrtime(); diff --git a/src/node/examples/qps_test.js b/src/node/performance/qps_test.js index ec968b8540..491f47364c 100644 --- a/src/node/examples/qps_test.js +++ b/src/node/performance/qps_test.js @@ -62,7 +62,7 @@ function runTest(concurrent_calls, seconds, callback) { var testServer = interop_server.getServer(0, false); testServer.server.start(); var client = new testProto.TestService('localhost:' + testServer.port, - grpc.Credentials.createInsecure()); + grpc.credentials.createInsecure()); var warmup_num = 100; diff --git a/src/node/src/client.js b/src/node/src/client.js index 7f510231b3..909376e766 100644 --- a/src/node/src/client.js +++ b/src/node/src/client.js @@ -40,7 +40,7 @@ var _ = require('lodash'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); var common = require('./common'); @@ -54,7 +54,7 @@ var Readable = stream.Readable; var Writable = stream.Writable; var Duplex = stream.Duplex; var util = require('util'); -var version = require('../package.json').version; +var version = require('../../../package.json').version; util.inherits(ClientWritableStream, Writable); @@ -233,17 +233,23 @@ function getCall(channel, method, options) { var host; var parent; var propagate_flags; + var credentials; if (options) { deadline = options.deadline; host = options.host; parent = _.get(options, 'parent.call'); propagate_flags = options.propagate_flags; + credentials = options.credentials; } if (deadline === undefined) { deadline = Infinity; } - return new grpc.Call(channel, method, deadline, host, - parent, propagate_flags); + var call = new grpc.Call(channel, method, deadline, host, + parent, propagate_flags); + if (credentials) { + call.setCredentials(credentials); + } + return call; } /** @@ -282,60 +288,53 @@ function makeUnaryRequestFunction(method, serialize, deserialize) { emitter.getPeer = function getPeer() { return call.getPeer(); }; - this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) { - if (error) { - call.cancel(); - callback(error); - return; - } - var client_batch = {}; - var message = serialize(argument); - if (options) { - message.grpcWriteFlags = options.flags; - } - client_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - client_batch[grpc.opType.SEND_MESSAGE] = message; - client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - client_batch[grpc.opType.RECV_MESSAGE] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - var status = response.status; - var error; - var deserialized; - if (status.code === grpc.status.OK) { - if (err) { - // Got a batch error, but OK status. Something went wrong - callback(err); - return; - } else { - try { - deserialized = deserialize(response.read); - } catch (e) { - /* Change status to indicate bad server response. This will result - * in passing an error to the callback */ - status = { - code: grpc.status.INTERNAL, - details: 'Failed to parse server response' - }; - } - } - } - if (status.code !== grpc.status.OK) { - error = new Error(response.status.details); - error.code = status.code; - error.metadata = status.metadata; - callback(error); + var client_batch = {}; + var message = serialize(argument); + if (options) { + message.grpcWriteFlags = options.flags; + } + client_batch[grpc.opType.SEND_INITIAL_METADATA] = + metadata._getCoreRepresentation(); + client_batch[grpc.opType.SEND_MESSAGE] = message; + client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; + client_batch[grpc.opType.RECV_INITIAL_METADATA] = true; + client_batch[grpc.opType.RECV_MESSAGE] = true; + client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; + call.startBatch(client_batch, function(err, response) { + response.status.metadata = Metadata._fromCoreRepresentation( + response.status.metadata); + var status = response.status; + var error; + var deserialized; + if (status.code === grpc.status.OK) { + if (err) { + // Got a batch error, but OK status. Something went wrong + callback(err); + return; } else { - callback(null, deserialized); + try { + deserialized = deserialize(response.read); + } catch (e) { + /* Change status to indicate bad server response. This will result + * in passing an error to the callback */ + status = { + code: grpc.status.INTERNAL, + details: 'Failed to parse server response' + }; + } } - emitter.emit('status', status); - emitter.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); + } + if (status.code !== grpc.status.OK) { + error = new Error(response.status.details); + error.code = status.code; + error.metadata = status.metadata; + callback(error); + } else { + callback(null, deserialized); + } + emitter.emit('status', status); + emitter.emit('metadata', Metadata._fromCoreRepresentation( + response.metadata)); }); return emitter; } @@ -371,62 +370,55 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) { metadata = metadata.clone(); } var stream = new ClientWritableStream(call, serialize); - this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) { - if (error) { - call.cancel(); - callback(error); + var metadata_batch = {}; + metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = + metadata._getCoreRepresentation(); + metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true; + call.startBatch(metadata_batch, function(err, response) { + if (err) { + // The call has stopped for some reason. A non-OK status will arrive + // in the other batch. return; } - var metadata_batch = {}; - metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - call.startBatch(metadata_batch, function(err, response) { + stream.emit('metadata', Metadata._fromCoreRepresentation( + response.metadata)); + }); + var client_batch = {}; + client_batch[grpc.opType.RECV_MESSAGE] = true; + client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; + call.startBatch(client_batch, function(err, response) { + response.status.metadata = Metadata._fromCoreRepresentation( + response.status.metadata); + var status = response.status; + var error; + var deserialized; + if (status.code === grpc.status.OK) { if (err) { - // The call has stopped for some reason. A non-OK status will arrive - // in the other batch. + // Got a batch error, but OK status. Something went wrong + callback(err); return; - } - stream.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); - var client_batch = {}; - client_batch[grpc.opType.RECV_MESSAGE] = true; - client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(client_batch, function(err, response) { - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - var status = response.status; - var error; - var deserialized; - if (status.code === grpc.status.OK) { - if (err) { - // Got a batch error, but OK status. Something went wrong - callback(err); - return; - } else { - try { - deserialized = deserialize(response.read); - } catch (e) { - /* Change status to indicate bad server response. This will result - * in passing an error to the callback */ - status = { - code: grpc.status.INTERNAL, - details: 'Failed to parse server response' - }; - } - } - } - if (status.code !== grpc.status.OK) { - error = new Error(response.status.details); - error.code = status.code; - error.metadata = status.metadata; - callback(error); } else { - callback(null, deserialized); + try { + deserialized = deserialize(response.read); + } catch (e) { + /* Change status to indicate bad server response. This will result + * in passing an error to the callback */ + status = { + code: grpc.status.INTERNAL, + details: 'Failed to parse server response' + }; + } } - stream.emit('status', status); - }); + } + if (status.code !== grpc.status.OK) { + error = new Error(response.status.details); + error.code = status.code; + error.metadata = status.metadata; + callback(error); + } else { + callback(null, deserialized); + } + stream.emit('status', status); }); return stream; } @@ -462,51 +454,44 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) { metadata = metadata.clone(); } var stream = new ClientReadableStream(call, deserialize); - this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) { - if (error) { - call.cancel(); - stream.emit('error', error); + var start_batch = {}; + var message = serialize(argument); + if (options) { + message.grpcWriteFlags = options.flags; + } + start_batch[grpc.opType.SEND_INITIAL_METADATA] = + metadata._getCoreRepresentation(); + start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; + start_batch[grpc.opType.SEND_MESSAGE] = message; + start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; + call.startBatch(start_batch, function(err, response) { + if (err) { + // The call has stopped for some reason. A non-OK status will arrive + // in the other batch. return; } - var start_batch = {}; - var message = serialize(argument); - if (options) { - message.grpcWriteFlags = options.flags; - } - start_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - start_batch[grpc.opType.SEND_MESSAGE] = message; - start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true; - call.startBatch(start_batch, function(err, response) { + stream.emit('metadata', Metadata._fromCoreRepresentation( + response.metadata)); + }); + var status_batch = {}; + status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; + call.startBatch(status_batch, function(err, response) { + response.status.metadata = Metadata._fromCoreRepresentation( + response.status.metadata); + stream.emit('status', response.status); + if (response.status.code !== grpc.status.OK) { + var error = new Error(response.status.details); + error.code = response.status.code; + error.metadata = response.status.metadata; + stream.emit('error', error); + return; + } else { if (err) { - // The call has stopped for some reason. A non-OK status will arrive - // in the other batch. - return; - } - stream.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); - var status_batch = {}; - status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(status_batch, function(err, response) { - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - stream.emit('status', response.status); - if (response.status.code !== grpc.status.OK) { - var error = new Error(response.status.details); - error.code = response.status.code; - error.metadata = response.status.metadata; - stream.emit('error', error); + // Got a batch error, but OK status. Something went wrong + stream.emit('error', err); return; - } else { - if (err) { - // Got a batch error, but OK status. Something went wrong - stream.emit('error', err); - return; - } } - }); + } }); return stream; } @@ -540,45 +525,38 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) { metadata = metadata.clone(); } var stream = new ClientDuplexStream(call, serialize, deserialize); - this.$updateMetadata(this.$auth_uri, metadata, function(error, metadata) { - if (error) { - call.cancel(); - stream.emit('error', error); + var start_batch = {}; + start_batch[grpc.opType.SEND_INITIAL_METADATA] = + metadata._getCoreRepresentation(); + start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; + call.startBatch(start_batch, function(err, response) { + if (err) { + // The call has stopped for some reason. A non-OK status will arrive + // in the other batch. return; } - var start_batch = {}; - start_batch[grpc.opType.SEND_INITIAL_METADATA] = - metadata._getCoreRepresentation(); - start_batch[grpc.opType.RECV_INITIAL_METADATA] = true; - call.startBatch(start_batch, function(err, response) { + stream.emit('metadata', Metadata._fromCoreRepresentation( + response.metadata)); + }); + var status_batch = {}; + status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; + call.startBatch(status_batch, function(err, response) { + response.status.metadata = Metadata._fromCoreRepresentation( + response.status.metadata); + stream.emit('status', response.status); + if (response.status.code !== grpc.status.OK) { + var error = new Error(response.status.details); + error.code = response.status.code; + error.metadata = response.status.metadata; + stream.emit('error', error); + return; + } else { if (err) { - // The call has stopped for some reason. A non-OK status will arrive - // in the other batch. + // Got a batch error, but OK status. Something went wrong + stream.emit('error', err); return; } - stream.emit('metadata', Metadata._fromCoreRepresentation( - response.metadata)); - }); - var status_batch = {}; - status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true; - call.startBatch(status_batch, function(err, response) { - response.status.metadata = Metadata._fromCoreRepresentation( - response.status.metadata); - stream.emit('status', response.status); - if (response.status.code !== grpc.status.OK) { - var error = new Error(response.status.details); - error.code = response.status.code; - error.metadata = response.status.metadata; - stream.emit('error', error); - return; - } else { - if (err) { - // Got a batch error, but OK status. Something went wrong - stream.emit('error', err); - return; - } - } - }); + } }); return stream; } @@ -618,15 +596,8 @@ exports.makeClientConstructor = function(methods, serviceName) { * @param {grpc.Credentials} credentials Credentials to use to connect * to the server * @param {Object} options Options to pass to the underlying channel - * @param {function(string, Object, function)=} updateMetadata function to - * update the metadata for each request */ - function Client(address, credentials, options, updateMetadata) { - if (!updateMetadata) { - updateMetadata = function(uri, metadata, callback) { - callback(null, metadata); - }; - } + function Client(address, credentials, options) { if (!options) { options = {}; } @@ -634,11 +605,6 @@ exports.makeClientConstructor = function(methods, serviceName) { /* Private fields use $ as a prefix instead of _ because it is an invalid * prefix of a method name */ this.$channel = new grpc.Channel(address, credentials, options); - // Remove the optional DNS scheme, trailing port, and trailing backslash - address = address.replace(/^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$/, '$2'); - this.$server_address = address; - this.$auth_uri = 'https://' + this.$server_address + '/' + serviceName; - this.$updateMetadata = updateMetadata; } _.each(methods, function(attrs, name) { diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js new file mode 100644 index 0000000000..009ff63306 --- /dev/null +++ b/src/node/src/credentials.js @@ -0,0 +1,163 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** + * Credentials module + * + * This module contains factory methods for two different credential types: + * CallCredentials and ChannelCredentials. ChannelCredentials are things like + * SSL credentials that can be used to secure a connection, and are used to + * construct a Client object. CallCredentials genrally modify metadata, so they + * can be attached to an individual method call. + * + * CallCredentials can be composed with other CallCredentials to create + * CallCredentials. ChannelCredentials can be composed with CallCredentials + * to create ChannelCredentials. No combined credential can have more than + * one ChannelCredentials. + * + * For example, to create a client secured with SSL that uses Google + * default application credentials to authenticate: + * + * var channel_creds = credentials.createSsl(root_certs); + * (new GoogleAuth()).getApplicationDefault(function(err, credential) { + * var call_creds = credentials.createFromGoogleCredential(credential); + * var combined_creds = credentials.combineChannelCredentials( + * channel_creds, call_creds); + * var client = new Client(address, combined_creds); + * }); + * + * @module + */ + +'use strict'; + +var grpc = require('bindings')('grpc_node.node'); + +var CallCredentials = grpc.CallCredentials; + +var ChannelCredentials = grpc.ChannelCredentials; + +var Metadata = require('./metadata.js'); + +/** + * Create an SSL Credentials object. If using a client-side certificate, both + * the second and third arguments must be passed. + * @param {Buffer} root_certs The root certificate data + * @param {Buffer=} private_key The client certificate private key, if + * applicable + * @param {Buffer=} cert_chain The client certificate cert chain, if applicable + * @return {ChannelCredentials} The SSL Credentials object + */ +exports.createSsl = ChannelCredentials.createSsl; + +/** + * Create a gRPC credentials object from a metadata generation function. This + * function gets the service URL and a callback as parameters. The error + * passed to the callback can optionally have a 'code' value attached to it, + * which corresponds to a status code that this library uses. + * @param {function(String, function(Error, Metadata))} metadata_generator The + * function that generates metadata + * @return {CallCredentials} The credentials object + */ +exports.createFromMetadataGenerator = function(metadata_generator) { + return CallCredentials.createFromPlugin(function(service_url, callback) { + metadata_generator(service_url, function(error, metadata) { + var code = grpc.status.OK; + var message = ''; + if (error) { + message = error.message; + if (error.hasOwnProperty('code')) { + code = error.code; + } + } + callback(code, message, metadata._getCoreRepresentation()); + }); + }); +}; + +/** + * Create a gRPC credential from a Google credential object. + * @param {Object} google_credential The Google credential object to use + * @return {CallCredentials} The resulting credentials object + */ +exports.createFromGoogleCredential = function(google_credential) { + return exports.createFromMetadataGenerator(function(service_url, callback) { + google_credential.getRequestMetadata(service_url, function(err, header) { + if (err) { + callback(err); + return; + } + var metadata = new Metadata(); + metadata.add('authorization', header.Authorization); + callback(null, metadata); + }); + }); +}; + +/** + * Combine a ChannelCredentials with any number of CallCredentials into a single + * ChannelCredentials object. + * @param {ChannelCredentials} channel_credential The ChannelCredentials to + * start with + * @param {...CallCredentials} credentials The CallCredentials to compose + * @return ChannelCredentials A credentials object that combines all of the + * input credentials + */ +exports.combineChannelCredentials = function(channel_credential) { + var current = channel_credential; + for (var i = 1; i < arguments.length; i++) { + current = current.compose(arguments[i]); + } + return current; +}; + +/** + * Combine any number of CallCredentials into a single CallCredentials object + * @param {...CallCredentials} credentials the CallCredentials to compose + * @return CallCredentials A credentials object that combines all of the input + * credentials + */ +exports.combineCallCredentials = function() { + var current = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + current = current.compose(arguments[i]); + } + return current; +} + +/** + * Create an insecure credentials object. This is used to create a channel that + * does not use SSL. This cannot be composed with anything. + * @return {ChannelCredentials} The insecure credentials object + */ +exports.createInsecure = ChannelCredentials.createInsecure; diff --git a/src/node/src/metadata.js b/src/node/src/metadata.js index c1da70b197..5c24e46c9b 100644 --- a/src/node/src/metadata.js +++ b/src/node/src/metadata.js @@ -59,6 +59,7 @@ function normalizeKey(key) { function validate(key, value) { if (_.endsWith(key, '-bin')) { if (!(value instanceof Buffer)) { + console.log(value.constructor.toString()); throw new Error('keys that end with \'-bin\' must have Buffer values'); } } else { @@ -173,7 +174,9 @@ Metadata.prototype._getCoreRepresentation = function() { Metadata._fromCoreRepresentation = function(metadata) { var newMetadata = new Metadata(); if (metadata) { - newMetadata._internal_repr = _.cloneDeep(metadata); + _.forOwn(metadata, function(value, key) { + newMetadata._internal_repr[key] = _.clone(value); + }); } return newMetadata; }; diff --git a/src/node/src/server.js b/src/node/src/server.js index 70b4a9d80e..87b5b7ad06 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -40,7 +40,7 @@ var _ = require('lodash'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); var common = require('./common'); diff --git a/src/node/test/async_test.js b/src/node/test/async_test.js index e81de62bc9..ce3ce50a2d 100644 --- a/src/node/test/async_test.js +++ b/src/node/test/async_test.js @@ -36,7 +36,7 @@ var assert = require('assert'); var grpc = require('..'); -var math = grpc.load(__dirname + '/../examples/math.proto').math; +var math = grpc.load(__dirname + '/math/math.proto').math; /** @@ -47,7 +47,7 @@ var math_client; /** * Server to test against */ -var getServer = require('../examples/math_server.js'); +var getServer = require('./math/math_server.js'); var server = getServer(); diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js index e7f071bcd5..c316fe7f10 100644 --- a/src/node/test/call_test.js +++ b/src/node/test/call_test.js @@ -34,7 +34,7 @@ 'use strict'; var assert = require('assert'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); /** * Helper function to return an absolute deadline given a relative timeout in @@ -48,7 +48,7 @@ function getDeadline(timeout_secs) { return deadline; } -var insecureCreds = grpc.Credentials.createInsecure(); +var insecureCreds = grpc.ChannelCredentials.createInsecure(); describe('call', function() { var channel; diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js index d81df2a36d..b86f89b8a8 100644 --- a/src/node/test/channel_test.js +++ b/src/node/test/channel_test.js @@ -34,7 +34,7 @@ 'use strict'; var assert = require('assert'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); /** * This is used for testing functions with multiple asynchronous calls that @@ -56,7 +56,7 @@ function multiDone(done, count) { } }; } -var insecureCreds = grpc.Credentials.createInsecure(); +var insecureCreds = grpc.ChannelCredentials.createInsecure(); describe('channel', function() { describe('constructor', function() { @@ -149,12 +149,13 @@ describe('channel', function() { afterEach(function() { channel.close(); }); - it('should time out if called alone', function(done) { + it.only('should time out if called alone', function(done) { var old_state = channel.getConnectivityState(); var deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 1); channel.watchConnectivityState(old_state, deadline, function(err, value) { assert(err); + console.log('Callback from watchConnectivityState'); done(); }); }); diff --git a/src/node/test/constant_test.js b/src/node/test/constant_test.js index fa06ad4e4d..b17cd339cb 100644 --- a/src/node/test/constant_test.js +++ b/src/node/test/constant_test.js @@ -34,7 +34,7 @@ 'use strict'; var assert = require('assert'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); /** * List of all status names diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js new file mode 100644 index 0000000000..8eb91ee69e --- /dev/null +++ b/src/node/test/credentials_test.js @@ -0,0 +1,243 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +var assert = require('assert'); +var fs = require('fs'); +var path = require('path'); + +var grpc = require('..'); + +/** + * This is used for testing functions with multiple asynchronous calls that + * can happen in different orders. This should be passed the number of async + * function invocations that can occur last, and each of those should call this + * function's return value + * @param {function()} done The function that should be called when a test is + * complete. + * @param {number} count The number of calls to the resulting function if the + * test passes. + * @return {function()} The function that should be called at the end of each + * sequence of asynchronous functions. + */ +function multiDone(done, count) { + return function() { + count -= 1; + if (count <= 0) { + done(); + } + }; +} + +describe('client credentials', function() { + var Client; + var server; + var port; + var client_ssl_creds; + var client_options = {}; + before(function() { + var proto = grpc.load(__dirname + '/test_service.proto'); + server = new grpc.Server(); + server.addProtoService(proto.TestService.service, { + unary: function(call, cb) { + call.sendMetadata(call.metadata); + cb(null, {}); + }, + clientStream: function(stream, cb){ + stream.on('data', function(data) {}); + stream.on('end', function() { + stream.sendMetadata(stream.metadata); + cb(null, {}); + }); + }, + serverStream: function(stream) { + stream.sendMetadata(stream.metadata); + stream.end(); + }, + bidiStream: function(stream) { + stream.on('data', function(data) {}); + stream.on('end', function() { + stream.sendMetadata(stream.metadata); + stream.end(); + }); + } + }); + var key_path = path.join(__dirname, './data/server1.key'); + var pem_path = path.join(__dirname, './data/server1.pem'); + var key_data = fs.readFileSync(key_path); + var pem_data = fs.readFileSync(pem_path); + var creds = grpc.ServerCredentials.createSsl(null, + [{private_key: key_data, + cert_chain: pem_data}]); + //creds = grpc.ServerCredentials.createInsecure(); + port = server.bind('localhost:0', creds); + server.start(); + + Client = proto.TestService; + var ca_path = path.join(__dirname, '../test/data/ca.pem'); + var ca_data = fs.readFileSync(ca_path); + client_ssl_creds = grpc.credentials.createSsl(ca_data); + var host_override = 'foo.test.google.fr'; + client_options['grpc.ssl_target_name_override'] = host_override; + client_options['grpc.default_authority'] = host_override; + }); + after(function() { + server.forceShutdown(); + }); + it('Should accept SSL creds for a client', function(done) { + var client = new Client('localhost:' + port, client_ssl_creds, + client_options); + client.unary({}, function(err, data) { + assert.ifError(err); + done(); + }); + }); + it('Should update metadata with SSL creds', function(done) { + var metadataUpdater = function(service_url, callback) { + var metadata = new grpc.Metadata(); + metadata.set('plugin_key', 'plugin_value'); + callback(null, metadata); + }; + var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater); + var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds, + creds); + var client = new Client('localhost:' + port, combined_creds, + client_options); + var call = client.unary({}, function(err, data) { + assert.ifError(err); + }); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + }); + it('Should update metadata for two simultaneous calls', function(done) { + done = multiDone(done, 2); + var metadataUpdater = function(service_url, callback) { + var metadata = new grpc.Metadata(); + metadata.set('plugin_key', 'plugin_value'); + callback(null, metadata); + }; + var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater); + var combined_creds = grpc.credentials.combineCredentials(client_ssl_creds, + creds); + var client = new Client('localhost:' + port, combined_creds, + client_options); + var call = client.unary({}, function(err, data) { + assert.ifError(err); + }); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + var call2 = client.unary({}, function(err, data) { + assert.ifError(err); + }); + call2.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + }); + describe('Per-rpc creds', function() { + var client; + var updater_creds; + before(function() { + client = new Client('localhost:' + port, client_ssl_creds, + client_options); + var metadataUpdater = function(service_url, callback) { + var metadata = new grpc.Metadata(); + metadata.set('plugin_key', 'plugin_value'); + callback(null, metadata); + }; + updater_creds = grpc.credentials.createFromMetadataGenerator( + metadataUpdater); + }); + it('Should update metadata on a unary call', function(done) { + var call = client.unary({}, function(err, data) { + assert.ifError(err); + }, null, {credentials: updater_creds}); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + }); + it('should update metadata on a client streaming call', function(done) { + var call = client.clientStream(function(err, data) { + assert.ifError(err); + }, null, {credentials: updater_creds}); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + call.end(); + }); + it('should update metadata on a server streaming call', function(done) { + var call = client.serverStream({}, null, {credentials: updater_creds}); + call.on('data', function() {}); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + }); + it('should update metadata on a bidi streaming call', function(done) { + var call = client.bidiStream(null, {credentials: updater_creds}); + call.on('data', function() {}); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + done(); + }); + call.end(); + }); + it('should be able to use multiple plugin credentials', function(done) { + var altMetadataUpdater = function(service_url, callback) { + var metadata = new grpc.Metadata(); + metadata.set('other_plugin_key', 'other_plugin_value'); + callback(null, metadata); + }; + var alt_updater_creds = grpc.credentials.createFromMetadataGenerator( + altMetadataUpdater); + var combined_updater = grpc.credentials.combineCallCredentials( + updater_creds, alt_updater_creds); + var call = client.unary({}, function(err, data) { + assert.ifError(err); + }, null, {credentials: updater_creds}); + call.on('metadata', function(metadata) { + assert.deepEqual(metadata.get('plugin_key'), ['plugin_value']); + assert.deepEqual(metadata.get('other_plugin_key'), + ['other_plugin_value']); + done(); + }); + }); + }); +}); diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js index 4b8da3bfb1..0f6c5941c4 100644 --- a/src/node/test/end_to_end_test.js +++ b/src/node/test/end_to_end_test.js @@ -34,7 +34,7 @@ 'use strict'; var assert = require('assert'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); /** * This is used for testing functions with multiple asynchronous calls that @@ -57,7 +57,7 @@ function multiDone(done, count) { }; } -var insecureCreds = grpc.Credentials.createInsecure(); +var insecureCreds = grpc.ChannelCredentials.createInsecure(); describe('end-to-end', function() { var server; diff --git a/src/node/test/health_test.js b/src/node/test/health_test.js index 9267bff7eb..a4dc24cf46 100644 --- a/src/node/test/health_test.js +++ b/src/node/test/health_test.js @@ -54,7 +54,7 @@ describe('Health Checking', function() { grpc.ServerCredentials.createInsecure()); healthServer.start(); healthClient = new health.Client('localhost:' + port_num, - grpc.Credentials.createInsecure()); + grpc.credentials.createInsecure()); }); after(function() { healthServer.forceShutdown(); diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js index 2ca07c1d50..804c1d45e4 100644 --- a/src/node/test/interop_sanity_test.js +++ b/src/node/test/interop_sanity_test.js @@ -90,4 +90,8 @@ describe('Interop tests', function() { interop_client.runTest(port, name_override, 'timeout_on_sleeping_server', true, true, done); }); + it('should pass custom_metadata', function(done) { + interop_client.runTest(port, name_override, 'custom_metadata', + true, true, done); + }); }); diff --git a/src/node/examples/math.proto b/src/node/test/math/math.proto index 311e148c02..311e148c02 100644 --- a/src/node/examples/math.proto +++ b/src/node/test/math/math.proto diff --git a/src/node/examples/math_server.js b/src/node/test/math/math_server.js index a4b237aeeb..9d06596f3d 100644 --- a/src/node/examples/math_server.js +++ b/src/node/test/math/math_server.js @@ -33,7 +33,7 @@ 'use strict'; -var grpc = require('..'); +var grpc = require('../..'); var math = grpc.load(__dirname + '/math.proto').math; /** diff --git a/src/node/test/math_client_test.js b/src/node/test/math_client_test.js index 6a6607ec74..6361d97857 100644 --- a/src/node/test/math_client_test.js +++ b/src/node/test/math_client_test.js @@ -36,7 +36,7 @@ var assert = require('assert'); var grpc = require('..'); -var math = grpc.load(__dirname + '/../examples/math.proto').math; +var math = grpc.load(__dirname + '/math/math.proto').math; /** * Client to use to make requests to a running server. @@ -46,7 +46,7 @@ var math_client; /** * Server to test against */ -var getServer = require('../examples/math_server.js'); +var getServer = require('./math/math_server.js'); var server = getServer(); @@ -56,7 +56,7 @@ describe('Math client', function() { grpc.ServerCredentials.createInsecure()); server.start(); math_client = new math.Math('localhost:' + port_num, - grpc.Credentials.createInsecure()); + grpc.credentials.createInsecure()); done(); }); after(function() { diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js index 1e69d52e58..999a183b3c 100644 --- a/src/node/test/server_test.js +++ b/src/node/test/server_test.js @@ -36,7 +36,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var grpc = require('bindings')('grpc.node'); +var grpc = require('bindings')('grpc_node'); describe('server', function() { describe('constructor', function() { diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js index d917c7a171..395ea887ec 100644 --- a/src/node/test/surface_test.js +++ b/src/node/test/surface_test.js @@ -41,7 +41,7 @@ var ProtoBuf = require('protobufjs'); var grpc = require('..'); -var math_proto = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto'); +var math_proto = ProtoBuf.loadProtoFile(__dirname + '/math/math.proto'); var mathService = math_proto.lookup('math.Math'); @@ -163,7 +163,7 @@ describe('waitForClientReady', function() { Client = surface_client.makeProtobufClientConstructor(mathService); }); beforeEach(function() { - client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); + client = new Client('localhost:' + port, grpc.credentials.createInsecure()); }); after(function() { server.forceShutdown(); @@ -217,7 +217,7 @@ describe('Echo service', function() { }); var port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(echo_service); - client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); + client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); after(function() { @@ -263,7 +263,7 @@ describe('Generic client and server', function() { server.start(); var Client = grpc.makeGenericClientConstructor(string_service_attrs); client = new Client('localhost:' + port, - grpc.Credentials.createInsecure()); + grpc.credentials.createInsecure()); }); after(function() { server.forceShutdown(); @@ -311,7 +311,7 @@ describe('Echo metadata', function() { }); var port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(test_service); - client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); + client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); metadata = new grpc.Metadata(); metadata.set('key', 'value'); @@ -356,7 +356,7 @@ describe('Echo metadata', function() { call.end(); }); it('shows the correct user-agent string', function(done) { - var version = require('../package.json').version; + var version = require('../../../package.json').version; var call = client.unary({}, function(err, data) { assert.ifError(err); }, metadata); call.on('metadata', function(metadata) { @@ -437,7 +437,7 @@ describe('Other conditions', function() { }); port = server.bind('localhost:0', server_insecure_creds); Client = surface_client.makeProtobufClientConstructor(test_service); - client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); + client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); after(function() { @@ -484,7 +484,7 @@ describe('Other conditions', function() { var Client = surface_client.makeClientConstructor(test_service_attrs, 'TestService'); misbehavingClient = new Client('localhost:' + port, - grpc.Credentials.createInsecure()); + grpc.credentials.createInsecure()); }); it('should respond correctly to a unary call', function(done) { misbehavingClient.unary(badArg, function(err, data) { @@ -690,165 +690,187 @@ describe('Other conditions', function() { }); }); }); - describe('Call propagation', function() { - var proxy; - var proxy_impl; - beforeEach(function() { - proxy = new grpc.Server(); - proxy_impl = { - unary: function(call) {}, - clientStream: function(stream) {}, - serverStream: function(stream) {}, - bidiStream: function(stream) {} - }; +}); +describe('Call propagation', function() { + var proxy; + var proxy_impl; + + var test_service; + var Client; + var client; + var server; + before(function() { + var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto'); + test_service = test_proto.lookup('TestService'); + server = new grpc.Server(); + server.addProtoService(test_service, { + unary: function(call) {}, + clientStream: function(stream) {}, + serverStream: function(stream) {}, + bidiStream: function(stream) {} }); - afterEach(function() { - console.log('Shutting down server'); - proxy.forceShutdown(); - }); - describe('Cancellation', function() { - it('With a unary call', function(done) { - done = multiDone(done, 2); - proxy_impl.unary = function(parent, callback) { - client.unary(parent.request, function(err, value) { - try { - assert(err); - assert.strictEqual(err.code, grpc.status.CANCELLED); - } finally { - callback(err, value); - done(); - } - }, null, {parent: parent}); - call.cancel(); - }; - proxy.addProtoService(test_service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.Credentials.createInsecure()); - var call = proxy_client.unary({}, function(err, value) { - done(); - }); - }); - it('With a client stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.clientStream = function(parent, callback) { - client.clientStream(function(err, value) { - try { - assert(err); - assert.strictEqual(err.code, grpc.status.CANCELLED); - } finally { - callback(err, value); - done(); - } - }, null, {parent: parent}); - call.cancel(); - }; - proxy.addProtoService(test_service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.Credentials.createInsecure()); - var call = proxy_client.clientStream(function(err, value) { - done(); - }); - }); - it('With a server stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.serverStream = function(parent) { - var child = client.serverStream(parent.request, null, - {parent: parent}); - child.on('error', function(err) { + var port = server.bind('localhost:0', server_insecure_creds); + Client = surface_client.makeProtobufClientConstructor(test_service); + client = new Client('localhost:' + port, grpc.credentials.createInsecure()); + server.start(); + }); + after(function() { + server.forceShutdown(); + }); + beforeEach(function() { + proxy = new grpc.Server(); + proxy_impl = { + unary: function(call) {}, + clientStream: function(stream) {}, + serverStream: function(stream) {}, + bidiStream: function(stream) {} + }; + }); + afterEach(function() { + proxy.forceShutdown(); + }); + describe('Cancellation', function() { + it('With a unary call', function(done) { + done = multiDone(done, 2); + proxy_impl.unary = function(parent, callback) { + client.unary(parent.request, function(err, value) { + try { assert(err); assert.strictEqual(err.code, grpc.status.CANCELLED); + } finally { + callback(err, value); done(); - }); - call.cancel(); - }; - proxy.addProtoService(test_service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.Credentials.createInsecure()); - var call = proxy_client.serverStream({}); - call.on('error', function(err) { - done(); - }); + } + }, null, {parent: parent}); + call.cancel(); + }; + proxy.addProtoService(test_service, proxy_impl); + var proxy_port = proxy.bind('localhost:0', server_insecure_creds); + proxy.start(); + var proxy_client = new Client('localhost:' + proxy_port, + grpc.credentials.createInsecure()); + var call = proxy_client.unary({}, function(err, value) { + done(); }); - it('With a bidi stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.bidiStream = function(parent) { - var child = client.bidiStream(null, {parent: parent}); - child.on('error', function(err) { + }); + it('With a client stream call', function(done) { + done = multiDone(done, 2); + proxy_impl.clientStream = function(parent, callback) { + client.clientStream(function(err, value) { + try { assert(err); assert.strictEqual(err.code, grpc.status.CANCELLED); + } finally { + callback(err, value); done(); - }); - call.cancel(); - }; - proxy.addProtoService(test_service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.Credentials.createInsecure()); - var call = proxy_client.bidiStream(); - call.on('error', function(err) { + } + }, null, {parent: parent}); + call.cancel(); + }; + proxy.addProtoService(test_service, proxy_impl); + var proxy_port = proxy.bind('localhost:0', server_insecure_creds); + proxy.start(); + var proxy_client = new Client('localhost:' + proxy_port, + grpc.credentials.createInsecure()); + var call = proxy_client.clientStream(function(err, value) { + done(); + }); + }); + it('With a server stream call', function(done) { + done = multiDone(done, 2); + proxy_impl.serverStream = function(parent) { + var child = client.serverStream(parent.request, null, + {parent: parent}); + child.on('error', function(err) { + assert(err); + assert.strictEqual(err.code, grpc.status.CANCELLED); done(); }); + call.cancel(); + }; + proxy.addProtoService(test_service, proxy_impl); + var proxy_port = proxy.bind('localhost:0', server_insecure_creds); + proxy.start(); + var proxy_client = new Client('localhost:' + proxy_port, + grpc.credentials.createInsecure()); + var call = proxy_client.serverStream({}); + call.on('error', function(err) { + done(); }); }); - describe('Deadline', function() { - /* jshint bitwise:false */ - var deadline_flags = (grpc.propagate.DEFAULTS & - ~grpc.propagate.CANCELLATION); - it('With a client stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.clientStream = function(parent, callback) { - client.clientStream(function(err, value) { - try { - assert(err); - assert(err.code === grpc.status.DEADLINE_EXCEEDED || - err.code === grpc.status.INTERNAL); - } finally { - callback(err, value); - done(); - } - }, null, {parent: parent, propagate_flags: deadline_flags}); - }; - proxy.addProtoService(test_service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.Credentials.createInsecure()); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - proxy_client.clientStream(function(err, value) { + it('With a bidi stream call', function(done) { + done = multiDone(done, 2); + proxy_impl.bidiStream = function(parent) { + var child = client.bidiStream(null, {parent: parent}); + child.on('error', function(err) { + assert(err); + assert.strictEqual(err.code, grpc.status.CANCELLED); done(); - }, null, {deadline: deadline}); - }); - it('With a bidi stream call', function(done) { - done = multiDone(done, 2); - proxy_impl.bidiStream = function(parent) { - var child = client.bidiStream( - null, {parent: parent, propagate_flags: deadline_flags}); - child.on('error', function(err) { + }); + call.cancel(); + }; + proxy.addProtoService(test_service, proxy_impl); + var proxy_port = proxy.bind('localhost:0', server_insecure_creds); + proxy.start(); + var proxy_client = new Client('localhost:' + proxy_port, + grpc.credentials.createInsecure()); + var call = proxy_client.bidiStream(); + call.on('error', function(err) { + done(); + }); + }); + }); + describe('Deadline', function() { + /* jshint bitwise:false */ + var deadline_flags = (grpc.propagate.DEFAULTS & + ~grpc.propagate.CANCELLATION); + it('With a client stream call', function(done) { + done = multiDone(done, 2); + proxy_impl.clientStream = function(parent, callback) { + client.clientStream(function(err, value) { + try { assert(err); assert(err.code === grpc.status.DEADLINE_EXCEEDED || err.code === grpc.status.INTERNAL); + } finally { + callback(err, value); done(); - }); - }; - proxy.addProtoService(test_service, proxy_impl); - var proxy_port = proxy.bind('localhost:0', server_insecure_creds); - proxy.start(); - var proxy_client = new Client('localhost:' + proxy_port, - grpc.Credentials.createInsecure()); - var deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - var call = proxy_client.bidiStream(null, {deadline: deadline}); - call.on('error', function(err) { + } + }, null, {parent: parent, propagate_flags: deadline_flags}); + }; + proxy.addProtoService(test_service, proxy_impl); + var proxy_port = proxy.bind('localhost:0', server_insecure_creds); + proxy.start(); + var proxy_client = new Client('localhost:' + proxy_port, + grpc.credentials.createInsecure()); + var deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + proxy_client.clientStream(function(err, value) { + done(); + }, null, {deadline: deadline}); + }); + it('With a bidi stream call', function(done) { + done = multiDone(done, 2); + proxy_impl.bidiStream = function(parent) { + var child = client.bidiStream( + null, {parent: parent, propagate_flags: deadline_flags}); + child.on('error', function(err) { + assert(err); + assert(err.code === grpc.status.DEADLINE_EXCEEDED || + err.code === grpc.status.INTERNAL); done(); }); + }; + proxy.addProtoService(test_service, proxy_impl); + var proxy_port = proxy.bind('localhost:0', server_insecure_creds); + proxy.start(); + var proxy_client = new Client('localhost:' + proxy_port, + grpc.credentials.createInsecure()); + var deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + var call = proxy_client.bidiStream(null, {deadline: deadline}); + call.on('error', function(err) { + done(); }); }); }); @@ -866,7 +888,7 @@ describe('Cancelling surface client', function() { }); var port = server.bind('localhost:0', server_insecure_creds); var Client = surface_client.makeProtobufClientConstructor(mathService); - client = new Client('localhost:' + port, grpc.Credentials.createInsecure()); + client = new Client('localhost:' + port, grpc.credentials.createInsecure()); server.start(); }); after(function() { diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c index 0f730ea756..4ad78ea0a3 100644 --- a/src/php/ext/grpc/php_grpc.c +++ b/src/php/ext/grpc/php_grpc.c @@ -109,91 +109,120 @@ PHP_MINIT_FUNCTION(grpc) { */ /* Register call error constants */ grpc_init(); - REGISTER_LONG_CONSTANT("Grpc\\CALL_OK", GRPC_CALL_OK, CONST_CS); - REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR", GRPC_CALL_ERROR, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_OK", GRPC_CALL_OK, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR", GRPC_CALL_ERROR, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_ON_SERVER", - GRPC_CALL_ERROR_NOT_ON_SERVER, CONST_CS); + GRPC_CALL_ERROR_NOT_ON_SERVER, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_ON_CLIENT", - GRPC_CALL_ERROR_NOT_ON_CLIENT, CONST_CS); + GRPC_CALL_ERROR_NOT_ON_CLIENT, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_ALREADY_INVOKED", - GRPC_CALL_ERROR_ALREADY_INVOKED, CONST_CS); + GRPC_CALL_ERROR_ALREADY_INVOKED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_INVOKED", - GRPC_CALL_ERROR_NOT_INVOKED, CONST_CS); + GRPC_CALL_ERROR_NOT_INVOKED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_ALREADY_FINISHED", - GRPC_CALL_ERROR_ALREADY_FINISHED, CONST_CS); + GRPC_CALL_ERROR_ALREADY_FINISHED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_TOO_MANY_OPERATIONS", - GRPC_CALL_ERROR_TOO_MANY_OPERATIONS, CONST_CS); + GRPC_CALL_ERROR_TOO_MANY_OPERATIONS, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_INVALID_FLAGS", - GRPC_CALL_ERROR_INVALID_FLAGS, CONST_CS); + GRPC_CALL_ERROR_INVALID_FLAGS, + CONST_CS | CONST_PERSISTENT); /* Register flag constants */ REGISTER_LONG_CONSTANT("Grpc\\WRITE_BUFFER_HINT", GRPC_WRITE_BUFFER_HINT, - CONST_CS); + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\WRITE_NO_COMPRESS", GRPC_WRITE_NO_COMPRESS, - CONST_CS); + CONST_CS | CONST_PERSISTENT); /* Register status constants */ - REGISTER_LONG_CONSTANT("Grpc\\STATUS_OK", GRPC_STATUS_OK, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_OK", GRPC_STATUS_OK, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_CANCELLED", GRPC_STATUS_CANCELLED, - CONST_CS); - REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNKNOWN", GRPC_STATUS_UNKNOWN, CONST_CS); + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNKNOWN", GRPC_STATUS_UNKNOWN, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_INVALID_ARGUMENT", - GRPC_STATUS_INVALID_ARGUMENT, CONST_CS); + GRPC_STATUS_INVALID_ARGUMENT, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_DEADLINE_EXCEEDED", - GRPC_STATUS_DEADLINE_EXCEEDED, CONST_CS); + GRPC_STATUS_DEADLINE_EXCEEDED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_NOT_FOUND", GRPC_STATUS_NOT_FOUND, - CONST_CS); + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_ALREADY_EXISTS", - GRPC_STATUS_ALREADY_EXISTS, CONST_CS); + GRPC_STATUS_ALREADY_EXISTS, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_PERMISSION_DENIED", - GRPC_STATUS_PERMISSION_DENIED, CONST_CS); + GRPC_STATUS_PERMISSION_DENIED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNAUTHENTICATED", - GRPC_STATUS_UNAUTHENTICATED, CONST_CS); + GRPC_STATUS_UNAUTHENTICATED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_RESOURCE_EXHAUSTED", - GRPC_STATUS_RESOURCE_EXHAUSTED, CONST_CS); + GRPC_STATUS_RESOURCE_EXHAUSTED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_FAILED_PRECONDITION", - GRPC_STATUS_FAILED_PRECONDITION, CONST_CS); - REGISTER_LONG_CONSTANT("Grpc\\STATUS_ABORTED", GRPC_STATUS_ABORTED, CONST_CS); + GRPC_STATUS_FAILED_PRECONDITION, + CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_ABORTED", GRPC_STATUS_ABORTED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_OUT_OF_RANGE", GRPC_STATUS_OUT_OF_RANGE, - CONST_CS); + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNIMPLEMENTED", - GRPC_STATUS_UNIMPLEMENTED, CONST_CS); + GRPC_STATUS_UNIMPLEMENTED, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_INTERNAL", GRPC_STATUS_INTERNAL, - CONST_CS); + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNAVAILABLE", GRPC_STATUS_UNAVAILABLE, - CONST_CS); + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\STATUS_DATA_LOSS", GRPC_STATUS_DATA_LOSS, - CONST_CS); + CONST_CS | CONST_PERSISTENT); /* Register op type constants */ REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_INITIAL_METADATA", - GRPC_OP_SEND_INITIAL_METADATA, CONST_CS); + GRPC_OP_SEND_INITIAL_METADATA, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_MESSAGE", - GRPC_OP_SEND_MESSAGE, CONST_CS); + GRPC_OP_SEND_MESSAGE, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_CLOSE_FROM_CLIENT", - GRPC_OP_SEND_CLOSE_FROM_CLIENT, CONST_CS); + GRPC_OP_SEND_CLOSE_FROM_CLIENT, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_SEND_STATUS_FROM_SERVER", - GRPC_OP_SEND_STATUS_FROM_SERVER, CONST_CS); + GRPC_OP_SEND_STATUS_FROM_SERVER, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_INITIAL_METADATA", - GRPC_OP_RECV_INITIAL_METADATA, CONST_CS); + GRPC_OP_RECV_INITIAL_METADATA, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_MESSAGE", - GRPC_OP_RECV_MESSAGE, CONST_CS); + GRPC_OP_RECV_MESSAGE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_STATUS_ON_CLIENT", - GRPC_OP_RECV_STATUS_ON_CLIENT, CONST_CS); + GRPC_OP_RECV_STATUS_ON_CLIENT, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\OP_RECV_CLOSE_ON_SERVER", - GRPC_OP_RECV_CLOSE_ON_SERVER, CONST_CS); + GRPC_OP_RECV_CLOSE_ON_SERVER, + CONST_CS | CONST_PERSISTENT); /* Register connectivity state constants */ REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_IDLE", - GRPC_CHANNEL_IDLE, CONST_CS); + GRPC_CHANNEL_IDLE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_CONNECTING", - GRPC_CHANNEL_CONNECTING, CONST_CS); + GRPC_CHANNEL_CONNECTING, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_READY", - GRPC_CHANNEL_READY, CONST_CS); + GRPC_CHANNEL_READY, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_TRANSIENT_FAILURE", - GRPC_CHANNEL_TRANSIENT_FAILURE, CONST_CS); + GRPC_CHANNEL_TRANSIENT_FAILURE, + CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("Grpc\\CHANNEL_FATAL_FAILURE", - GRPC_CHANNEL_FATAL_FAILURE, CONST_CS); + GRPC_CHANNEL_FATAL_FAILURE, + CONST_CS | CONST_PERSISTENT); grpc_init_call(TSRMLS_C); grpc_init_channel(TSRMLS_C); |