Repository: google/go-cloud
Pull request #507 (2018-10-09 19:14:17)

blob: add support for escape hatches via As
I still need to update the documentation with an overview, but I'd rather do that after getting through the review.

Feedback on naming is welcome. I ended up using func types in a couple of places instead of defining a new interface because it felt simpler.

vangent: Issue #470.
vangent: > remove references to the term "escape hatch" in the doc comments

Done.

Patch:
diff --git a/blob/blob.go b/blob/blob.go
index fc4a7e37..3bacf3a2 100644
--- a/blob/blob.go
+++ b/blob/blob.go
@@ -61,6 +61,12 @@ func (r *Reader) Size() int64 {
 	return r.r.Attributes().Size
 }
 
+// As converts i to provider-specific types.
+// See Bucket.As for more details.
+func (r *Reader) As(i interface{}) bool {
+	return r.r.As(i)
+}
+
 // Attributes contains attributes about a blob.
 type Attributes struct {
 	// ContentType is the MIME type of the blob object. It will not be empty.
@@ -76,6 +82,17 @@ type Attributes struct {
 	ModTime time.Time
 	// Size is the size of the object in bytes.
 	Size int64
+
+	asFunc func(interface{}) bool
+}
+
+// As converts i to provider-specific types.
+// See Bucket.As for more details.
+func (a *Attributes) As(i interface{}) bool {
+	if a.asFunc == nil {
+		return false
+	}
+	return a.asFunc(i)
 }
 
 // Writer implements io.WriteCloser to write to blob. It must be closed after
@@ -165,6 +182,25 @@ func NewBucket(b driver.Bucket) *Bucket {
 	return &Bucket{b: b}
 }
 
+// As converts i to provider-specific types. See provider documentation for
+// which type(s) are supported.
+//
+// Usage:
+// 1. Declare a variable of the provider-specific type you want to access.
+// 2. Pass a pointer to it to As.
+// 3. As will return true iff the type is supported, and copy the
+//    provider-specific type into your variable.
+//
+// Provider-specific types that are intended to be mutable will be exposed
+// as a pointer to the underlying type.
+//
+// See
+// https://github.com/google/go-cloud/blob/master/internal/docs/design.md#as
+// for more background.
+func (b *Bucket) As(i interface{}) bool {
+	return b.b.As(i)
+}
+
 // ReadAll is a shortcut for creating a Reader via NewReader and reading the entire blob.
 func (b *Bucket) ReadAll(ctx context.Context, key string) ([]byte, error) {
 	r, err := b.NewReader(ctx, key)
@@ -196,6 +232,7 @@ func (b *Bucket) Attributes(ctx context.Context, key string) (Attributes, error)
 		Metadata:    md,
 		ModTime:     a.ModTime,
 		Size:        a.Size,
+		asFunc:      a.AsFunc,
 	}, nil
 }
 
@@ -258,7 +295,8 @@ func (b *Bucket) NewWriter(ctx context.Context, key string, opt *WriterOptions)
 	var w driver.Writer
 	if opt != nil {
 		dopt = &driver.WriterOptions{
-			BufferSize: opt.BufferSize,
+			BufferSize:  opt.BufferSize,
+			BeforeWrite: opt.BeforeWrite,
 		}
 		if len(opt.Metadata) > 0 {
 			// Providers are inconsistent, but at least some treat keys
@@ -326,6 +364,14 @@ type WriterOptions struct {
 	// Keys may not be empty, and are lowercased before being written.
 	// Duplicate case-insensitive keys (e.g., "foo" and "FOO") are an error.
 	Metadata map[string]string
+
+	// BeforeWrite is a callback that will be called exactly once, before
+	// any data is written (unless NewWriter returns an error, in which case
+	// it will not be called at all). Note that this is not necessarily during
+	// or after the first Write call, as providers may buffer.
+	// asFunc converts its argument to provider-specific types.
+	// See Bucket.As for more details.
+	BeforeWrite func(asFunc func(interface{}) bool) error
 }
 
 // IsNotExist returns true iff err indicates that the referenced blob does not exist.
diff --git a/blob/driver/driver.go b/blob/driver/driver.go
index fbf7f3a4..8065f6d6 100644
--- a/blob/driver/driver.go
+++ b/blob/driver/driver.go
@@ -43,8 +43,11 @@ type Reader interface {
 	io.ReadCloser
 
 	// Attributes returns a subset of attributes about the blob.
-	// Use Bucket.Attributes to get the full set.
 	Attributes() ReaderAttributes
+
+	// As allows providers to expose provider-specific types;
+	// see Bucket.As for more details.
+	As(interface{}) bool
 }
 
 // Writer writes an object to the blob.
@@ -61,11 +64,16 @@ type WriterOptions struct {
 	// Metadata holds key/value strings to be associated with the blob.
 	// Keys are guaranteed to be non-empty and lowercased.
 	Metadata map[string]string
+	// BeforeWrite is a callback that must be called exactly once before
+	// any data is written, unless NewTypedWriter returns an error, in
+	// which case it should not be called.
+	// asFunc allows providers to expose provider-specific types;
+	// see Bucket.As for more details.
+	BeforeWrite func(asFunc func(interface{}) bool) error
 }
 
 // ReaderAttributes contains a subset of attributes about a blob that are
-// accessible from Reader. Use Bucket.Attributes to get the full set of
-// attributes.
+// accessible from Reader.
 type ReaderAttributes struct {
 	// ContentType is the MIME type of the blob object. It must not be empty.
 	ContentType string
@@ -89,11 +97,39 @@ type Attributes struct {
 	ModTime time.Time
 	// Size is the size of the object in bytes.
 	Size int64
+	// AsFunc allows providers to expose provider-specific types;
+	// see Bucket.As for more details.
+	// If not set, no provider-specific types are supported.
+	AsFunc func(interface{}) bool
 }
 
 // Bucket provides read, write and delete operations on objects within it on the
 // blob service.
 type Bucket interface {
+	// As allows providers to expose provider-specific types.
+	//
+	// i will be a pointer to the type the user wants filled in.
+	// As should either fill it in and return true, or return false.
+	//
+	// Mutable objects should be exposed as a pointer to the object;
+	// i will therefore be a **.
+	//
+	// A provider should document the type(s) it support in package
+	// comments, and add conformance tests verifying them.
+	//
+	// A sample implementation might look like this, for supporting foo.MyType:
+	//   mt, ok := i.(*foo.MyType)
+	//   if !ok {
+	//     return false
+	//   }
+	//   *i = foo.MyType{}  // or, more likely, the existing value
+	//   return true
+	//
+	// See
+	// https://github.com/google/go-cloud/blob/master/internal/docs/design.md#as
+	// for more background.
+	As(i interface{}) bool
+
 	// Attributes returns attributes for the blob. If the specified object does
 	// not exist, Attributes must return an error whose Kind method returns
 	// NotFound.
diff --git a/blob/fileblob/fileblob.go b/blob/fileblob/fileblob.go
index d7fcfeb2..562788f4 100644
--- a/blob/fileblob/fileblob.go
+++ b/blob/fileblob/fileblob.go
@@ -20,6 +20,8 @@
 // spaces, underscores, and dashes. Repeated slashes, a leading "./" or "../",
 // or the sequence "/./" is not permitted. This is to ensure that blob names map
 // cleanly onto files underneath a directory.
+//
+// It does not support any types for As.
 package fileblob
 
 import (
@@ -99,6 +101,9 @@ func (b *bucket) forKey(key string) (string, os.FileInfo, *xattrs, error) {
 	return path, info, &xa, nil
 }
 
+// As implements driver.As.
+func (b *bucket) As(i interface{}) bool { return false }
+
 // Attributes implements driver.Attributes.
 func (b *bucket) Attributes(ctx context.Context, key string) (driver.Attributes, error) {
 	_, info, xa, err := b.forKey(key)
@@ -167,6 +172,8 @@ func (r reader) Attributes() driver.ReaderAttributes {
 	return r.attrs
 }
 
+func (r reader) As(i interface{}) bool { return false }
+
 // NewTypedWriter implements driver.NewTypedWriter.
 func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType string, opt *driver.WriterOptions) (driver.Writer, error) {
 	relpath, err := resolvePath(key)
@@ -184,6 +191,11 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str
 	if err != nil {
 		return nil, fmt.Errorf("open file blob %s: %v", key, err)
 	}
+	if opt != nil && opt.BeforeWrite != nil {
+		if err := opt.BeforeWrite(func(interface{}) bool { return false }); err != nil {
+			return nil, err
+		}
+	}
 	var metadata map[string]string
 	if opt != nil && len(opt.Metadata) > 0 {
 		metadata = opt.Metadata
diff --git a/blob/gcsblob/gcsblob.go b/blob/gcsblob/gcsblob.go
index 05a13c98..c43f6656 100644
--- a/blob/gcsblob/gcsblob.go
+++ b/blob/gcsblob/gcsblob.go
@@ -12,8 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package gcsblob provides an implementation of using blob API on GCS.
-// It is a wrapper around GCS client library.
+// Package gcsblob provides an implementation of blob that uses GCS.
+//
+// It exposes the following types for As:
+// Bucket: *storage.Client
+// Reader: storage.Reader
+// Attributes: storage.ObjectAttrs
+// Writer: *storage.Writer
 package gcsblob
 
 import (
@@ -53,10 +58,11 @@ type bucket struct {
 
 var emptyBody = ioutil.NopCloser(strings.NewReader(""))
 
-// reader reads a GCS object. It implements io.ReadCloser.
+// reader reads a GCS object. It implements driver.Reader.
 type reader struct {
 	body  io.ReadCloser
 	attrs driver.ReaderAttributes
+	raw   *storage.Reader
 }
 
 func (r *reader) Read(p []byte) (int, error) {
@@ -72,6 +78,25 @@ func (r *reader) Attributes() driver.ReaderAttributes {
 	return r.attrs
 }
 
+func (r *reader) As(i interface{}) bool {
+	p, ok := i.(*storage.Reader)
+	if !ok {
+		return false
+	}
+	*p = *r.raw
+	return true
+}
+
+// As implements driver.As.
+func (b *bucket) As(i interface{}) bool {
+	p, ok := i.(**storage.Client)
+	if !ok {
+		return false
+	}
+	*p = b.client
+	return true
+}
+
 // Attributes implements driver.Attributes.
 func (b *bucket) Attributes(ctx context.Context, key string) (driver.Attributes, error) {
 	bkt := b.client.Bucket(b.name)
@@ -88,6 +113,14 @@ func (b *bucket) Attributes(ctx context.Context, key string) (driver.Attributes,
 		Metadata:    attrs.Metadata,
 		ModTime:     attrs.Updated,
 		Size:        attrs.Size,
+		AsFunc: func(i interface{}) bool {
+			p, ok := i.(*storage.ObjectAttrs)
+			if !ok {
+				return false
+			}
+			*p = *attrs
+			return true
+		},
 	}, nil
 }
 
@@ -110,6 +143,7 @@ func (b *bucket) NewRangeReader(ctx context.Context, key string, offset, length
 			ModTime:     modTime,
 			Size:        r.Size(),
 		},
+		raw: r,
 	}, nil
 }
 
@@ -123,6 +157,19 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str
 		w.ChunkSize = bufferSize(opts.BufferSize)
 		w.Metadata = opts.Metadata
 	}
+	if opts != nil && opts.BeforeWrite != nil {
+		asFunc := func(i interface{}) bool {
+			p, ok := i.(**storage.Writer)
+			if !ok {
+				return false
+			}
+			*p = w
+			return true
+		}
+		if err := opts.BeforeWrite(asFunc); err != nil {
+			return nil, err
+		}
+	}
 	return w, nil
 }
 
diff --git a/blob/s3blob/s3blob.go b/blob/s3blob/s3blob.go
index 3b31fc65..0d1a3547 100644
--- a/blob/s3blob/s3blob.go
+++ b/blob/s3blob/s3blob.go
@@ -12,7 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Package s3blob provides an implementation of using blob API on S3.
+// Package s3blob provides an implementation of blob using S3.
+//
+// It exposes the following types for As:
+// Bucket: *s3.S3
+// Reader: s3.GetObjectOutput
+// Attributes: s3.HeadObjectOutput
+// Writer: *s3manager.UploadInput
 package s3blob
 
 import (
@@ -52,6 +58,7 @@ var emptyBody = ioutil.NopCloser(strings.NewReader(""))
 type reader struct {
 	body  io.ReadCloser
 	attrs driver.ReaderAttributes
+	raw   *s3.GetObjectOutput
 }
 
 func (r *reader) Read(p []byte) (int, error) {
@@ -63,6 +70,15 @@ func (r *reader) Close() error {
 	return r.body.Close()
 }
 
+func (r *reader) As(i interface{}) bool {
+	p, ok := i.(*s3.GetObjectOutput)
+	if !ok {
+		return false
+	}
+	*p = *r.raw
+	return true
+}
+
 func (r *reader) Attributes() driver.ReaderAttributes {
 	return r.attrs
 }
@@ -71,13 +87,10 @@ func (r *reader) Attributes() driver.ReaderAttributes {
 type writer struct {
 	w *io.PipeWriter
 
-	bucket      string
-	key         string
-	ctx         context.Context
-	uploader    *s3manager.Uploader
-	contentType string
-	metadata    map[string]*string
-	donec       chan struct{} // closed when done writing
+	ctx      context.Context
+	uploader *s3manager.Uploader
+	req      *s3manager.UploadInput
+	donec    chan struct{} // closed when done writing
 	// The following fields will be written before donec closes:
 	err error
 }
@@ -104,13 +117,8 @@ func (w *writer) open() error {
 	go func() {
 		defer close(w.donec)
 
-		_, err := w.uploader.UploadWithContext(w.ctx, &s3manager.UploadInput{
-			Bucket:      aws.String(w.bucket),
-			ContentType: aws.String(w.contentType),
-			Key:         aws.String(w.key),
-			Body:        pr,
-			Metadata:    w.metadata,
-		})
+		w.req.Body = pr
+		_, err := w.uploader.UploadWithContext(w.ctx, w.req)
 		if err != nil {
 			w.err = err
 			pr.CloseWithError(err)
@@ -140,13 +148,8 @@ func (w *writer) touch() {
 		return
 	}
 	defer close(w.donec)
-	_, w.err = w.uploader.UploadWithContext(w.ctx, &s3manager.UploadInput{
-		Bucket:      aws.String(w.bucket),
-		ContentType: aws.String(w.contentType),
-		Key:         aws.String(w.key),
-		Body:        emptyBody,
-		Metadata:    w.metadata,
-	})
+	w.req.Body = emptyBody
+	_, w.err = w.uploader.UploadWithContext(w.ctx, w.req)
 }
 
 // bucket represents an S3 bucket and handles read, write and delete operations.
@@ -156,6 +159,16 @@ type bucket struct {
 	client *s3.S3
 }
 
+// As implements driver.As.
+func (b *bucket) As(i interface{}) bool {
+	p, ok := i.(**s3.S3)
+	if !ok {
+		return false
+	}
+	*p = b.client
+	return true
+}
+
 // Attributes implements driver.Attributes.
 func (b *bucket) Attributes(ctx context.Context, key string) (driver.Attributes, error) {
 	in := &s3.HeadObjectInput{
@@ -183,6 +196,14 @@ func (b *bucket) Attributes(ctx context.Context, key string) (driver.Attributes,
 		Metadata:    md,
 		ModTime:     aws.TimeValue(resp.LastModified),
 		Size:        aws.Int64Value(resp.ContentLength),
+		AsFunc: func(i interface{}) bool {
+			p, ok := i.(*s3.HeadObjectOutput)
+			if !ok {
+				return false
+			}
+			*p = *resp
+			return true
+		},
 	}, nil
 }
 
@@ -211,6 +232,7 @@ func (b *bucket) NewRangeReader(ctx context.Context, key string, offset, length
 			ModTime:     aws.TimeValue(resp.LastModified),
 			Size:        getSize(resp),
 		},
+		raw: resp,
 	}, nil
 }
 
@@ -245,16 +267,31 @@ func (b *bucket) NewTypedWriter(ctx context.Context, key string, contentType str
 			metadata[k] = aws.String(v)
 		}
 	}
-	w := &writer{
-		bucket:      b.name,
-		ctx:         ctx,
-		key:         key,
-		uploader:    uploader,
-		contentType: contentType,
-		metadata:    metadata,
-		donec:       make(chan struct{}),
+	req := &s3manager.UploadInput{
+		Bucket:      aws.String(b.name),
+		ContentType: aws.String(contentType),
+		Key:         aws.String(key),
+		Metadata:    metadata,
 	}
-	return w, nil
+	if opts != nil && opts.BeforeWrite != nil {
+		asFunc := func(i interface{}) bool {
+			p, ok := i.(**s3manager.UploadInput)
+			if !ok {
+				return false
+			}
+			*p = req
+			return true
+		}
+		if err := opts.BeforeWrite(asFunc); err != nil {
+			return nil, err
+		}
+	}
+	return &writer{
+		ctx:      ctx,
+		uploader: uploader,
+		req:      req,
+		donec:    make(chan struct{}),
+	}, nil
 }
 
 // Delete implements driver.Delete.
diff --git a/internal/docs/design.md b/internal/docs/design.md
index 1047d461..5f193638 100644
--- a/internal/docs/design.md
+++ b/internal/docs/design.md
@@ -141,7 +141,7 @@ canonical example is `gcpkms` and `awskms`.
 [cascading failure]:
 https://landing.google.com/sre/book/chapters/addressing-cascading-failures.html
 
-## Escape Hatches
+## As
 
 It is not feasible or desirable for APIs like [`blob.Bucket`] to encompass the
 full functionality of every provider. Rather, we intend to provide a subset of
@@ -155,80 +155,49 @@ wants to access provider-specific functionality, which might consist of:
 1.  **Options**. Different providers may support different options for
     functionality.
 
-**Escape hatches** provide the user a way to escape the Go Cloud abstraction to
-access provider-specific functionality. They might be used as an interim
+**As** functions in the APIs provide the user a way to escape the Go Cloud
+abstraction to access provider-specific types. They might be used as an interim
 solution until a feature request to Go Cloud is implemented. Or, Go Cloud may
-choose not to support specific features, and the escape hatch will be permanent.
+choose not to support specific features, and the use of `As` will be permanent.
 As an example, both S3 and GCS blobs have the concept of ACLs, but it might be
 difficult to represent them in a generic way (although, we have not tried).
 
-Using an escape hatch implies that the resulting code is no longer portable; the
-escape hatched code will need to be ported in order to switch providers.
-Therefore, they should be avoided if possible.
+Using `As`implies that the resulting code is no longer portable; the
+provider-specific code will need to be ported in order to switch providers.
+Therefore, it should be avoided if possible.
 
-### Ways To Escape Hatch
-
-Users can always access the provider service directly, by constructing the
-top-level handle and making API calls, bypassing Go Cloud.
-
-*   For top-level operations, this may be fine, although it might require a
-    bunch of plumbing code to pass the provider service handle to where it is
-    needed.
-*   For data objects, it implies dropping Go Cloud entirely; for example,
-    instead of using `blob.Reader` to read a blob, the user would have to use
-    the provider-specific method for reading.
-
-This approach exists today. Due to its shortcomings, we are designing a second
-approach. The proposal is to extend Go Cloud APIs, at the top-level (e.g.
-`blob.Bucket`) and in data objects, to expose provider-specific handles. For
-example:
+### Example
 
 ```
 // The existing blob.Reader exposes some blob attributes, but not everything
 // that every provider exposes.
-type Reader struct {
-    ...
-    Size        int64
-    ContentType string
-    ...
-
-    // New field!
-    Sys interface{}
-}
-```
+type Reader struct {...}
 
-The name `Sys` is modeled after
-[examples](https://golang.org/pkg/os/#ProcessState.Sys) in the `os` package.
-Using the `Sys` escape hatch to read an S3-specific attribute would look
-something like this:
+// As converts i to provider-specific types.
+func (r *Reader) As func(i interface{}) bool {...}
 
-```
-if r, err := bucket.NewReader(ctx, "foo.txt"); err == nil {
-  acls := r.Sys.(*s3.GetObjectOutput).ACLs
-  ...
+// User code would look like:
+r, _ := bucket.NewReader(ctx, "foo.txt")
+var s3type s3.GetObjectOutput
+if r.As(&s3type) {
+  ... use s3type...
 }
 ```
 
-Each provider implementation would document what type it returns for each of the
-escape hatch functions. `nil` is a valid return value for providers that don't
-support the escape hatch.
+Each provider implementation documents what type(s) it supports for each of the
+`As` functions.
 
-We are also considering an alternative to `Sys` that would look something like
-this:
+### Other Ways To Access Provider-Specific Features
 
-```
-var s3obj s3.GetObjectOutput
-if r.As(&s3obj) {
-  acls := s3obj.ACLs
-  ...
-}
-```
-
-This alternative allows providers to map to multiple types.
+Users can always access the provider service directly, by constructing the
+top-level handle and making API calls, bypassing Go Cloud.
 
-Design discussions regarding escape hatches are ongoing; we welcome input either
-on the [tracking issue](https://github.com/google/go-cloud/issues/470) or on the
-[mailing list](https://groups.google.com/forum/#!forum/go-cloud).
+*   For top-level operations, this may be fine, although it might require a
+    bunch of plumbing code to pass the provider service handle to where it is
+    needed.
+*   For data objects, it implies dropping Go Cloud entirely; for example,
+    instead of using `blob.Reader` to read a blob, the user would have to use
+    the provider-specific method for reading.
 
 ## Enforcing Portability
 


Test patch:
diff --git a/blob/drivertest/drivertest.go b/blob/drivertest/drivertest.go
index 1ebdde49..ed98e98e 100644
--- a/blob/drivertest/drivertest.go
+++ b/blob/drivertest/drivertest.go
@@ -19,6 +19,7 @@ package drivertest
 import (
 	"bytes"
 	"context"
+	"errors"
 	"io"
 	"io/ioutil"
 	"path/filepath"
@@ -43,11 +44,70 @@ type Harness interface {
 // It is called exactly once per test; Harness.Close() will be called when the test is complete.
 type HarnessMaker func(ctx context.Context, t *testing.T) (Harness, error)
 
+// AsTest represents a test of As functionality.
+// The conformance test:
+// 1. Calls BucketCheck.
+// 2. Creates a blob using BeforeWrite as a WriterOption.
+// 3. Fetches the blob's attributes and calls AttributeCheck.
+// 4. Creates a Reader for the blob, and calls ReaderCheck.
+// For example, an AsTest might set a provider-specific field to a custom
+// value in BeforeWrite, and then verify the custom value was returned in
+// AttributesCheck and/or ReaderCheck.
+type AsTest interface {
+	// Name should return a descriptive name for the test.
+	Name() string
+	// BucketCheck will be called to allow verification of Bucket.As.
+	BucketCheck(b *blob.Bucket) error
+	// BeforeWrite will be passed directly to WriterOptions as part of creating
+	// a test blob.
+	BeforeWrite(as func(interface{}) bool) error
+	// AttributesCheck will be called after fetching the test blob's attributes.
+	// It should call attrs.As and verify the results.
+	AttributesCheck(attrs *blob.Attributes) error
+	// ReaderCheck will be called after creating a blob.Reader.
+	// It should call r.As and verify the results.
+	ReaderCheck(r *blob.Reader) error
+}
+
+type verifyAsFailsOnNil struct{}
+
+func (verifyAsFailsOnNil) Name() string {
+	return "verify As returns false when passed nil"
+}
+
+func (verifyAsFailsOnNil) BucketCheck(b *blob.Bucket) error {
+	if b.As(nil) {
+		return errors.New("want Bucket.As to return false when passed nil")
+	}
+	return nil
+}
+
+func (verifyAsFailsOnNil) BeforeWrite(as func(interface{}) bool) error {
+	if as(nil) {
+		return errors.New("want Writer.As to return false when passed nil")
+	}
+	return nil
+}
+
+func (verifyAsFailsOnNil) AttributesCheck(attrs *blob.Attributes) error {
+	if attrs.As(nil) {
+		return errors.New("want Attributes.As to return false when passed nil")
+	}
+	return nil
+}
+
+func (verifyAsFailsOnNil) ReaderCheck(r *blob.Reader) error {
+	if r.As(nil) {
+		return errors.New("want Reader.As to return false when passed nil")
+	}
+	return nil
+}
+
 // RunConformanceTests runs conformance tests for provider implementations
 // of blob.
 // pathToTestdata is a (possibly relative) path to a directory containing
 // blob/testdata/* (e.g., test-small.txt).
-func RunConformanceTests(t *testing.T, newHarness HarnessMaker, pathToTestdata string) {
+func RunConformanceTests(t *testing.T, newHarness HarnessMaker, pathToTestdata string, asTests []AsTest) {
 	t.Run("TestRead", func(t *testing.T) {
 		testRead(t, newHarness)
 	})
@@ -66,6 +126,17 @@ func RunConformanceTests(t *testing.T, newHarness HarnessMaker, pathToTestdata s
 	t.Run("TestDelete", func(t *testing.T) {
 		testDelete(t, newHarness)
 	})
+	asTests = append(asTests, verifyAsFailsOnNil{})
+	t.Run("TestAs", func(t *testing.T) {
+		for _, st := range asTests {
+			if st.Name() == "" {
+				t.Fatalf("AsTest.Name is required")
+			}
+			t.Run(st.Name(), func(t *testing.T) {
+				testAs(t, newHarness, st)
+			})
+		}
+	})
 }
 
 // testRead tests the functionality of NewReader, NewRangeReader, and Reader.
@@ -636,3 +707,50 @@ func testDelete(t *testing.T, newHarness HarnessMaker) {
 		}
 	})
 }
+
+// testAs tests the various As functions, using AsTest.
+func testAs(t *testing.T, newHarness HarnessMaker, st AsTest) {
+	const key = "as-test"
+	var content = []byte("hello world")
+	ctx := context.Background()
+
+	h, err := newHarness(ctx, t)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+
+	b, err := h.MakeBucket(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Verify Bucket.As.
+	if err := st.BucketCheck(b); err != nil {
+		t.Error(err)
+	}
+
+	// Create a blob, using the provided callback.
+	if err := b.WriteAll(ctx, key, content, &blob.WriterOptions{BeforeWrite: st.BeforeWrite}); err != nil {
+		t.Error(err)
+	}
+	defer func() { _ = b.Delete(ctx, key) }()
+
+	// Verify Attributes.As.
+	attrs, err := b.Attributes(ctx, key)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := st.AttributesCheck(&attrs); err != nil {
+		t.Error(err)
+	}
+
+	// Verify Reader.As.
+	r, err := b.NewReader(ctx, key)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := st.ReaderCheck(r); err != nil {
+		t.Error(err)
+	}
+}
diff --git a/blob/example_test.go b/blob/example_test.go
index 7f2b4abc..d0e385b9 100644
--- a/blob/example_test.go
+++ b/blob/example_test.go
@@ -166,6 +166,53 @@ func ExampleBucket_ReadAll() {
 	// Go Cloud
 }
 
+func ExampleBucket_As() {
+	// Connect to a bucket when your program starts up.
+	// This example uses the file-based implementation.
+	dir, cleanup := newTempDir()
+	defer cleanup()
+
+	// Create the file-based bucket.
+	bucket, err := fileblob.NewBucket(dir)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// This example uses As to try to fill in a string variable. As will return
+	// false because fileblob doesn't support any types for Bucket.As.
+	// See the package documentation for your provider (e.g., gcsblob or s3blob)
+	// to see what type(s) it supports.
+	var providerSpecific string
+	if bucket.As(&providerSpecific) {
+		fmt.Println("fileblob supports the `string` type for Bucket.As")
+		// Use providerSpecific.
+	} else {
+		fmt.Println("fileblob does not support the `string` type for Bucket.As")
+	}
+
+	// This example sets WriterOptions.BeforeWrite to be called before the
+	// provider starts writing. In the callback, it uses asFunc to try to fill in
+	// a *string. Again, asFunc will return false because fileblob doesn't support
+	// any types for Writer.
+	fn := func(asFunc func(i interface{}) bool) error {
+		var mutableProviderSpecific *string
+		if asFunc(&mutableProviderSpecific) {
+			fmt.Println("fileblob supports the `*string` type for WriterOptions.BeforeWrite")
+			// Use mutableProviderSpecific.
+		} else {
+			fmt.Println("fileblob does not support the `*string` type for WriterOptions.BeforeWrite")
+		}
+		return nil
+	}
+	ctx := context.Background()
+	if err := bucket.WriteAll(ctx, "foo.txt", []byte("Go Cloud"), &blob.WriterOptions{BeforeWrite: fn}); err != nil {
+		log.Fatal(err)
+	}
+	// Output:
+	// fileblob does not support the `string` type for Bucket.As
+	// fileblob does not support the `*string` type for WriterOptions.BeforeWrite
+}
+
 func newTempDir() (string, func()) {
 	dir, err := ioutil.TempDir("", "go-cloud-blob-example")
 	if err != nil {
diff --git a/blob/fileblob/fileblob_test.go b/blob/fileblob/fileblob_test.go
index d49f7ea9..287ba318 100644
--- a/blob/fileblob/fileblob_test.go
+++ b/blob/fileblob/fileblob_test.go
@@ -51,7 +51,7 @@ func (h *harness) Close() {
 }
 
 func TestConformance(t *testing.T) {
-	drivertest.RunConformanceTests(t, newHarness, "../testdata")
+	drivertest.RunConformanceTests(t, newHarness, "../testdata", nil)
 }
 
 // File-specific unit tests.
diff --git a/blob/gcsblob/gcsblob_test.go b/blob/gcsblob/gcsblob_test.go
index d42533fe..52a887da 100644
--- a/blob/gcsblob/gcsblob_test.go
+++ b/blob/gcsblob/gcsblob_test.go
@@ -16,8 +16,11 @@ package gcsblob
 
 import (
 	"context"
+	"errors"
+	"fmt"
 	"testing"
 
+	"cloud.google.com/go/storage"
 	"github.com/google/go-cloud/blob"
 	"github.com/google/go-cloud/blob/drivertest"
 	"github.com/google/go-cloud/gcp"
@@ -53,7 +56,54 @@ func (h *harness) Close() {
 }
 
 func TestConformance(t *testing.T) {
-	drivertest.RunConformanceTests(t, newHarness, "../testdata")
+	drivertest.RunConformanceTests(t, newHarness, "../testdata", []drivertest.AsTest{verifyContentLanguage{}})
+}
+
+const language = "nl"
+
+// verifyContentLanguage uses As to access the underlying GCS types and
+// read/write the ContentLanguage field.
+type verifyContentLanguage struct{}
+
+func (verifyContentLanguage) Name() string {
+	return "verify ContentLanguage can be written and read through As"
+}
+
+func (verifyContentLanguage) BucketCheck(b *blob.Bucket) error {
+	var client *storage.Client
+	if !b.As(&client) {
+		return errors.New("Bucket.As failed")
+	}
+	return nil
+}
+
+func (verifyContentLanguage) BeforeWrite(as func(interface{}) bool) error {
+	var sw *storage.Writer
+	if !as(&sw) {
+		return errors.New("Writer.As failed")
+	}
+	sw.ContentLanguage = language
+	return nil
+}
+
+func (verifyContentLanguage) AttributesCheck(attrs *blob.Attributes) error {
+	var oa storage.ObjectAttrs
+	if !attrs.As(&oa) {
+		return errors.New("Attributes.As returned false")
+	}
+	if got := oa.ContentLanguage; got != language {
+		return fmt.Errorf("got %q want %q", got, language)
+	}
+	return nil
+}
+
+func (verifyContentLanguage) ReaderCheck(r *blob.Reader) error {
+	var rr storage.Reader
+	if !r.As(&rr) {
+		return errors.New("Reader.As returned false")
+	}
+	// GCS doesn't return Content-Language via storage.Reader.
+	return nil
 }
 
 // GCS-specific unit tests.
diff --git a/blob/gcsblob/testdata/TestConformance/TestAs/verify_As_returns_false.yaml b/blob/gcsblob/testdata/TestConformance/TestAs/verify_As_returns_false.yaml
new file mode 100644
index 00000000..b9c4bfd3
--- /dev/null
+++ b/blob/gcsblob/testdata/TestConformance/TestAs/verify_As_returns_false.yaml
@@ -0,0 +1,464 @@
+---
+version: 1
+interactions:
+- request:
+    body: "--61bc657a69c858260899ebfb88cd7a2343dbd03689e05e5438a6a8cf782b\r\nContent-Type:
+      application/json\r\n\r\n{\"bucket\":\"go-cloud-blob-test-bucket\",\"contentType\":\"text/plain;
+      charset=utf-8\",\"name\":\"as-test\"}\n\r\n--61bc657a69c858260899ebfb88cd7a2343dbd03689e05e5438a6a8cf782b\r\nContent-Type:
+      text/plain; charset=utf-8\r\n\r\nhello world\r\n--61bc657a69c858260899ebfb88cd7a2343dbd03689e05e5438a6a8cf782b--\r\n"
+    form: {}
+    headers:
+      Content-Type:
+      - multipart/related; boundary=61bc657a69c858260899ebfb88cd7a2343dbd03689e05e5438a6a8cf782b
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/upload/storage/v1/b/go-cloud-blob-test-bucket/o?alt=json&projection=full&uploadType=multipart
+    method: POST
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/as-test/1539109475297999",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test",
+       "name": "as-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1539109475297999",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-09T18:24:35.297Z",
+       "updated": "2018-10-09T18:24:35.297Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-09T18:24:35.297Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?generation=1539109475297999&alt=media",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CM/Vrsz9+d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CM/Vrsz9+d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CM/Vrsz9+d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CM/Vrsz9+d0CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CM/Vrsz9+d0CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "2978"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Tue, 09 Oct 2018 18:24:35 GMT
+      Etag:
+      - CM/Vrsz9+d0CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539109775000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - hilaxcag4:4038,/bns/hg/borg/hg/bns/blobstore2/bitpusher/57.scotty,acsfog12-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=Y_K8W-KpBOKUxgPS5qCYBA
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - acsfog12-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/57.scotty,acsfog12-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/57:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_single_post_uploads
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2Uq5hxRSqg35DkRfFvn3ufCXDkRc0nimnXEvm3tHL4cmgH5x4VmVdoDXm9PGvnDC0Ny6ZIlpxcjK9S6tOOlnyCkqODXtUQ
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?alt=json&projection=full
+    method: GET
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/as-test/1539109475297999",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test",
+       "name": "as-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1539109475297999",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-09T18:24:35.297Z",
+       "updated": "2018-10-09T18:24:35.297Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-09T18:24:35.297Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?generation=1539109475297999&alt=media",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CM/Vrsz9+d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CM/Vrsz9+d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CM/Vrsz9+d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109475297999/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109475297999",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CM/Vrsz9+d0CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CM/Vrsz9+d0CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "2978"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Tue, 09 Oct 2018 18:24:35 GMT
+      Etag:
+      - CM/Vrsz9+d0CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539109775000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/devstorage.read_write
+        https://www.googleapis.com/auth/devstorage.read_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - hilaxcbf10:4020,/bns/hg/borg/hg/bns/blobstore2/bitpusher/33.scotty,acsfog12-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=Y_K8W6-jE6uWxgOfmZHgDQ
+      X-Google-Gfe-Request-Trace:
+      - acsfog12-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/33.scotty,acsfog12-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/33:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6gAEKEmNsb3VkLXN0b3JhZ2Utcm9zeRIIYmlnc3RvcmUYisjPuJ4WIkg3NjQwODYwNTE4NTAtNnFyNHA2Z3BpNmhuNTA2cHQ4ZWp1cTgzZGkzNDFodXIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wkJYCMOArMJGWAjDhKzDiK0oPOg0xL1IyYlZDRmpHNUh-
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UpCy2OhvmPwB5rcc08fl78dhU1rcJUammg9KHOTc9uQgJjMehk58SNtFQdIxRxt7Y4tjwg9SBK95eK3hDONjgTjO6VDQw
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers: {}
+    url: https://storage.googleapis.com/go-cloud-blob-test-bucket/as-test
+    method: GET
+  response:
+    body: hello world
+    headers:
+      Accept-Ranges:
+      - bytes
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - private, max-age=0
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:24:35 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Expires:
+      - Tue, 09 Oct 2018 18:24:35 GMT
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:24:35 GMT
+      Server:
+      - UploadServer
+      X-Goog-Generation:
+      - "1539109475297999"
+      X-Goog-Hash:
+      - crc32c=yZRlqg==
+      - md5=XrY7u+Ae7tCTyyK7j1rNww==
+      X-Goog-Metageneration:
+      - "1"
+      X-Goog-Storage-Class:
+      - REGIONAL
+      X-Goog-Stored-Content-Encoding:
+      - identity
+      X-Goog-Stored-Content-Length:
+      - "11"
+      X-Google-Backends:
+      - /bns/hg/borg/hg/bns/cloud-storage/prod-cloud-storage-frontend.frontend/45,/bns/hg/borg/hg/bns/blobstore2/bitpusher/69.scotty,acsfoi19-v6:443
+      X-Google-Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=Y_K8W9-EIuSYxgPm57Z4
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - acsfoi19-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/69.scotty,acsfoi19-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-cloud-storage
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/69:caf3
+      X-Google-Service:
+      - bitpusher-cloud-storage
+      X-Google-Shellfish-Status:
+      - CA0gBEBJ
+      X-Google-Storage-Location:
+      - US-WEST2
+      X-Guploader-Customer:
+      - cloud-storage
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2Uo4bu6yr6pM_N1WliEiuF6QhnkaiglzxqHFqwC_LtbNCMayNCnw5HXmlo9BE20qyB7bgZC3qI_azD65v1QXdscT2we3hw
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?alt=json
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "0"
+      Content-Type:
+      - application/json
+      Date:
+      - Tue, 09 Oct 2018 18:24:35 GMT
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539109775000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - hglaxabk8:4388,/bns/hg/borg/hg/bns/blobstore2/bitpusher/19.scotty,acsfog12-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=Y_K8W_KaKeqYxgPv6LvADQ
+      X-Google-Gfe-Request-Trace:
+      - acsfog12-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/19.scotty,acsfog12-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/19:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UqDIXdheim5hMph_EYln_JG6S1wkL6upf2FlBM8JORM3ggJl_WX8Gny6SaWHgf3h3DekqCPf20spw1ia_aANW6tmeLvtQ
+    status: 204 No Content
+    code: 204
+    duration: ""
diff --git a/blob/gcsblob/testdata/TestConformance/TestAs/verify_As_returns_false_when_passed_nil.yaml b/blob/gcsblob/testdata/TestConformance/TestAs/verify_As_returns_false_when_passed_nil.yaml
new file mode 100644
index 00000000..41e28eaf
--- /dev/null
+++ b/blob/gcsblob/testdata/TestConformance/TestAs/verify_As_returns_false_when_passed_nil.yaml
@@ -0,0 +1,464 @@
+---
+version: 1
+interactions:
+- request:
+    body: "--4175bf7fb83775edce9e233380bd08e3a8d8ba32c9a41ec6d8411b6227b6\r\nContent-Type:
+      application/json\r\n\r\n{\"bucket\":\"go-cloud-blob-test-bucket\",\"contentType\":\"text/plain;
+      charset=utf-8\",\"name\":\"as-test\"}\n\r\n--4175bf7fb83775edce9e233380bd08e3a8d8ba32c9a41ec6d8411b6227b6\r\nContent-Type:
+      text/plain; charset=utf-8\r\n\r\nhello world\r\n--4175bf7fb83775edce9e233380bd08e3a8d8ba32c9a41ec6d8411b6227b6--\r\n"
+    form: {}
+    headers:
+      Content-Type:
+      - multipart/related; boundary=4175bf7fb83775edce9e233380bd08e3a8d8ba32c9a41ec6d8411b6227b6
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/upload/storage/v1/b/go-cloud-blob-test-bucket/o?alt=json&projection=full&uploadType=multipart
+    method: POST
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/as-test/1539109808507063",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test",
+       "name": "as-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1539109808507063",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-09T18:30:08.506Z",
+       "updated": "2018-10-09T18:30:08.506Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-09T18:30:08.506Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?generation=1539109808507063&alt=media",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CLeRoOv++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CLeRoOv++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CLeRoOv++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CLeRoOv++d0CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CLeRoOv++d0CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "2978"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Tue, 09 Oct 2018 18:30:08 GMT
+      Etag:
+      - CLeRoOv++d0CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539110108000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - lmkm22:4304,/bns/lt/borg/lt/bns/blobstore2/bitpusher/33.scotty,lqc14-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=sPO8W9rEDIr6zQWswpOYCg
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - lqc14-v6:443,/bns/lt/borg/lt/bns/blobstore2/bitpusher/33.scotty,lqc14-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/lt/borg/lt/bns/blobstore2/bitpusher/33:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_single_post_uploads
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UoMrDtvk6JTh0LM_uKJ9Yzl2Ltnb6NVIaqC5J-3CWqevsYeMAZt_Sy2YJRUPGBmwyBvTK_nTxET3dbnBnx2ojVqjzIijjYmFJsqhwSj1-TQ5bqgDws
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?alt=json&projection=full
+    method: GET
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/as-test/1539109808507063",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test",
+       "name": "as-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1539109808507063",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-09T18:30:08.506Z",
+       "updated": "2018-10-09T18:30:08.506Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-09T18:30:08.506Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?generation=1539109808507063&alt=media",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CLeRoOv++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CLeRoOv++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CLeRoOv++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109808507063/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109808507063",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CLeRoOv++d0CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CLeRoOv++d0CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "2978"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Tue, 09 Oct 2018 18:30:09 GMT
+      Etag:
+      - CLeRoOv++d0CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539110108000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/devstorage.read_write
+        https://www.googleapis.com/auth/devstorage.read_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - lmit16:4088,/bns/lq/borg/lq/bns/blobstore2/bitpusher/144.scotty,lqc14-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=sPO8W8_sMImCyAXjwLWICw
+      X-Google-Gfe-Request-Trace:
+      - lqc14-v6:443,/bns/lq/borg/lq/bns/blobstore2/bitpusher/144.scotty,lqc14-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/lq/borg/lq/bns/blobstore2/bitpusher/144:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6gAEKEmNsb3VkLXN0b3JhZ2Utcm9zeRIIYmlnc3RvcmUYisjPuJ4WIkg3NjQwODYwNTE4NTAtNnFyNHA2Z3BpNmhuNTA2cHQ4ZWp1cTgzZGkzNDFodXIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wkJYCMOArMJGWAjDhKzDiK0oPOg0xL1IyYlZDRmpHNUh-
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UpFFPMAgJcNdJe_GncSHhSozztXaatiB-7P6ct-sfBzQUJE2t9OJn4iXASYEnvK-hCVgKGfmU3dhNJ70_qKMUtj_3cCrA
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers: {}
+    url: https://storage.googleapis.com/go-cloud-blob-test-bucket/as-test
+    method: GET
+  response:
+    body: hello world
+    headers:
+      Accept-Ranges:
+      - bytes
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - private, max-age=0
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:30:09 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Expires:
+      - Tue, 09 Oct 2018 18:30:09 GMT
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:30:08 GMT
+      Server:
+      - UploadServer
+      X-Goog-Generation:
+      - "1539109808507063"
+      X-Goog-Hash:
+      - crc32c=yZRlqg==
+      - md5=XrY7u+Ae7tCTyyK7j1rNww==
+      X-Goog-Metageneration:
+      - "1"
+      X-Goog-Storage-Class:
+      - REGIONAL
+      X-Goog-Stored-Content-Encoding:
+      - identity
+      X-Goog-Stored-Content-Length:
+      - "11"
+      X-Google-Backends:
+      - /bns/hg/borg/hg/bns/cloud-storage/prod-cloud-storage-frontend.frontend/62,/bns/hg/borg/hg/bns/blobstore2/bitpusher/25.scotty,acsfou20-v6:443
+      X-Google-Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=sfO8W5bjDoybxgPp3L7oDA
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - acsfou20-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/25.scotty,acsfou20-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-cloud-storage
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/25:caf3
+      X-Google-Service:
+      - bitpusher-cloud-storage
+      X-Google-Shellfish-Status:
+      - CA0gBEBJ
+      X-Google-Storage-Location:
+      - US-WEST2
+      X-Guploader-Customer:
+      - cloud-storage
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UoGj-hmCApxjIGPpmZl6CD6HPn5hobi_oNpZOI9ZhQoPJ9RJL6NU9ZIIZD4c9aEOziBBYkGDEEmzVaAmtXsXMERvDfSAA
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?alt=json
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "0"
+      Content-Type:
+      - application/json
+      Date:
+      - Tue, 09 Oct 2018 18:30:09 GMT
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539110108000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - lmct11:4038,/bns/lq/borg/lq/bns/blobstore2/bitpusher/0.scotty,lqc14-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=sfO8W5r5GovCyQXW4rmQBQ
+      X-Google-Gfe-Request-Trace:
+      - lqc14-v6:443,/bns/lq/borg/lq/bns/blobstore2/bitpusher/0.scotty,lqc14-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/lq/borg/lq/bns/blobstore2/bitpusher/0:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UrTP4BWK6HxGeJNO2G3FMdua_s13mO46D30eDCo88pavqsXtLdCQQgEG-VMDMaI_HAZC-fzMS4w84QrpaBu1KBSGxT74w
+    status: 204 No Content
+    code: 204
+    duration: ""
diff --git a/blob/gcsblob/testdata/TestConformance/TestAs/verify_ContentLanguage_can_be_written_and_read_through_As.yaml b/blob/gcsblob/testdata/TestConformance/TestAs/verify_ContentLanguage_can_be_written_and_read_through_As.yaml
new file mode 100644
index 00000000..e5b479bc
--- /dev/null
+++ b/blob/gcsblob/testdata/TestConformance/TestAs/verify_ContentLanguage_can_be_written_and_read_through_As.yaml
@@ -0,0 +1,468 @@
+---
+version: 1
+interactions:
+- request:
+    body: "--9adc257081dcc17ebe39d8264043e66f442e2d0c683e1aa073fd5a74e8d0\r\nContent-Type:
+      application/json\r\n\r\n{\"bucket\":\"go-cloud-blob-test-bucket\",\"contentLanguage\":\"nl\",\"contentType\":\"text/plain;
+      charset=utf-8\",\"name\":\"as-test\"}\n\r\n--9adc257081dcc17ebe39d8264043e66f442e2d0c683e1aa073fd5a74e8d0\r\nContent-Type:
+      text/plain; charset=utf-8\r\n\r\nhello world\r\n--9adc257081dcc17ebe39d8264043e66f442e2d0c683e1aa073fd5a74e8d0--\r\n"
+    form: {}
+    headers:
+      Content-Type:
+      - multipart/related; boundary=9adc257081dcc17ebe39d8264043e66f442e2d0c683e1aa073fd5a74e8d0
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/upload/storage/v1/b/go-cloud-blob-test-bucket/o?alt=json&projection=full&uploadType=multipart
+    method: POST
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/as-test/1539109806711752",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test",
+       "name": "as-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1539109806711752",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-09T18:30:06.711Z",
+       "updated": "2018-10-09T18:30:06.711Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-09T18:30:06.711Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?generation=1539109806711752&alt=media",
+       "contentLanguage": "nl",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CMjHsur++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CMjHsur++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CMjHsur++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CMjHsur++d0CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CMjHsur++d0CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "3004"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Tue, 09 Oct 2018 18:30:06 GMT
+      Etag:
+      - CMjHsur++d0CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539110106000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - lmgj13:4327,/bns/lq/borg/lq/bns/blobstore2/bitpusher/70.scotty,lqc14-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=rvO8W6aoGNHGyAWIopewDg
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - lqc14-v6:443,/bns/lq/borg/lq/bns/blobstore2/bitpusher/70.scotty,lqc14-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/lq/borg/lq/bns/blobstore2/bitpusher/70:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_single_post_uploads
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2Up6TJu_Kh8azmYIG-B1NC4y32mIU6hyPb7R5rgvAiTdhcCGiGVB1Sn_qGnBWcmUtSn6LpOnpRsSHlVxTh9-vJDNP3ihqQ
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?alt=json&projection=full
+    method: GET
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/as-test/1539109806711752",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test",
+       "name": "as-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1539109806711752",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-09T18:30:06.711Z",
+       "updated": "2018-10-09T18:30:06.711Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-09T18:30:06.711Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?generation=1539109806711752&alt=media",
+       "contentLanguage": "nl",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CMjHsur++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CMjHsur++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CMjHsur++d0CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/as-test/1539109806711752/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "as-test",
+         "generation": "1539109806711752",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CMjHsur++d0CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CMjHsur++d0CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "3004"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Tue, 09 Oct 2018 18:30:07 GMT
+      Etag:
+      - CMjHsur++d0CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539110106000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/devstorage.read_write
+        https://www.googleapis.com/auth/devstorage.read_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - lmcc42:4030,/bns/lq/borg/lq/bns/blobstore2/bitpusher/3.scotty,lqc14-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=rvO8W4XvPMrlyAXjxpDoAw
+      X-Google-Gfe-Request-Trace:
+      - lqc14-v6:443,/bns/lq/borg/lq/bns/blobstore2/bitpusher/3.scotty,lqc14-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/lq/borg/lq/bns/blobstore2/bitpusher/3:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6gAEKEmNsb3VkLXN0b3JhZ2Utcm9zeRIIYmlnc3RvcmUYisjPuJ4WIkg3NjQwODYwNTE4NTAtNnFyNHA2Z3BpNmhuNTA2cHQ4ZWp1cTgzZGkzNDFodXIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wkJYCMOArMJGWAjDhKzDiK0oPOg0xL1IyYlZDRmpHNUh-
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UrdMgnAjpMk7QvmFv9aX3me2U_utAsZC1-4DqNBR3g2zCYrHfWog2b9N05fXES6BKNI-fGKk9PsfBl7KSiapSrmibI5tg
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers: {}
+    url: https://storage.googleapis.com/go-cloud-blob-test-bucket/as-test
+    method: GET
+  response:
+    body: hello world
+    headers:
+      Accept-Ranges:
+      - bytes
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - private, max-age=0
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:30:07 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Expires:
+      - Tue, 09 Oct 2018 18:30:07 GMT
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:30:06 GMT
+      Server:
+      - UploadServer
+      X-Goog-Generation:
+      - "1539109806711752"
+      X-Goog-Hash:
+      - crc32c=yZRlqg==
+      - md5=XrY7u+Ae7tCTyyK7j1rNww==
+      X-Goog-Metageneration:
+      - "1"
+      X-Goog-Storage-Class:
+      - REGIONAL
+      X-Goog-Stored-Content-Encoding:
+      - identity
+      X-Goog-Stored-Content-Length:
+      - "11"
+      X-Google-Backends:
+      - /bns/hi/borg/hi/bns/cloud-storage/prod-cloud-storage-frontend.frontend/7,/bns/hg/borg/hg/bns/blobstore2/bitpusher/66.scotty,acsfou20-v6:443
+      X-Google-Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=r_O8W8K7H4yWxgP8nLroAg
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - acsfou20-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/66.scotty,acsfou20-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-cloud-storage
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/66:caf3
+      X-Google-Service:
+      - bitpusher-cloud-storage
+      X-Google-Shellfish-Status:
+      - CA0gBEBJ
+      X-Google-Storage-Location:
+      - US-WEST2
+      X-Guploader-Customer:
+      - cloud-storage
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UrNRAOnbpKB2jtm8ZWe_LVP_YDc-iQO5NXfow5CtfRYUH3ZpIm0Hi30GkPMhxOA53KxdZMGKtEkBCmOUw7ns_QY1NOsRA
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/as-test?alt=json
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "0"
+      Content-Type:
+      - application/json
+      Date:
+      - Tue, 09 Oct 2018 18:30:07 GMT
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1539110106000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - lmph26:4012,/bns/lq/borg/lq/bns/blobstore2/bitpusher/47.scotty,lqc14-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=r_O8W9CLLMO4yAWA-Z-gBA
+      X-Google-Gfe-Request-Trace:
+      - lqc14-v6:443,/bns/lq/borg/lq/bns/blobstore2/bitpusher/47.scotty,lqc14-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/lq/borg/lq/bns/blobstore2/bitpusher/47:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UoanouN8HDAQn8pwfCpbOrmxDPO-w6OScszCVVxA5UpGPGfGh3ADsyOfUC6QVlvnknhx7MV8tDmyvV_vNcgJjzTVUA2RA
+    status: 204 No Content
+    code: 204
+    duration: ""
diff --git a/blob/gcsblob/testdata/TestConformance/TestSys/ContentLanguage.yaml b/blob/gcsblob/testdata/TestConformance/TestSys/ContentLanguage.yaml
new file mode 100644
index 00000000..8c0e89c9
--- /dev/null
+++ b/blob/gcsblob/testdata/TestConformance/TestSys/ContentLanguage.yaml
@@ -0,0 +1,389 @@
+---
+version: 1
+interactions:
+- request:
+    body: "--7ad432c8574b357bd0ab95c147487e2e77302506c7a44b008e3e929b28c4\r\nContent-Type:
+      application/json\r\n\r\n{\"bucket\":\"go-cloud-blob-test-bucket\",\"contentLanguage\":\"nl\",\"contentType\":\"text/plain;
+      charset=utf-8\",\"name\":\"sys-test\"}\n\r\n--7ad432c8574b357bd0ab95c147487e2e77302506c7a44b008e3e929b28c4\r\nContent-Type:
+      text/plain; charset=utf-8\r\n\r\nhello world\r\n--7ad432c8574b357bd0ab95c147487e2e77302506c7a44b008e3e929b28c4--\r\n"
+    form: {}
+    headers:
+      Content-Type:
+      - multipart/related; boundary=7ad432c8574b357bd0ab95c147487e2e77302506c7a44b008e3e929b28c4
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/upload/storage/v1/b/go-cloud-blob-test-bucket/o?alt=json&projection=full&uploadType=multipart
+    method: POST
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test",
+       "name": "sys-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1538758278204030",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-05T16:51:18.203Z",
+       "updated": "2018-10-05T16:51:18.203Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-05T16:51:18.203Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test?generation=1538758278204030&alt=media",
+       "contentLanguage": "nl",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CP6UxaTh790CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CP6UxaTh790CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CP6UxaTh790CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CP6UxaTh790CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CP6UxaTh790CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "3020"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Fri, 05 Oct 2018 16:51:18 GMT
+      Etag:
+      - CP6UxaTh790CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1538758577000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - hilaxcbc4:4278,/bns/hg/borg/hg/bns/blobstore2/bitpusher/19.scotty,acsfog4-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=hZa3W6HIOeqYxgPv6LvADQ
+      X-Google-Gfe-Cloud-Project-Number:
+      - "892942638129"
+      X-Google-Gfe-Request-Trace:
+      - acsfog4-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/19.scotty,acsfog4-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/19:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_single_post_uploads
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2Uon_JPEB5Q2xHQ8iEAdmXbF5VRY8ICYjJE4t4YLEMYKtU0G2sUH9WiN79n-6Y5GHrvkkkqfGhepr-EA2ysaaNe0geJYVw
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test?alt=json&projection=full
+    method: GET
+  response:
+    body: |
+      {
+       "kind": "storage#object",
+       "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030",
+       "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test",
+       "name": "sys-test",
+       "bucket": "go-cloud-blob-test-bucket",
+       "generation": "1538758278204030",
+       "metageneration": "1",
+       "contentType": "text/plain; charset=utf-8",
+       "timeCreated": "2018-10-05T16:51:18.203Z",
+       "updated": "2018-10-05T16:51:18.203Z",
+       "storageClass": "REGIONAL",
+       "timeStorageClassUpdated": "2018-10-05T16:51:18.203Z",
+       "size": "11",
+       "md5Hash": "XrY7u+Ae7tCTyyK7j1rNww==",
+       "mediaLink": "https://www.googleapis.com/download/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test?generation=1538758278204030&alt=media",
+       "contentLanguage": "nl",
+       "acl": [
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/project-owners-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/project-owners-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "project-owners-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "owners"
+         },
+         "etag": "CP6UxaTh790CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/project-editors-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/project-editors-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "project-editors-892942638129",
+         "role": "OWNER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "editors"
+         },
+         "etag": "CP6UxaTh790CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/project-viewers-892942638129",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/project-viewers-892942638129",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "project-viewers-892942638129",
+         "role": "READER",
+         "projectTeam": {
+          "projectNumber": "892942638129",
+          "team": "viewers"
+         },
+         "etag": "CP6UxaTh790CEAE="
+        },
+        {
+         "kind": "storage#objectAccessControl",
+         "id": "go-cloud-blob-test-bucket/sys-test/1538758278204030/user-rvangent@google.com",
+         "selfLink": "https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test/acl/user-rvangent@google.com",
+         "bucket": "go-cloud-blob-test-bucket",
+         "object": "sys-test",
+         "generation": "1538758278204030",
+         "entity": "user-rvangent@google.com",
+         "role": "OWNER",
+         "email": "rvangent@google.com",
+         "etag": "CP6UxaTh790CEAE="
+        }
+       ],
+       "owner": {
+        "entity": "user-rvangent@google.com"
+       },
+       "crc32c": "yZRlqg==",
+       "etag": "CP6UxaTh790CEAE="
+      }
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "3020"
+      Content-Type:
+      - application/json; charset=UTF-8
+      Date:
+      - Fri, 05 Oct 2018 16:51:18 GMT
+      Etag:
+      - CP6UxaTh790CEAE=
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1538758577000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/cloud-platform.read-only https://www.googleapis.com/auth/devstorage.read_write
+        https://www.googleapis.com/auth/devstorage.read_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - hglaxaaf8:4296,/bns/hg/borg/hg/bns/blobstore2/bitpusher/7.scotty,acsfog4-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=hpa3W4XSDcqYxgPOq6bwBg
+      X-Google-Gfe-Request-Trace:
+      - acsfog4-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/7.scotty,acsfog4-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/7:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6gAEKEmNsb3VkLXN0b3JhZ2Utcm9zeRIIYmlnc3RvcmUYisjPuJ4WIkg3NjQwODYwNTE4NTAtNnFyNHA2Z3BpNmhuNTA2cHQ4ZWp1cTgzZGkzNDFodXIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wkJYCMOArMJGWAjDhKzDiK0oPOg0xL1IyYlZDRmpHNUh-
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UrYn6CGw6J3Xe_XT_wF_QXXn9PJ1sHdNNxgljMN12raNH9Nuyy6u1wY7MzY7BA8qSjMfZ_eIsf1_S5Pw3hSetbVpbiQ2w
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - google-api-go-client/0.5
+      X-Goog-Api-Client:
+      - gl-go/1.11.0-rc2 gccl/20180226
+    url: https://www.googleapis.com/storage/v1/b/go-cloud-blob-test-bucket/o/sys-test?alt=json
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Alt-Svc:
+      - quic=":443"; ma=2592000; v="44,43,39,35"
+      Cache-Control:
+      - no-cache, no-store, max-age=0, must-revalidate
+      Content-Length:
+      - "0"
+      Content-Type:
+      - application/json
+      Date:
+      - Fri, 05 Oct 2018 16:51:18 GMT
+      Expires:
+      - Mon, 01 Jan 1990 00:00:00 GMT
+      Pragma:
+      - no-cache
+      Server:
+      - UploadServer
+      Vary:
+      - Origin
+      - X-Origin
+      X-Google-Apiary-Auth-Expires:
+      - "1538758577000"
+      X-Google-Apiary-Auth-Scopes:
+      - https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/devstorage.full_control
+        https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/devstorage.write_only
+      X-Google-Apiary-Auth-User:
+      - "61688748495"
+      X-Google-Backends:
+      - hglaxaad8:4147,/bns/hg/borg/hg/bns/blobstore2/bitpusher/13.scotty,acsfog4-v6:443
+      X-Google-Dos-Service-Trace:
+      - main:apps-upload-cloud-storage-unified
+      X-Google-Gfe-Backend-Request-Info:
+      - eid=hpa3W_-AGKWYxgO3v7rgDQ
+      X-Google-Gfe-Request-Trace:
+      - acsfog4-v6:443,/bns/hg/borg/hg/bns/blobstore2/bitpusher/13.scotty,acsfog4-v6:443
+      X-Google-Gfe-Response-Code-Details-Trace:
+      - response_code_set_by_backend
+      X-Google-Gfe-Service-Trace:
+      - bitpusher-gcs-apiary
+      X-Google-Netmon-Label:
+      - /bns/hg/borg/hg/bns/blobstore2/bitpusher/13:caf3
+      X-Google-Service:
+      - bitpusher-gcs-apiary
+      X-Google-Session-Info:
+      - CM-zvuflARoCGAY6fAoSY2xvdWQtc3RvcmFnZS1yb3N5EghiaWdzdG9yZRiKyM-4nhYiSDc2NDA4NjA1MTg1MC02cXI0cDZncGk2aG41MDZwdDhlanVxODNkaTM0MWh1ci5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTCQlgIw4Csw4Ssw4ytKDzoNMS9SMmJWQ0ZqRzVIfg
+      X-Google-Shellfish-Status:
+      - CA0gBEBG
+      X-Guploader-Customer:
+      - apiary_cloudstorage_metadata
+      X-Guploader-Request-Result:
+      - success
+      X-Guploader-Upload-Result:
+      - success
+      X-Guploader-Uploadid:
+      - AEnB2UpTjjNjL1vrAHF2NPPij66LPNZRpNc96ygjMdMF9zfTIRjYTcBrXrlulpW3y7ii1mDiCKMgnLHTGAnoPK-WA6AXysWsIw
+    status: 204 No Content
+    code: 204
+    duration: ""
diff --git a/blob/s3blob/s3blob_test.go b/blob/s3blob/s3blob_test.go
index 7c2ecf6b..3c638717 100644
--- a/blob/s3blob/s3blob_test.go
+++ b/blob/s3blob/s3blob_test.go
@@ -16,9 +16,14 @@ package s3blob
 
 import (
 	"context"
+	"errors"
+	"fmt"
 	"testing"
 
+	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/s3"
+	"github.com/aws/aws-sdk-go/service/s3/s3manager"
 	"github.com/google/go-cloud/blob"
 	"github.com/google/go-cloud/blob/drivertest"
 	"github.com/google/go-cloud/internal/testing/setup"
@@ -55,5 +60,54 @@ func (h *harness) Close() {
 }
 
 func TestConformance(t *testing.T) {
-	drivertest.RunConformanceTests(t, newHarness, "../testdata")
+	drivertest.RunConformanceTests(t, newHarness, "../testdata", []drivertest.AsTest{verifyContentLanguage{}})
+}
+
+const language = "nl"
+
+// verifyContentLanguage uses As to access the underlying GCS types and
+// read/write the ContentLanguage field.
+type verifyContentLanguage struct{}
+
+func (verifyContentLanguage) Name() string {
+	return "verify ContentLanguage can be written and read through As"
+}
+
+func (verifyContentLanguage) BucketCheck(b *blob.Bucket) error {
+	var client *s3.S3
+	if !b.As(&client) {
+		return errors.New("Bucket.As failed")
+	}
+	return nil
+}
+
+func (verifyContentLanguage) BeforeWrite(as func(interface{}) bool) error {
+	var req *s3manager.UploadInput
+	if !as(&req) {
+		return errors.New("WriterAs failed")
+	}
+	req.ContentLanguage = aws.String(language)
+	return nil
+}
+
+func (verifyContentLanguage) AttributesCheck(attrs *blob.Attributes) error {
+	var hoo s3.HeadObjectOutput
+	if !attrs.As(&hoo) {
+		return errors.New("Attributes.As returned false")
+	}
+	if got := *hoo.ContentLanguage; got != language {
+		return fmt.Errorf("got %q want %q", got, language)
+	}
+	return nil
+}
+
+func (verifyContentLanguage) ReaderCheck(r *blob.Reader) error {
+	var goo s3.GetObjectOutput
+	if !r.As(&goo) {
+		return errors.New("Reader.As returned false")
+	}
+	if got := *goo.ContentLanguage; got != language {
+		return fmt.Errorf("got %q want %q", got, language)
+	}
+	return nil
 }
diff --git a/blob/s3blob/testdata/TestConformance/TestAs/verify_As_returns_false_when_passed_nil.yaml b/blob/s3blob/testdata/TestConformance/TestAs/verify_As_returns_false_when_passed_nil.yaml
new file mode 100644
index 00000000..04b7f837
--- /dev/null
+++ b/blob/s3blob/testdata/TestConformance/TestAs/verify_As_returns_false_when_passed_nil.yaml
@@ -0,0 +1,173 @@
+---
+version: 1
+interactions:
+- request:
+    body: hello world
+    form: {}
+    headers:
+      Content-Length:
+      - "11"
+      Content-Md5:
+      - XrY7u+Ae7tCTyyK7j1rNww==
+      Content-Type:
+      - text/plain; charset=utf-8
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64) S3Manager
+      X-Amz-Content-Sha256:
+      - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: PUT
+  response:
+    body: ""
+    headers:
+      Content-Length:
+      - "0"
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - fro3Z6TnamrlO7I5EEKUnrFGaDgF6uQzwtI8NJbNC/7IBLXO0NdAi//N2wp4w0vo4dYIqIuKkDE=
+      X-Amz-Request-Id:
+      - 81BF9096433C66B9
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: HEAD
+  response:
+    body: ""
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - QZZtLWV3pL7zALJv5fOXP63bf1DBc5IWVKrOLS6ER/72KtsGwxLMGySqiHCLEwO85+6a3G/c5yc=
+      X-Amz-Request-Id:
+      - 0CF7F8CFB091AC31
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: GET
+  response:
+    body: hello world
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - XYvH+omEpq5ZvK0ELpJhmFCgCOQkGmRLVApzi/nl0z8vZnh+iYD1LmyL/wlo8tpFLHiKnwMy+n8=
+      X-Amz-Request-Id:
+      - C5761447860C6112
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: HEAD
+  response:
+    body: ""
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - a2Q4qX3n5xWe9eFPUsTjcobUmYoNzc0M1c2lbO355eDO33ac0Vkn9Ov1/l5HoQ+gv01ZRXp++aw=
+      X-Amz-Request-Id:
+      - 2FBF5DF8463977D5
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - VWFr8Dc3WkEbKFKL0Oq3NrXLfQ/UFv3/2o5uLUIoWV0Zlp5no1vbveHJ6p6JP2GzMXtHZ8XZXxk=
+      X-Amz-Request-Id:
+      - 41C164933F1E4E90
+    status: 204 No Content
+    code: 204
+    duration: ""
diff --git a/blob/s3blob/testdata/TestConformance/TestAs/verify_ContentLanguage_can_be_written_and_read_through_As.yaml b/blob/s3blob/testdata/TestConformance/TestAs/verify_ContentLanguage_can_be_written_and_read_through_As.yaml
new file mode 100644
index 00000000..d27a2731
--- /dev/null
+++ b/blob/s3blob/testdata/TestConformance/TestAs/verify_ContentLanguage_can_be_written_and_read_through_As.yaml
@@ -0,0 +1,181 @@
+---
+version: 1
+interactions:
+- request:
+    body: hello world
+    form: {}
+    headers:
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Md5:
+      - XrY7u+Ae7tCTyyK7j1rNww==
+      Content-Type:
+      - text/plain; charset=utf-8
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64) S3Manager
+      X-Amz-Content-Sha256:
+      - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
+      X-Amz-Date:
+      - 20181009T183501Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: PUT
+  response:
+    body: ""
+    headers:
+      Content-Length:
+      - "0"
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - /2S7dS6l0lywd960HWMMWC7TebOwp2+pZjZMzHxHPBmlaD/KX5y2guyYwjQAzooZ4Bx58vUn2E8=
+      X-Amz-Request-Id:
+      - B47F333DC59A36AE
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: HEAD
+  response:
+    body: ""
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - A8SYkVq2dK5t92ZY5yj+33WhGs2IJ4OluqmEBHejNQs5+7Ugl/LowB3MJhPHfYovrSJB6mzxd+Q=
+      X-Amz-Request-Id:
+      - 106E2ADC49CC46AE
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: GET
+  response:
+    body: hello world
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - B/sn04EJ5j+uLGlQ9tqVxLjX6E71Upzm5SB7a4BiaAHNtogysrMr6sVwh1maz6Kia9IqGSNwHIo=
+      X-Amz-Request-Id:
+      - 18DF3E3DF3449E47
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: HEAD
+  response:
+    body: ""
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - CQZ5NFthpDGlkDUQpt7pZDpzOv4D8SHpoktIVu/hE7WIjVpec/EumZfBUn2DA3lbCNGMugkdGWE=
+      X-Amz-Request-Id:
+      - 98743AFA9FC2AD02
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181009T183502Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/as-test
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Date:
+      - Tue, 09 Oct 2018 18:35:03 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - NlS+6olzg7/NinihkGpq++pIINSVnIO2ddkbpPip0xRS7UDZfc4KMSvhAExThoadEWzaJgO2vCA=
+      X-Amz-Request-Id:
+      - 9824A8B78727328C
+    status: 204 No Content
+    code: 204
+    duration: ""
diff --git a/blob/s3blob/testdata/TestConformance/TestSys/ContentLanguage.yaml b/blob/s3blob/testdata/TestConformance/TestSys/ContentLanguage.yaml
new file mode 100644
index 00000000..8d6891ba
--- /dev/null
+++ b/blob/s3blob/testdata/TestConformance/TestSys/ContentLanguage.yaml
@@ -0,0 +1,181 @@
+---
+version: 1
+interactions:
+- request:
+    body: hello world
+    form: {}
+    headers:
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Md5:
+      - XrY7u+Ae7tCTyyK7j1rNww==
+      Content-Type:
+      - text/plain; charset=utf-8
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64) S3Manager
+      X-Amz-Content-Sha256:
+      - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
+      X-Amz-Date:
+      - 20181005T165125Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/sys-test
+    method: PUT
+  response:
+    body: ""
+    headers:
+      Content-Length:
+      - "0"
+      Date:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - s9EcL6UeFxOIyykRfMO8CvsqsrIRy8Il5xWWRY78v2b5NgiitvgdCRG4TKFGC/EGxxvdaFwkiWc=
+      X-Amz-Request-Id:
+      - DC97E0ACA814EF7D
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181005T165126Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/sys-test
+    method: HEAD
+  response:
+    body: ""
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - IAl4dhr9Jho7s+GEjtxNBOTjp9En7lyO8QHgLuN26eBIO7r5aO/w6mRU2A0vKLTSJkkLuTGSEnE=
+      X-Amz-Request-Id:
+      - 8211C7A104B18CCC
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181005T165126Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/sys-test
+    method: GET
+  response:
+    body: hello world
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - HbmRVx8WN+JYqvDd9I3IqJ1R1ynQafUklKB9FSBi5FsJd4K5Es1fkA2d6MFWg7TjQCjX/thSTl0=
+      X-Amz-Request-Id:
+      - 4120CF04BE21800E
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181005T165126Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/sys-test
+    method: HEAD
+  response:
+    body: ""
+    headers:
+      Accept-Ranges:
+      - bytes
+      Content-Language:
+      - nl
+      Content-Length:
+      - "11"
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Etag:
+      - '"5eb63bbbe01eeed093cb22bb8f5acdc3"'
+      Last-Modified:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - +AamOPlM2Jk437HMZw0yweR23EDGC84m+BVAmuwJnIOVTp04NxeTH0kWaaTgUGKZ9IqQIU+kIiE=
+      X-Amz-Request-Id:
+      - EDA3DAC03AD85288
+    status: 200 OK
+    code: 200
+    duration: ""
+- request:
+    body: ""
+    form: {}
+    headers:
+      User-Agent:
+      - aws-sdk-go/1.13.20 (go1.11rc2; linux; amd64)
+      X-Amz-Content-Sha256:
+      - e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+      X-Amz-Date:
+      - 20181005T165126Z
+    url: https://go-cloud-bucket.s3.us-east-2.amazonaws.com/sys-test
+    method: DELETE
+  response:
+    body: ""
+    headers:
+      Date:
+      - Fri, 05 Oct 2018 16:51:27 GMT
+      Server:
+      - AmazonS3
+      X-Amz-Id-2:
+      - 15yQi2pHlVa/9yhZ7FBYITe5oN5wc7Yr9JAwOPLuM44d/+G66LlR2/B+ptgKvt09wurX6rDuuCs=
+      X-Amz-Request-Id:
+      - 7C309C128335918D
+    status: 204 No Content
+    code: 204
+    duration: ""