A (wrapped) werkzeug handler for serving health pills.

    Accepts POST requests and responds with health pills. The request accepts
    several POST parameters:

      node_names: (required string) A JSON-ified list of node names for which
          the client would like to request health pills.
      run: (optional string) The run to retrieve health pills for. Defaults to
          '.'. This data is sent via POST (not GET) since URL length is limited.
      step: (optional integer): The session run step for which to
          retrieve health pills. If provided, the handler reads the health pills
          of that step from disk (which is slow) and produces a response with
          only health pills at that step. If not provided, the handler returns a
          response with health pills at all steps sampled by the event
          multiplexer (the fast path). The motivation here is that, sometimes,
          one desires to examine health pills at a specific step (to say find
          the first step that causes a model to blow up with NaNs).
          get_plugin_apps must be called before this slower feature is used
          because that method passes the logdir (directory path) to this plugin.

    This handler responds with a JSON-ified object mapping from node names to a
    list (of size 1) of health pill event objects, each of which has these
    properties.

    {
        'wall_time': float,
        'step': int,
        'node_name': string,
        'output_slot': int,
        # A list of 12 floats that summarizes the elements of the tensor.
        'value': float[],
    }

    Node names for which there are no health pills to be found are excluded from
    the mapping.

    Args:
      request: The request issued by the client for health pills.

    Returns:
      A werkzeug BaseResponse object.

def _serve_health_pills_handler(self, request):
    """A (wrapped) werkzeug handler for serving health pills.

    Accepts POST requests and responds with health pills. The request accepts
    several POST parameters:

      node_names: (required string) A JSON-ified list of node names for which
          the client would like to request health pills.
      run: (optional string) The run to retrieve health pills for. Defaults to
          '.'. This data is sent via POST (not GET) since URL length is limited.
      step: (optional integer): The session run step for which to
          retrieve health pills. If provided, the handler reads the health pills
          of that step from disk (which is slow) and produces a response with
          only health pills at that step. If not provided, the handler returns a
          response with health pills at all steps sampled by the event
          multiplexer (the fast path). The motivation here is that, sometimes,
          one desires to examine health pills at a specific step (to say find
          the first step that causes a model to blow up with NaNs).
          get_plugin_apps must be called before this slower feature is used
          because that method passes the logdir (directory path) to this plugin.

    This handler responds with a JSON-ified object mapping from node names to a
    list (of size 1) of health pill event objects, each of which has these
    properties.

    {
        'wall_time': float,
        'step': int,
        'node_name': string,
        'output_slot': int,
        # A list of 12 floats that summarizes the elements of the tensor.
        'value': float[],
    }

    Node names for which there are no health pills to be found are excluded from
    the mapping.

    Args:
      request: The request issued by the client for health pills.

    Returns:
      A werkzeug BaseResponse object.
    """
    if request.method != 'POST':
      return wrappers.Response(response=(
          '%s requests are forbidden by the debugger plugin.' %
          request.method), status=405)

    if _NODE_NAMES_POST_KEY not in request.form:
      return wrappers.Response(response=(
          'The %r POST key was not found in the request for health pills.' %
          _NODE_NAMES_POST_KEY), status=400)

    jsonified_node_names = request.form[_NODE_NAMES_POST_KEY]
    try:
      node_names = json.loads(tf.compat.as_text(jsonified_node_names))
    except Exception as e:  # pylint: disable=broad-except
      # Different JSON libs raise different exceptions, so we just do a
      # catch-all here. This problem is complicated by how Tensorboard might be
      # run in many different environments, as it is open-source.
      # TODO(@caisq, @chihuahua): Create platform-dependent adapter to catch
      # specific types of exceptions, instead of the broad catching here.
      logger.error('Could not decode node name JSON string %r: %s',
                       jsonified_node_names, e)
      return wrappers.Response(status=400)

    if not isinstance(node_names, list):
      logger.error('%r is not a JSON list of node names:',
                       jsonified_node_names)
      return wrappers.Response(status=400)

    run = request.form.get(_RUN_POST_KEY, _DEFAULT_RUN)
    step_string = request.form.get(_STEP_POST_KEY, None)
    if step_string is None:
      # Use all steps sampled by the event multiplexer (Relatively fast).
      mapping = self._obtain_sampled_health_pills(run, node_names)
    else:
      # Read disk to obtain the health pills for that step (Relatively slow).
      # Make sure that the directory for the run exists.
      # Determine the directory of events file to read.
      events_directory = self._logdir
      if run != _DEFAULT_RUN:
        # Use the directory for the specific run.
        events_directory = os.path.join(events_directory, run)

      step = int(step_string)
      try:
        mapping = self._obtain_health_pills_at_step(
            events_directory, node_names, step)
      except IOError as error:
        logger.error(
            'Error retrieving health pills for step %d: %s', step, error)
        return wrappers.Response(status=404)

    # Convert event_accumulator.HealthPillEvents to JSON-able dicts.
    jsonable_mapping = {}
    for node_name, events in mapping.items():
      jsonable_mapping[node_name] = [e._asdict() for e in events]
    return http_util.Respond(request, jsonable_mapping, 'application/json')