Bowker Image Service Documentation

Overview

The primary function of the Bowker Image Service is to allow you to obtain the image that corresponds to an image identifier such as an ISBN-10, ISBN-13, EAN, ISSN, UPC, or OCLC. You provide the image identifier and some optional parameters, and the service returns the associated image to you.

A typical request for an image might look like this:

http://imageweb.bowker.com/rest/images/ean/0-262-19502-X?size=200x200

The above would translate into English like this:

Please provide the image that's associated with the ISBN-10 0-262-19502-X, sized to fit into a square 200 pixels wide by 200 pixels high.

Because the format is not specified, the service returns the image in the JPEG format. The PNG format is also supported.

The Bowker Image Service also allows for authorized clients to perform the following sorts of functions:

  • Submit new images and update or delete existing images
  • Harvest insertions, updates, and deletions in order to, for example, keep a separate store in sync with the image service store
  • Determine, without retrieving an image, if an image exists for a given identifier
  • With a single request, obtain an image that matches any of several identifiers
  • Ask for an annotates image (the client can specify the annotation)
  • Ask for a special image to be returned when the requested image is not found or when there's some other common errors such as the client lacking the permission to see a given image

Standard Image Distribution

The image service is ideal for Web applications that want to obtain and serve images to end-users on the fly, without having to keep an image store. These types of applications can apply the Standard Image Distribution (SID) methodologies:

SID1:

  1. End-user requests information, including a book cover, relating to book X
  2. Web application obtains the information for the book from one source (a local database or maybe the Bowker Meta Data Service)
  3. Web application assembles HTML for the end-user, including a link to an image-fetcher part of the Web application
  4. End-user's browser receives the HTML and issues a new request for the image using the image-fetcher URI
  5. The image-fetcher issues a request to the Bowker Image Service using the Web application's credentials
  6. The Bowker Image Service returns the image data to the image-fetcher
  7. The image-fetcher sends the image data to the end-user's browser

SID2:

  1. End-user requests information, including a book cover, relating to book X
  2. Web application obtains the information for the book from one source (a local database or maybe the Bowker Meta Data Service).
  3. Web application looks in location Y in a local drive to determine if it has the image that the end-user requested. If the image doesn't exist in location Y, Web application obtains the image from the Bowker Image Service and stores it in a temporary location Y.
  4. Web application assembles HTML for the end-user, including a link to the image file in location Y, and sends this HTML back to the end-user
  5. End-user's browser receives the HTML and issues a new request for the image in location Y
  6. Web application returns the image in location Y to the end-user
  7. Web application keeps the image in location Y for some period of time and then deletes it

There are two important things to keep in mind about SID mythologies:

  1. The end user never requests an image directly from the Bowker Image Service
    To do so, the end-user would have to come into possession of the Web application's credentials for the Bowker Image Service. It's important for the safety of Bowker clients and Bowker itself that Bowker clients do not distribute the credentials that Bowker issues to Bowker clients.
  2. The Web application does not retain images that it obtains from Bowker
    Bowker is continuously updating the images in the image service, ensuring that the most up-to-date and accurate data is always available. By deleting the cached image, the Web application can be sure that it is always serving the latest version or correction of an image.

Tags and Roles

Tags can be used to retrieve lists of images. Roles are used to grant access to users on a per-image basis.

There are some important considerations to keep in mind when uploading images to the Bowker Image Service. Most of them derive from these facts:

  • Tags are stored in a relational database and are not accessible to someone who is merely retrieving an image
  • The relational database can be queried to retrieve a list of images that have specific tags associated with them
  • The roles for an image are stored in the file system alongside the image itself and are not accessible to someone querying the image database
  • The roles for an image dictate who was access to the image
  • Every role must have an associated tag, but tags with no corresponding role can exist

The considerations that arise are as follows:

  • You should submit all images with at least one tag
  • You should submit all images with at least one role
  • Make sure that your credentials are associated with at least one the roles that you are assigning to an image
  • When uploading an image, you must provide tags that correspond to every role that you're assigning to the image

REST

The Bowker Image Service is a RESTful service. You can reference resources on the service and request operations on those resources. To the service, the images are resources. Other resources include forms, lists, and tags. Forms allow you to submit image requests, insertions, or updates. However, forms should be used for testing the service only. Lists are simply lists of images that satisfy certain criteria. Tags are keywords that you can associate with images.

Each resource has a URI and allowed operations for the resource map to HTTP verbs as follows:

Operation HTTP Verb
Retrieve a resource GET
Change a resource (insert or update) POST
Delete a resource DELETE

You can perform all these operations except for Delete using a regular HTML form. (HTML forms don't support the HTTP verb DELETE.) Example HTML forms exist as resources on the service at http://imageweb.bowker.com/rest/images/forms.

You can perform all the operations if you use an HTTP client designed to interact with the Bowker Image Service. Such clients are easy to build and several clients already exist. The Technical Interface Reference section (below) describes the Bowker Image Service API and provides example client code in several programming languages.

Technical Interface Reference

Resources

Images
/rest/images
The front page for the REST interface. This resource supports the following HTTP verb.
  • GET
    Returns XHTML with examples and links to documentation and other Bowker Image Service resources.

/rest/images/[type]/[id]
This path points to an image resource of type type and with image ID id.

Valid values for type are:

  • ean
  • issn
  • upc
  • oclc

The id value must be in the format that the given type value implies. However, in the case of the ean type, you may supply the id value in one of the following formats: EAN, ISBN-10, ISBN-13, ISSN-13.

An image resource supports the following HTTP verbs.

  • GET
    Returns the specified image. When accessed with GET, the resource supports the following parameters:
    • bgcolor
      This parameter allows you to specify the background color for the overlay text. When you request a text overlay on the image with the text parameter, the image service first draws a semi-transparent rectangle in the middle of the image and then draws the text on top of that rectangle. This parameter defines the color of the semi-transparent rectangle. If you request that the text be rendered below the image (with the textpos parameter), then the service draws a solid rectangle of color bgcolor under the image and renders the text on top of that. Colors can be specified with a word (for example 'blue'), with a 6-digit hex value such as 0000ff, or with a 3-number decimal value like 0,0,255.
    • fgcolor
      This parameter allows you to specify the color for the overlay text. Colors can be specified with a word (for example 'blue'), with a 6-digit hex value such as 0000ff, or with a 3-number decimal value like 0,0,255.
    • format
      The client can request one of the following formats: png, jpg, or info. The png and jpg values cause the image service to return binary data. The info format causes the image service to return a small XML file with some information about the image, including if the image is present, the size of the image, and the roles that are associated with the image. If you don't specify a format with this parameter, the service returns images in JPEG format.
    • height
      The height of the image to return. The aspect ratio of the image remains the same always. The client can specify only one of width, height, or size. And when the client fails to provide one of those values, service returns the image in the default size, 110x170.
    • html
      Currently the only legal values for this parameter are '' (empty string) and 'image.html'. If client doesn't specify a value, it defaults to the empty string. If the client specifies 'image.html', client is requesting not an image but rather HTML text that includes a reference to the image. The reference to the image includes all the parameters that are passed together with the html parameter minus the html parameter itself. The client (or browser will need to make a separate call for the actual image. Important: This feature is provided for testing purposes only and should never be used by a client that is retrieving images from Bowker to serve to another client.
    • nicolor
      When the client specifies a value of 'auto1' for the noimage parameter, the client can specify the background color for the image with this parameter. Colors can be specified with a word (for example 'blue'), with a 6-digit hex value such as 0000ff, or with a 3-number decimal value like 0,0,255.
    • nifixsize
      Normally, when returning a noimage image, the service resizes the image just like any other image. For some types of noimage image, the client can request that the service always return the image in its original size. A good example of this is the pixel.png image, which consists of a single transparent pixel. Many clients would prefer to have the single transparent pixel back when they specify 'pixel.png' for noimage.
    • nitext
      This parameter is exactly like the text parameter described later, but applies to noimage images. The nitext parameter allows the client to specify text to be overlayed on a noimage image. When the service encounters an error (such as an image-not-found error) and is unable to return the requested image, the image service can return a noimage image with a text overlay.
    • nitextpos
      This parameter is exactly like the textpos parameter described later. It allows the client to specify whether to center text on the image or extend the height of the image and place the text below the original image. The nitextpos parameter applies only when both of the following conditions exist: (1) The client has provided text with the nitext paramater and has requested that the image service return a noimage image instead of errors. (2) The image service didn't find the requested image and is returning a noimage image instead. The default value for this parameter is 'middle'. Legal values are 'below' and 'middle'.
    • noimage
      The name of the image to return if the requested image is not found or in place of most other common errors that the service might encounter. This parameter supports multiple values, depending on the client that needs them (clients can submit noimage files). But there are a couple of noimage values that any client can use: one is 'pixel.png', which consists of a single, transparent pixel. The other is 'auto1', which causes the image service to create a noimage image on the fly rather than read it from disk. The auto1 image that the image service creates is really just a rectangle of a solid color. The client can specify the color of the rectangle with the nicolor parameter. If the client omits the noimage parameter, the service returns any errors that it might encounter. For example, if the client requests an image that the service can't find, then the service responds in one of two ways: if the client specifies a good value for noimage, the service returns the image with that filename. But if the client doesn't specify a value for noimage, the service returns a 404 Not Found error to indicate that the image that the client requested was not found. The purpose of this parameter is to reduce the amount of code that needs to be developed for a client that merely displays a special image when the requested image is not found or when there's some other such common error. In most cases, it's better if the client receives the 404 Not Found error and then takes an appropriate action on its own.
    • size
      This value, which is in the format WxH (for example, 120x200), will restrict the size of the image intelligently, without changing its aspect ratio, so that the image best fits within a rectangle of the specified size. The service also allows the value original here, returning the image in the same size that it was originally submitted to the image service. If no width, height, or size values are provided, the service returns images of size 110x170.
    • text
      The client can request that the resource include a text overlay. The image will be returned with the text that the client specifies here drawn across the middle of the image or appended to the bottom of the image.
    • textfont
      The font to use for the text overlay. Use the fonts resource to obtain a list of the fonts available on the image service. Italics fonts have different names than regular fonts, so if the client needs an italic font, it must specify the name of an italic font. There's no separate parameter to make an arbitrary font italic. The same is true for bold fonts.
    • textpos
      Where to position the text overlay. Valid values are 'middle' and 'below'. The 'middle' value causes the text to be drawn centered vertically and horizontally on the image. The 'below' value causes the height of the image to be extended with a solid-color rectangle at the bottom of the image; the text is centered vertically and horizontally in the rectangle.
    • textsize
      When requesting that text be drawn upon the image with the text parameter, you can use the textsize parameter to specify the size of the font in points.
    • textu
      Set this parameter to 1 or 0 to indicate underlined or regular overlay text, respectively.
    • width
      The width of the image to return. The aspect ratio of the image remains the same always. The client can specify only one of width, height, or size. And when the client fails to provide one of those values, service returns the image in the default size, 110x170.
  • POST
    Submits a new, non existing image resource or updates an existing image resource. If the method succeeds in creating the new image, it returns the URI of the newly created image resource. That URI will come back in the HTTP Location header (which will cause a standard web browser to redirect to the new location) as well as in the content of the response. Upon failure, the method returns an HTTP error with a description. The image resource supports the following parameters for the POST method:
    • image_data
      The image's binary data.
    • notes
      A free-style text note to be associated with the image.
    • tag
      A tag name. The client can use the Tags resource to obtain published tag names. A client may submit multiple tag values by providing a list of comma-separated tags for this parameter.
    • role
      A role name. The client can use the Roles resource to obtain published role names. A client may submit multiple roles by submitting a list of comma-separated roles for this parameter. For each role that the client submits, the client must submit an corresponding identical tag. The reverse is not true.
    • action
      One of the following values: c, u, cu, ow, for create, update, create-or-update, and overwrite, respectively. If this parameter is not specified, it defaults to cu. All of these values except for ow put image replacement operations under the control of image replacement rules. The ow value causes the image service to ignore any rules that it normally follows when determining if a new image should replace an existing image and to instead always overwrite the existing image.
  • DELETE
    Submits a deletion request to the resource. If the image is deleted, this method returns the HTTP constant OK. Otherwise, it returns an HTTP error with a description.

/rest/images/anyof
This resource allows a client to retrieve an image given up to 3 image IDs. Here's an example URI:

.../rest/images/anyof?ean=9781234567890&upc=012345678912&oclc=87654321

When a client requests an anyof resource, the image service tries to look up the image by EAN, UPC, then OCLC, in that order. If the client doesn't specify one of those types of ID or if the service fails to locate an image for the specified ID, the service skips to the next ID type. If the service fails to find an image after checking all the types that the client submitted, the service behaves as if you had called the resource URI that corresponds to the first type from among EAN, UPC, and OCLC for which the client provided a value. For example, if the client submits oclc=123&ean=456 and the service is unable to locate an image for either ID, the service will return the same error that it would have returned had the client requested the image with the following URI:

http://imageweb.bowker.com/rest/images/ean/456

Similarly, when the service finds an image, it responds as if the client had issued a request using the resource URI that corresponds to the ID type for which the service found an image.

The anyof resource supports only the GET method. The resource supports all the parameters of the /rest/images/type/id resource, and the following parameters in addition to those:

  • ean
  • upc
  • oclc

Forms
Form resources are a convenience that the image service provides to allow humans to interact with the service directly via a browser.

Form resources support only the GET method.

/rest/images/forms
A list of forms that can be used for fetching and manipulating images.

/rest/images/forms/get
This form allows you to obtain an image resource. The form resource supports the following parameters:
  • type
    Valid values are ean, issn, upc, and oclc. If you don't specify this parameter, the value will default to ean, causing the requested form to require an EAN, ISBN-10, or ISBN-13 value to fetch an image.

This form provides one means of easily issuing an HTTP GET method to a resource of the type /rest/images/[type]/[id], described in the section #Images.

/rest/images/forms/update
This form allows you to insert a new image resource or replace an existing one. The form resource supports the following parameters:
  • type
    Valid values are ean, issn, upc, and oclc. The form resource defaults to type = ean if the parameter is not specified.

This form provides one means of easily issuing an HTTP POST method with a resource of the type /rest/images/[type]/[id], described in the section Images.

Lists
A list resource consists of a list of image resources. For example, if the client wants to obtain a list of the images that were added or modified in the last day, the client would request a list resource.

The service is able to provide complete support for harvesters via list resources. When a harvester requests a list of images and the length of the list is larger than what the image service can return in one call, the image service returns a portion of the list and indicates, via the next-offset attribute of the root element, that the client should make additional calls to retrieve the rest of the list. If the value of the next-offset attribute is an integer greater than 0, then the client should repeat the call that it just made but with the offset parameter set to the value of the next-offset attribute. Otherwise, if the value of the next-offset attribute is 0, the client can assume it has received the complete list.

List resources support only the GET method.

/rest/images/list
A list of images that satisfy the given conditions. Supports the following parameters:

  • before
    Images that were updated before (and including on) the given date. Provide the date in YYYY-MM-DD format.
  • deleted
    If you provide the value 1 for this parameter, the list will contain images that satisfy any other given conditions and that were deleted from the image service. If you don't specify this parameter or if you specify 0, then the list will contain existing images only (no deleted images). So you if you wanted a list of the images that were deleted since 2007-03-08, you would specify the since parameter with that date and the deleted parameter with the value 1.
  • format
    How you want the list to be formatted. Legal values are xml, html, text, uri, isbn10, and isbn10gif. The default and most useful format is xml. That format provides hints as to what the next request should look like when the current request returns results that exceed the maximum that the image service can return in one request.
  • isbn-format
    Valid values are '10' and '13', for ISBN-10 and ISBN-13, respectively. Use this parameter only when specifying 'XML' for the format parameter and requiring that the service provide ISBNs in the old 10-digit format. In conjuction with a value of 10 for this parameter, clients should consider also providing a value of '978' for the type parameter.
  • limit
    The maximum number of records you want in the list.
  • offset
    This parameter is useful for obtaining the next batch or IDs in the list when the result count is larger than what the image service can return in one call. By default, the image service indicates in the result set what value this parameter should be set to in the next call. If the image service indicates that the next offset is 0, the client can assume that it has retrieved the entire result set.
  • since
    Images that were updated since (and including on) the given date. Provide the date in YYYY-MM-DD format.
  • tag
    A comma-separated list of tags that you want the images to have. References to images without at least one of these tags will not appear in the result set.
  • type
    The type of images you want in the list. Legal values are gtin, isbn, isbn978, issn, upc, and oclc.
  • user
    Images that the given user uploaded.

Tags
The Bowker image service allows clients to tag images with keywords when those images are loaded or updated. The Tags resource allows you to list existing tags or to create new ones. When a client program wants to allow a user to tag an image that the user is uploading or updating, the client can obtain a list of published tags using the Tags resource. Only published tags can be associated with an image. But publishing a tag is easy.

Tags are stored in a database. They're associated with one ore more images. It's possible to use the list resource to obtain a list of the images that are associated with one or more tags.

The Tags resource supports the GET and POST methods. Tag deletion is not supported, so tags must be deleted manually.

/rest/images/tags
  • GET
    Returns a list of the tags that the service publishes. The GET method accepts no parameters.
  • POST
    Allows the client to submit a new tag for inclusion among the service's published tags. The resource accepts the following parameters for the POST method:
    • kw
      The text for the new tag. Alternatively, this text can be specified in the URI as follows: /rest/images/tags/[text].

Roles
The Bowker Image service allows you to associate roles with an image when you submit the image to the service. The roles you associate with the image must be among the roles that the service publishes. The service publishes roles via the Roles resource.

Roles are stored in files that are written alongside the image byte files, in the file system. For this reason, roles can be used for image-based access control and can be displayed or manipulated easily given an image. But because roles are not kept in a database, one cannot query for images with specific roles. However, when a client submits an image with a given role, the service always requires that the role have an identical tag and that the image be submitted with that identical tag as well as the role. This effectively allows clients to be able to query the service for images of a particular role.

The Roles resource supports the GET and POST methods. Role deletion is not supported, so roles must be deleted manually.

/rest/images/roles
  • GET
    Returns a list of the roles that the service publishes. The GET method accepts no parameters.
  • POST
    Allows the client to submit new roles for inclusion among the service's published roles. The resource accepts the following parameters for the POST method:
    • kw
      The text for the new role. Alternatively, this text can be specified in the URI as follows: /rest/images/roles/[text].

Fonts
The image service allows clients to request images with a text overlay. The clients can specify the font for the text overlay. But the font has to be among the fonts that are installed in the image service. The Fonts resource provides a means for the client to identify the fonts that the service makes available.

/rest/images/fonts
  • GET
    Returns a list of the fonts that the image service supports for text overlays.

Image Service Clients

Several image service clients exist already. The perlbisc.pl and Bisc.NET clients allow us to load large sets of images to the image service. The bliss.pl client is a harvester that can keep separate FTP image stores up to date with changes that we make to the image service. And the dump-images.pl client allows us to dump a large number of images into a folder or directory structure in a local disk.

These clients are all implemented in Perl, except for the Bisc.NET client, which is a Microsoft Windows application written in C#.

The following subsections describe how to write simple image service clients in Perl and in Ruby.

Perl Example

#!/usr/bin/perl

use LWP::UserAgent;
use HTTP::Headers;
use strict;
use warnings;

my $image= '0007156111';
my $headers= HTTP::Headers->new;

# Create a user agent object
my $agent= LWP::UserAgent->new(agent => 'image-service-ref');

# Create the authorization header
$headers->authorization_basic('username', 'password');

# Query the image service
my $r= $agent->get(
  "http://imageweb.bowker.com/rest/images/ean/$image?size=200x200",
  Authorization => $headers->authorization
);

if($r->is_success) {
  # Open a file and write the image to the file
  open(IMAGE_FILE, ">$image.jpg") or die $!;
  binmode IMAGE_FILE; # In case you're still using Windows
  print IMAGE_FILE $r->content;
  close IMAGE_FILE;
  print "Fetched image $image!\n";
}
else {
  # Convert HTML error string into regular text...
  $_= $r->content; s/.+<\/head>//sg; s/<.+?>//sg; s/\n+/\n/sg;

  # ...and print the error
  print "Failed to get image: ", $r->status_line, "\n", $_, "\n";
}

Ruby Example

#!/usr/bin/ruby
require 'net/http'
image = '0007156111'
http = Net::HTTP.new('imageweb.bowker.com')
http.start do |http|
  request= Net::HTTP::Get.new("/rest/images/ean/#{image}?size=200x200")
  request.basic_auth 'username', 'password'
  response= http.request(request)
  response.value
  fout = File.open("#{image}.jpg", 'w')
  fout.puts response.body
  fout.close
end
puts "Fetched image #{image}!\n"

Python Example

#!/usr/bin/python
import urllib2
import base64

image= '0007156111'
uri= "http://imageweb.bowker.com/rest/images/ean/%s?size=200x200" % image
req= urllib2.Request(uri)
base64string= base64.encodestring('%s:%s' % ('username', 'password'))[:-1]
req.add_header("Authorization", "Basic %s" % base64string)
file= open("%s.jpg" % image, 'w')
file.write(urllib2.urlopen(req).read())
file.close
print "Fetched image %s!" % image

PHP Example

<?php

$username = 'your-username';
$password = 'your-secret-password';
$isbn = '1565921496';
$uri = sprintf('http://%s:%s@imageweb.bowker.com/rest/images/ean/%s',
  $username,
  $password,
  $isbn
);

$result = @file_get_contents($uri);
list($version, $status_code, $msg) = explode(' ', $http_response_header[0], 3);
if($status_code != 200) {
  throw new Exception(
    "Imageweb request request failed ($status_code $msg) - $uri"
  );
}

# Return Image
header('Content-Type: image/jpeg');
header('Content-Length: '  .  strlen($result));
echo $result;

?>

General Guidelines for Uploading Images to the Image Service

There's not much to consider when writing a client that downloads an image. The client basically issues an HTTP request that conforms to the specific resource description. In essence, the client assembles a URI and then simply issues an HTTP GET on that URI. The server authenticates the client and returns the image. Here's an example URI:

When creating a client that submits new images, however, there are some subtle nuances.

Tags and Roles
The server will reject image submissions that carry tags or roles that the server doesn't publish. Before you use a tag or a role, you must ensure that the server publishes it. You can check the server's /rest/images/tags or /rest/images/roles URIs for a list of published tags and roles. Or you can add your own tag or role using a form from /rest/images/forms.

Client programs that submit images should first download the list of published tags and roles and verify locally the validity of the tags and roles that the user is associating with images. This is important to prevent the unnecessary waste of bandwidth that an image submission with bad tags or roles brings about. Because the server will be managing millions of images, it will expect clients to behave efficiently and might lock out clients that don't.

Clients must also ensure that every role that is associated with a new image has a corresponding tag with the same name associated with the image.

Updates and Inserts
When a client submits an image, one of the parameters of the submission is the action parameter. If the action parameter indicates that the client is inserting an image and the server finds that the image already exists, the server will return an error indicating that the submission can't be processed because the image already exists.

The same is true when a client submits an update. If the server finds that the image doesn't already exist in the image store, the server will reply with an error indicating that the update didn't occur because the relevant image did not already exist.

There is a value for the action parameter that tells the server that the client is uploading an image for an update or insertion, as the server sees fit. Clients should use this value with care (at least avoiding making this value the default) because when an image is overwritten unintentionally, the image's access rights are also overwritten.

Unless the client uses a fourth action parameter called 'ow', the image service will replace an existing image only subject to certain rules that the Bowker makes available to the service via the rules.conf file. The rules.conf file dictates whether a submission will replace an image, and if so, what access rights the new image will have (based on the access rights of the new image, the access rights of the existing image, and the specific rules that apply to the image replacement operation.

Posting Client Implementation Considerations
The easiest .NET Framework class to use for issuing Internet requests is the WebClient class in the System.Net name space. Unfortunately, that class is just flexible enough to issue a GET request. It can't issue a multi-part form request that includes a file field. So you'll need to build your multi-part form manually and issue it using the HttpWebRequest class.

The documentation for assembling multi-part forms that include a file field is out there, but can take an afternoon to put together sometimes. Here's a template that you can use as a guide:

POST /path/to/program HTTP/1.0
Host: isdev.bowker-int.com
Content-type: multipart/form-data, boundary=evAlartneC036RekwoB
Content-Length: $requestlen

--evAlartneC036RekwoB
content-disposition: form-data; name="title"

$title
--evAlartneC036RekwoB
content-disposition: form-data; name="tags"

$tags
--evAlartneC036RekwoB
content-disposition: form-data; name="image_file"; filename="$filename"
Content-Type: $mimetype
Content-Transfer-Encoding: binary

$binary_file_data
--evAlartneC036RekwoB--

And here are some notes on that template:

Be Faithful
This format will work consistently with every Web application or Web service that accepts multi-part forms, but keep in mind that you have to be exact about some things. If you leave out the dashes in front of a boundary, the POST will fail. If you forget the last two dashes at the end of the last boundary (as Microsoft's .NET Framework classes do), the POST will fail. In general, you have to be careful about keeping exactly to the format I've provided here.

Variables
The words that start with a dollar sign ($), such as $filename, represent variables that the code that issues the POST replaces with actual data. Think of this template as a Perl or PHP string.

New Lines
Each new line is a ridiculous Windows-style new line, \r\n. So, for example, where you see a blank line, the preceding line ended with \r\n\r\n. If you try to use sane new lines (\n), the POST will fail.

Content-Length
Because the content length value represents the length of the entire body, your code should assemble the POST request body entirely first, then calculate the length of the body and prepend the headers.

Header and Body
The Header goes from the beginning of the template until the blank line (the header ends in \r\n\r\n). Everything else is the body.

C# Class For Assembling Multi-part Form POST Requests
The following class allows you to assemble and post a multi-part form request that the Bowker Image Service will understand. The resulting request could be used to insert or update an image in the Bowker Image Store.

using System;
using System.IO;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace bisc {
    class BiscWebClient {
        Encoding _enc= new System.Text.ASCIIEncoding();
        ArrayList _parts= new ArrayList();
        MemoryStream _body= new MemoryStream();
        public string UserAgent= "BISC.NET 0.2";
        string _boundary= "4194304cdb";
        string _uri;
        string _method;

        public BiscWebClient(string method, string uri) {
            _method= method;
            _uri= uri;
        }

        public void AddStandardPart(string name, string value)
        {
            byte[] bytes;
            StringBuilder body= new StringBuilder();
            string[] elements= new String[] {
                "--", _boundary, "\r\n", "Content-Disposition: form-data; name=\"",
                name, "\"\r\n\r\n", value, "\r\n"
            };
            foreach(string s in elements) { body.Append(s); }
            bytes= _enc.GetBytes(body.ToString());
            _body.Write(bytes, 0, bytes.Length);
        }

        public void AddFilePart(
            string name,
            string filename,
            string mimetype,
            byte[] data
        ) {
            StringBuilder body= new StringBuilder();
            MemoryStream ms= new MemoryStream();
            byte[] bytes;
            string[] parts= new String[] {
                "--", _boundary, "\r\n", "Content-Disposition: form-data; name=\"", 
                name, "\"; ", "filename=\"", filename, "\"\r\nContent-Type: ",
                mimetype, "\r\nContent-Transfer-Encoding: binary\r\n\r\n"
            };
            foreach(string s in parts) { body.Append(s); }
            bytes= _enc.GetBytes(body.ToString());
            _body.Write(bytes, 0, bytes.Length);
            _body.Write(data, 0, data.Length);
            bytes= _enc.GetBytes("\r\n");
            _body.Write(bytes, 0, bytes.Length);
        }
                
        public Stream Send()
        {
            HttpWebRequest request= (HttpWebRequest) WebRequest.Create(_uri);
            HttpWebResponse response;
            Stream sout;
            Stream sin;
            StringBuilder eof= new StringBuilder();
            string[] parts= new String[] { "--", _boundary, "--\r\n" };
            byte[] bytes;
            request.Method= _method;
            request.UserAgent= UserAgent;
            request.Accept = "image/gif, image/jpeg, image/png, */*";
            request.KeepAlive = true;
            if(_method == "POST") {
                foreach(string s in parts) { eof.Append(s); }
                bytes= _enc.GetBytes(eof.ToString());
                _body.Write(bytes, 0, bytes.Length);
                bytes= _body.ToArray();
                request.ContentType= "multipart/form-data; boundary=" + _boundary;
                request.ContentLength= bytes.Length;
                sout= request.GetRequestStream();
                sout.Write(bytes, 0, bytes.Length);
                sout.Close();
            }
            try {
                response= (HttpWebResponse)request.GetResponse();
                sin= response.GetResponseStream();
            }
            catch(WebException ex) {
                sin= ex.Response.GetResponseStream();
            }
            return sin;
        }
    }
}

Here's an example of how such a class could be used:

            Bitmap image= Image.FromFile("image.jpg");
            BiscWebClient client= new BiscWebClient(
                "POST", 
                "http://imageweb.bowker.com/rest/images/ean/" + imageid
            );
            client.AddStandardPart("action", "c");
            client.AddStandardPart("notes", "image.jpg");
            foreach(string tagname in (new string[] {'bookcover', 'bip'})) {
                client.AddStandardPart("tag", tagname);
            }
            client.AddFilePart(
                "image_data", 
                "image.jpg",
                "image/jpeg",
                ImageBytes(
                    image, 
                    System.Drawing.Imaging.ImageFormat.Jpeg
                )
            );
            String s= (new StreamReader(client.Send)).ReadToEnd();

If the post is successful, then s contains the bytes of the image. Otherwise, s contains an error string. In this case, rather than reading the response stream into a string, it's best to send it to a Web browser control.

Perl Class For Assembling Multi-part Form POST Requests
Here's a Perl class that assembles and posts multi-part forms designed for inserting, updating, and deleting images in the Bowker Image Service:
package DcBisc;

use LWP::UserAgent;
use HTTP::Request;
use DcUtilities;

use strict;
use warnings;

my $VERSION= '0.1';

sub new {
  my ($proto, $bis_uri)= @_;
  my $class= ref($proto) || $proto;
  my $ua= LWP::UserAgent->new;
  $ua->agent('DcBisc/' . $VERSION);
  $bis_uri.= '/' unless $bis_uri =~ /\/$/;
  bless +{
    ua => $ua,
    uri => $bis_uri,
    tags => 0,
    u => DcUtilities->new
  } => $class;
}

sub agent { $_[0]->{ua}->agent($_[1] . '; DcBisc/' . $VERSION) };

sub get_published_tags {
  my ($self, $refresh)= @_;
  my $response;
  if(!$self->{tags} || $refresh) {
    $response= $self->{ua}->get($self->{uri} . 'tags');
    return +{
      result => '',
      error => 'Failed to get tags.',
      response => $response
    } unless $response->is_success;
    $self->{tags}= +{
      map {split /=/, $_} $self->{u}->remove_if_not(
        sub { my $s= shift; $s=~ s/^\s+|\s+$//g; $s; },
        split("\n", $response->content)
      )
    };
  }
  return +{
    result => $self->{tags},
    error => 0
  };
}

sub upload_image_bytes {
  my ($self, %x)= @_;
  my $response;
  my @content= map {$_ => $x{$_}} qw/action notes/;
  push @content, map {(tag => $_)} @{$x{tags}} if $x{tags} && @{$x{tags}};
  push @content, (image_data => +[
    undef,   # File to read data from (we're providing actual bytes instead)
    'x.img', # Filename (in this case, the filename doesn't matter much)
    'Content-Type' => $x{mime},                 # Header
    'Content-Transfer-Encoding' => 'binary',    # Another header
    'Content' => $x{image_bytes}                # Binary image data
  ]);
  $response= $self->{ua}->post(
    $self->{uri} . $x{id_type} . '/' . $x{id},
    Content_Type => 'form-data',
    Content => +[@content]
  );

  # If upload went well
  return $self->assemble_result($response->header('Location'))
    if $response->is_success || $response->is_redirect;

  # Otherwise
  return $self->assemble_error(
    "Failed to upload image.",
    $response
  );
}

sub delete_image {
  my ($self, $id_type, $id)= @_;
  my $request= HTTP::Request->new(
    DELETE => $self->{uri} . $id_type . '/' . $id
  );
  my $response= $self->{ua}->request($request);

  # Deletion went well
  return $self->assemble_result($response->content) if $response->is_success;

  # Deletion went south
  return $self->assemble_error(
    "Failed to delete image.",
    $response
  );
}

sub assemble_result {
  my ($self, $result)= @_;
  return +{
    result => $result,
    error => 0
  };
}

sub assemble_error {
  my ($self, $error, $response)= @_;
  my $title;
  $response->content =~ /<title>([^<]+)<\/title>/;
  $title= $1 || '';
  return +{
    result => '',
    error => $error,
    response => $response,
    full_error => join(
      "\n",
      $error,
      $response->status_line,
      $self->strip_html($response->content)
    ) . "\n",
    log_error => $error . ' ' . $title
  };
}

sub strip_html {
  my ($self, $data)= @_;
  $data=~ s/.+<\/head>//s;
  $data=~ s/<[^>]+>//sg;
  $data=~ s/\n\n+/\n\n/sg;
  $data=~ s/^\s+|\s+$//sg;
  $data= "\n" . $data . "\n";
  return $data;
}

That class can be used as follows for a deletion request:

sub delete_image {
  my ($id_type, $id)= @_;
  my $b= DcBisc->new("http://imageweb.bowker.com/rest/images");

  # Have the Bisc module send the deletion request
  my $status= $self->b->delete_image($id_type, $id);

  # Deletion went badly
  return $status->{full_error} if $status->{error};

  # Deletion went well
  return "Image successfully deleted.";
}

or as follows to insert a new image:

sub upload_image {
  my %x= (
    id_type => 'ean',
    id => '9780130880055',
    action => 'c',
    filename => 'x.gif',
    image_bytes => load_image('9780130880055.gif'),
    mime => 'image/gif',
    tags => +[qw/bookcover bip/],
    notes => '9780130880055.gif'
  );

  # Have the Bisc module upload the image
  $status= $self->b->upload_image_bytes(%x);

  # Upload went sour
  return $status->{full_error} if $status->{error};

  # Upload went well
  return (
    "Image successfully uploaded. The image can be accessed at:\n",
    $status->{result}
  );
}

Example Image URIs

The following images are all the same image, requested in various sizes and formats.


/rest/images/ean/9789810249038
The default.


/rest/images/ean/9789810249038?format=jpeg&width=200
In JPEG format and 200 pixels wide.


/rest/images/ean/9789810249038?text=Hello+World&size=300x300
In PNG format, made to fit as best as possible within a 300x300 box, with a text overlay


/rest/images/ean/981-02-4903-9
Requested using the old ISBN-10 format.

Revision: r1.8 - 03 Sep 2013 - 17:37:36